module Development.Duplo where

import Control.Exception (throw)
import Control.Lens hiding (Action)
import Control.Monad (void, when, unless)
import Control.Monad.Except (runExceptT)
import Development.Duplo.Git as Git
import Development.Duplo.Markups as Markups
import Development.Duplo.Scripts as Scripts
import Development.Duplo.Static as Static
import Development.Duplo.Styles as Styles
import Development.Duplo.Utilities (logStatus, headerPrintSetter, successPrintSetter, createStdEnv)
import Development.Shake
import Development.Shake.FilePath ((</>))
import System.Console.GetOpt (OptDescr(..), ArgDescr(..))
import System.IO (readFile)
import qualified Development.Duplo.Component as CM
import qualified Development.Duplo.Types.AppInfo as AI
import qualified Development.Duplo.Types.Builder as BD
import qualified Development.Duplo.Types.Config as TC
import qualified Development.Duplo.Types.Options as OP
import qualified Development.Shake as DS

shakeOpts = shakeOptions { shakeThreads = 4 }

build :: String -> [String] -> TC.BuildConfig -> OP.Options -> IO ()
build cmdName cmdArgs config options = shake shakeOpts $ do
    let headerPrinter  = liftIO . logStatus headerPrintSetter
    let successPrinter = liftIO . logStatus successPrintSetter

    let port       = config ^. TC.port
    let cwd        = config ^. TC.cwd
    let utilPath   = config ^. TC.utilPath
    let miscPath   = config ^. TC.miscPath
    let targetPath = config ^. TC.targetPath
    let bumpLevel  = config ^. TC.bumpLevel
    let appName    = config ^. TC.appName
    let appVersion = config ^. TC.appVersion
    let appId      = config ^. TC.appId
    let duploPath  = config ^. TC.duploPath

    -- What to build and each action's related action
    let targetScript = targetPath </> "index.js"
    let targetStyle  = targetPath </> "index.css"
    let targetMarkup = targetPath </> "index.html"
    targetScript *> (void . runExceptT . Scripts.build config)
    targetStyle  *> (void . runExceptT . Styles.build config)
    targetMarkup *> (void . runExceptT . Markups.build config)

    -- Manually bootstrap Shake
    action $ do
      -- Keep a list of commands so we can check before we call Shake,
      -- which doesn't allow us to change the error message when an action
      -- isn't found.
      let actions = [ "static"
                    , "clean"
                    , "build"
                    , "bump"
                    , "init"
                    , "version"
                    ]

      -- Default to help
      let cmdName' = if cmdName `elem` actions then cmdName else "help"
      -- Call command
      need [cmdName']

      -- Trailing space
      putNormal ""

    -- Handling static assets
    Static.qualify config &?> Static.build config

    "static" ~> Static.deps config

    -- Install dependencies.
    "deps" ~> do
      liftIO $ logStatus headerPrintSetter "Installing dependencies"

      envOpt <- createStdEnv config

      command_ [envOpt] (utilPath </> "install-deps.sh") []

    "clean" ~> do
      -- Clean only when the target is there.
      needCleaning <- doesDirectoryExist targetPath
      when needCleaning $ liftIO $ removeFiles targetPath ["//*"]

      successPrinter "Clean completed"

    "build" ~> do
      -- Always rebuild if we're building for production.
      unless (TC.isInDev config) $ need ["clean"]

      -- Make sure all static files and dependencies are there.
      need ["static", "deps"]
      -- Then compile, in parallel.
      need [targetScript, targetStyle, targetMarkup]

      successPrinter "Build completed"

      when (TC.isInTest config) $ need ["test"]

    "bump" ~> do
      (oldVersion, newVersion) <- Git.commit config bumpLevel

      successPrinter $ "Bumped version from " ++ oldVersion ++ " to " ++ newVersion

    "init" ~> do
      let user = cmdArgs ^. element 0
      let repo = cmdArgs ^. element 1
      let name = user ++ "/" ++ repo
      let src = miscPath </> "boilerplate/"
      let dest = cwd ++ "/"

      -- Check prerequisites
      when (null user) $ throw BD.MissingGithubUserException
      when (null repo) $ throw BD.MissingGithubRepoException

      headerPrinter $ "Creating new duplo project " ++ name

      -- Initialize with boilerplate
      command_ [] (utilPath </> "init-boilerplate.sh") [src, dest]

      -- Update fields
      appInfo <- liftIO CM.readManifest
      let newAppInfo = appInfo { AI.name = repo
                               , AI.repo = name
                               }
      -- Commit app info
      liftIO $ CM.writeManifest newAppInfo

      -- Initalize git
      command_ [] (utilPath </> "init-git.sh") [name]

      successPrinter $ "Project created at " ++ dest

    "test" ~> do
      envOpt <- createStdEnv config
      command_ [envOpt] (utilPath </> "run-test.sh") [duploPath]

    -- Version should have already been displayed if requested
    "version" ~> return ()

    "help" ~> liftIO (readFile (miscPath </> "help.txt") >>= putStr)