content: module IHP.IDE.CodeGen.Controller where
  
  import IHP.ControllerPrelude hiding (appPort)
  import IHP.IDE.ToolServer.Types
  import IHP.IDE.CodeGen.View.Generators
  import IHP.IDE.CodeGen.View.NewController
  import IHP.IDE.CodeGen.View.NewScript
  import IHP.IDE.CodeGen.View.NewView
  import IHP.IDE.CodeGen.View.NewMail
  import IHP.IDE.CodeGen.View.NewAction
  import IHP.IDE.CodeGen.View.NewApplication
  import IHP.IDE.CodeGen.View.NewJob
  import IHP.IDE.CodeGen.Types
  import IHP.IDE.CodeGen.ControllerGenerator as ControllerGenerator
  import IHP.IDE.CodeGen.ScriptGenerator as ScriptGenerator
  import IHP.IDE.CodeGen.ViewGenerator as ViewGenerator
  import IHP.IDE.CodeGen.MailGenerator as MailGenerator
  import IHP.IDE.CodeGen.ActionGenerator as ActionGenerator
  import IHP.IDE.CodeGen.ApplicationGenerator as ApplicationGenerator
  import IHP.IDE.CodeGen.JobGenerator as JobGenerator
  import IHP.IDE.ToolServer.Helper.Controller
  import qualified System.Process as Process
  import qualified System.Directory as Directory
  import qualified Data.Text as Text
  import qualified Data.Text.IO as Text
  import qualified Text.Inflections as Inflector
  import System.Directory
  
  instance Controller CodeGenController where
      action GeneratorsAction = do
          render GeneratorsView
  
      action NewControllerAction = do
          let controllerName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let pagination = paramOrDefault False "pagination"
          controllerAlreadyExists <- doesControllerExist controllerName applicationName
          applications <- findApplications
          when controllerAlreadyExists do
              setErrorMessage "Controller with this name does already exist."
              redirectTo NewControllerAction
          plan <- ControllerGenerator.buildPlan controllerName applicationName pagination
          render NewControllerView { .. }
          where
              doesControllerExist controllerName applicationName = doesFileExist $ cs applicationName <> "/Controller/" <> cs controllerName <> ".hs"
  
      action CreateControllerAction = do
          let controllerName = param "name"
          let applicationName = param "applicationName"
          let pagination = paramOrDefault False "pagination"
          (Right plan) <- ControllerGenerator.buildPlan controllerName applicationName pagination
          executePlan plan
          setSuccessMessage "Controller generated"
          redirectTo GeneratorsAction
  
      action NewScriptAction = do
          let scriptName = paramOrDefault "" "name"
          scriptAlreadyExists <- doesFileExist $ "Application/Script/" <> cs scriptName <> ".hs"
          when scriptAlreadyExists do
              setErrorMessage "Script with this name already exists."
              redirectTo NewScriptAction
          let plan = ScriptGenerator.buildPlan scriptName
          render NewScriptView { .. }
  
      action CreateScriptAction = do
          let scriptName = paramOrDefault "" "name"
          let (Right plan) = ScriptGenerator.buildPlan scriptName
          executePlan plan
          setSuccessMessage "Script generated"
          redirectTo GeneratorsAction
  
      action NewViewAction = do
          let viewName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let controllerName = paramOrDefault "" "controllerName"
          viewAlreadyExists <- doesFileExist $ (cs applicationName) <> "/View/" <> (cs controllerName) <> "/" <> (cs viewName) <>".hs"
          when viewAlreadyExists do
              setErrorMessage "View with this name already exists."
              redirectTo NewViewAction
          controllers <- findControllers applicationName
          applications <- findApplications
          plan <- ViewGenerator.buildPlan viewName applicationName controllerName
          render NewViewView { .. }
  
      action CreateViewAction = do
          let viewName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let controllerName = paramOrDefault "" "controllerName"
          (Right plan) <- ViewGenerator.buildPlan viewName applicationName controllerName
          executePlan plan
          setSuccessMessage "View generated"
          redirectTo GeneratorsAction
  
      action NewMailAction = do
          let mailName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let controllerName = paramOrDefault "" "controllerName"
          mailAlreadyExists <- doesFileExist $ (cs applicationName) <> "/Mail/" <> (cs controllerName) <> "/" <> (cs mailName) <>".hs"
          when mailAlreadyExists do
              setErrorMessage "Mail with this name already exists."
              redirectTo NewMailAction
          controllers <- findControllers applicationName
          applications <- findApplications
          plan <- MailGenerator.buildPlan mailName applicationName controllerName
          render NewMailView { .. }
  
      action CreateMailAction = do
          let mailName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let controllerName = paramOrDefault "" "controllerName"
          (Right plan) <- MailGenerator.buildPlan mailName applicationName controllerName
          executePlan plan
          setSuccessMessage "Mail generated"
          redirectTo GeneratorsAction
  
      action NewActionAction = do
          let actionName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let controllerName = paramOrDefault "" "controllerName"
          let doGenerateView = paramOrDefault False "doGenerateView"
          controllers <- findWebControllers
          applications <- findApplications
          plan <- ActionGenerator.buildPlan actionName applicationName controllerName doGenerateView
          render NewActionView { .. }
  
      action CreateActionAction = do
          let actionName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          let controllerName = paramOrDefault "" "controllerName"
          let doGenerateView = paramOrDefault False "doGenerateView"
          (Right plan) <- ActionGenerator.buildPlan actionName applicationName controllerName doGenerateView
          executePlan plan
          setSuccessMessage $ "Action" ++ (if doGenerateView then " and View " else "") ++ " generated"
          redirectTo GeneratorsAction
  
      action NewApplicationAction = do
          let applicationName = paramOrDefault "" "name"
          plan <- ApplicationGenerator.buildPlan applicationName
          render NewApplicationView { .. }
  
      action CreateApplicationAction = do
          let applicationName = paramOrDefault "" "name"
          (Right plan) <- ApplicationGenerator.buildPlan applicationName
          executePlan plan
          setSuccessMessage "Application generated"
          redirectTo GeneratorsAction
  
      action NewJobAction = do
          let jobName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          controllers <- findControllers applicationName
          applications <- findApplications
          plan <- JobGenerator.buildPlan jobName applicationName
          render NewJobView { .. }
  
      action CreateJobAction = do
          let jobName = paramOrDefault "" "name"
          let applicationName = paramOrDefault "Web" "applicationName"
          (Right plan) <- JobGenerator.buildPlan jobName applicationName
          executePlan plan
          setSuccessMessage "Job generated"
          redirectTo GeneratorsAction
  
      action OpenControllerAction = do
          let name = param "name"
          case name |> Inflector.toCamelCased True of
              Left error -> renderPlain "Failed to transform name to camel case"
              Right indexActionName-> do
                  appPort <- theAppPort
                  redirectToUrl ("http://localhost:" <> tshow appPort <> "/" <> indexActionName)
  
  
  executePlan :: [GeneratorAction] -> IO ()
  executePlan actions = forEach actions evalAction
      where
          evalAction CreateFile { filePath, fileContent } = do
              Text.writeFile (cs filePath) (cs fileContent)
              putStrLn ("+ " <> filePath)
          evalAction AppendToFile { filePath, fileContent } = do
              Text.appendFile (cs filePath) fileContent
              putStrLn ("* " <> filePath)
          evalAction AppendToMarker { marker, filePath, fileContent } = do
              content <- Text.readFile (cs filePath)
              let newContent = Text.replace marker (marker <> "\n" <> cs fileContent) (cs content)
              Text.writeFile (cs filePath) (cs newContent)
              putStrLn ("* " <> filePath <> " (import)")
          evalAction AddImport { filePath, fileContent } = do
              addImport filePath fileContent
              putStrLn ("* " <> filePath <> " (import)")
          evalAction AddAction { filePath, fileContent } = do
              addAction filePath [fileContent]
              putStrLn ("* " <> filePath <> " (AddAction)")
          evalAction AddMountToFrontController { filePath, applicationName } = do
              addMountControllerStatement filePath applicationName
              putStrLn ("* " <> filePath <> " (AddMountToFrontController)")
          evalAction AddToDataConstructor { dataConstructor, filePath, fileContent } = do
              content <- Text.readFile (cs filePath)
              case addToDataConstructor content dataConstructor fileContent of
                  Just newContent -> do
                      Text.writeFile (cs filePath) (cs newContent)
                      putStrLn ("* " <> filePath <> " (AddToDataConstructor)")
                  Nothing -> putStrLn ("Could not automatically add " <> tshow content <> " to " <> filePath)
          evalAction EnsureDirectory { directory } = do
              Directory.createDirectoryIfMissing True (cs directory)
          evalAction RunShellCommand { shellCommand } = do
              _ <- Process.system (cs shellCommand)
              putStrLn ("* " <> shellCommand)
  
  undoPlan :: [GeneratorAction] -> IO()
  undoPlan actions = forEach actions evalAction
      where
          evalAction CreateFile { filePath, fileContent } = do
              (Directory.removeFile (cs filePath)) `catch` handleError
              putStrLn ("- " <> filePath)
          evalAction AppendToFile { filePath, fileContent } = do
              deleteTextFromFile (cs filePath) fileContent `catch` handleError
              putStrLn ("* " <> filePath)
          evalAction AppendToMarker { marker, filePath, fileContent } = do
              (deleteTextFromFile (cs filePath) (fileContent <> "\n")) `catch` handleError
              putStrLn ("* " <> filePath <> " (import)")
          evalAction AddImport { filePath, fileContent } = do
              (deleteTextFromFile (cs filePath) (fileContent <> "\n")) `catch` handleError
              putStrLn ("* " <> filePath <> " (import)")
          evalAction AddAction { filePath, fileContent } = do
              (deleteTextFromFile (cs filePath) (fileContent <> "\n")) `catch` handleError
              putStrLn ("* " <> filePath <> " (RemoveAction)")
          evalAction AddToDataConstructor { dataConstructor, filePath, fileContent } = do
              (deleteTextFromFile (cs filePath) (fileContent <> "\n")) `catch` handleError
              putStrLn ("* " <> filePath <> " (RemoveFromDataConstructor)")
          evalAction EnsureDirectory { directory } = do
              (Directory.removeDirectory (cs directory)) `catch` handleError
          evalAction RunShellCommand { shellCommand } = pure ()
          handleError :: SomeException -> IO ()
          handleError ex = putStrLn (tshow ex)
  
  deleteTextFromFile :: Text -> Text -> IO ()
  deleteTextFromFile filePath lineContent = do
      fileContent <- Text.readFile (cs filePath)
      let replacedContent = Text.replace lineContent "" fileContent
      Text.writeFile (cs filePath) replacedContent
  
  addImport :: Text -> Text -> IO ()
  addImport file importStatement = do
      content :: Text <- Text.readFile (cs file)
      case addImport' content importStatement of
          Just newContent -> Text.writeFile (cs file) (cs newContent)
          Nothing -> pure ()
      pure ()
  
  addImport' :: Text -> Text -> Maybe Text
  addImport' content importStatement = do
      if importStatement `isInfixOf` content
          then Nothing
          else appendLineAfter content ("import" `isPrefixOf`) [importStatement]
  
  addAction :: Text -> [Text] -> IO ()
  addAction filePath fileContent = do
      content <- Text.readFile (cs filePath)
      case addAction' content fileContent of
          Just newContent -> Text.writeFile (cs filePath) (cs newContent)
          Nothing -> putStrLn ("Could not automatically add " <> tshow content <> " to " <> filePath)
      pure ()
  
  addAction' :: Text -> [Text] -> Maybe Text
  addAction' fileContent = appendLineAfter fileContent ("instance Controller" `isPrefixOf`)
  
  addMountControllerStatement :: Text -> Text -> IO ()
  addMountControllerStatement file applicationName = do
      content :: Text <- Text.readFile (cs file)
      case addMountControllerStatement' applicationName content of
          Just newContent -> Text.writeFile (cs file) (cs newContent)
          Nothing -> putStrLn ("Could not automatically add " <> tshow applicationName <> " to " <> file)
      pure ()
  
  addMountControllerStatement' :: Text -> Text -> Maybe Text
  addMountControllerStatement' applicationName file =
      let withMaybeMountedFrontController = appendLineAfter file ("mountFrontController" `isInfixOf`) ["            , mountFrontController " <> applicationName <> "Application"]
      in
          case withMaybeMountedFrontController of
              Just result -> Just result
              Nothing -> Just (Text.replace needle replacement file)
                  where
                      needle =  "    controllers = []"
                      replacement = "    controllers = [\n            mountFrontController " <> applicationName <> "Application" <> "\n        ]"
  
  -- | Gets content of a Types.hs, a existent data constructor and a type which should be added to it
  --   and returns fileContent with the type in it.
  addToDataConstructor :: Text -> Text -> Text -> Maybe Text
  addToDataConstructor fileContent dataConstructor content = do
      lineOfDataConstructor <- lines fileContent
          |> zip [1..]
          |> filter (\(n, line) -> dataConstructor `isInfixOf` line)
          |> lastMay
          |> fmap fst
      lineOfDerivingStatement <- ((drop lineOfDataConstructor $ lines fileContent) :: [Text])
          |> zip [lineOfDataConstructor..]
          |> filter (\(n, line) -> "deriving" `isInfixOf` line)
          |> headMay
          |> fmap fst
      Just $ unlines $ ((take lineOfDerivingStatement $ lines fileContent) <> [content] <> (drop lineOfDerivingStatement $ lines fileContent))
  
  appendLineAfter :: Text -> (Text -> Bool) -> [Text] -> Maybe Text
  appendLineAfter file isRelevantLine newLines =
      let content :: [Text] = lines file
          lastImportLine = content
              |> zip [1..]
              |> filter (\(n, line) -> isRelevantLine line)
              |> lastMay
              |> fmap fst
      in fmap (\lastImportLine -> unlines $ (take lastImportLine content) <> newLines <> (drop lastImportLine content)) lastImportLine
  