module Development.Duplo.Git where

import           Control.Applicative             ((<$>))
import           Control.Lens                    hiding (Action, Level)
import           Data.List                       (filter, intercalate)
import           Data.Text                       (pack, splitOn, unpack)
import qualified Development.Duplo.Component     as CM
import qualified Development.Duplo.Types.AppInfo as AI
import qualified Development.Duplo.Types.Config  as TC
import           Development.Shake
import           Development.Shake.FilePath      ((</>))
import           System.FilePath.Posix           (makeRelative)

type Level         = String
type Version       = String
type Subversion    = String
type FileExtension = String

-- We use Semantic Versioning protocol
versionLength :: Int
versionLength = 3

-- | Commit to git and bump version for current project
commit :: TC.BuildConfig
       -> Level
       -> Action (Version, Version)
commit config level = do
    let utilPath = config ^. TC.utilPath
    appInfo <- liftIO CM.readManifest
    let version = AI.version appInfo
    let cwd = config ^. TC.cwd
    let manifest = cwd </> "component.json"

    -- First stash any outstanding change
    command_ [] "git" ["stash"]
    -- Then make sure we're on master
    command_ [] "git" ["checkout", "master"]

    -- Increment version according to level
    let newVersion = incrementVersion level version
    -- Update app info
    let appInfo' = updateVersion appInfo newVersion
    -- Update registered file list with Component.IO
    appInfo'' <- updateFileRegistry config appInfo'
    -- Commit app info
    liftIO $ CM.writeManifest appInfo''

    -- Commit with the version
    command_ [] (utilPath </> "commit.sh") [newVersion]

    return (version, newVersion)

-- | Bump a version, given a bump type
incrementVersion :: Version
                 -> Level
                 -> Version
incrementVersion level version =
    intercalate "." $ incrementSubversion expanded index
  where
    expanded = unpack <$> splitOn (pack ".") (pack version)
    index    = case level of
                 "major" -> 0
                 "minor" -> 1
                 "patch" -> 2

-- | Given a version component, increment it.
incrementSubversion :: [Subversion] -> Int -> [Subversion]
incrementSubversion version index =
    resetVer
  where
    oldPart  = version ^. element index
    oldPart' = read oldPart :: Int
    newPart  = oldPart' + 1
    newPart' = show newPart :: String
    -- Increment target subversion
    incrVer  = version & element index .~ newPart'
    -- Reset other
    resetVer = resetSubversion incrVer (index + 1) versionLength

-- | Reset a version component. The reset cascades up to the max subversion
-- index.
resetSubversion :: [Subversion] -> Int -> Int -> [Subversion]
resetSubversion version index max
  | index <= max = let newVersion = resetSubversion version (index + 1) max
                   in  newVersion & element index .~ "0"
  | otherwise    = version

updateVersion :: AI.AppInfo -> Version -> AI.AppInfo
updateVersion manifest version = manifest { AI.version = version }

-- | Read from the given directory and update the app manifest object.
updateFileRegistry :: TC.BuildConfig -> AI.AppInfo -> Action AI.AppInfo
updateFileRegistry config appInfo = do
    let cwd = config ^. TC.cwd
    let utilPath = config ^. TC.utilPath
    let appPath = cwd </> "app"
    let assetPath = appPath </> "assets"
    let imagePath = assetPath </> "images"
    let fontPath = assetPath </> "fonts"

    -- Helper functions
    let find path pttrn = command [] (utilPath </> "find.sh") [path, pttrn]
    let split = fmap unpack . splitOn "\n" . pack
    let makeRelative' = makeRelative cwd
    let filterNames = filter ((> 0) . length)
    let prepareFileList = filterNames . fmap (makeRelative cwd) . split

    -- Collect eligible files
    Stdout scripts <- find appPath "*.js"
    Stdout styles <- find appPath "*.styl"
    Stdout markups <- find appPath "*.jade"
    Stdout images <- find imagePath "*"
    Stdout fonts <- find fontPath "*"

    -- Convert to arrays and make relative to cwd
    let scripts' = prepareFileList scripts
    let styles' = prepareFileList styles
    let markups' = prepareFileList markups
    let images' = prepareFileList images
    let fonts' = prepareFileList fonts

    -- Update manifest
    return appInfo { AI.images    = images'
                   , AI.scripts   = scripts'
                   , AI.styles    = styles'
                   , AI.templates = markups'
                   , AI.fonts     = fonts'
                   }