{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE StrictData        #-}
module HaskellCI.GitHub.Yaml where

import HaskellCI.Prelude

import qualified Data.Map.Strict as M

import HaskellCI.Compiler
import HaskellCI.List
import HaskellCI.Sh
import HaskellCI.YamlSyntax

-------------------------------------------------------------------------------
-- Data
-------------------------------------------------------------------------------

data GitHub = GitHub
    { ghName :: String
    , ghOn   :: GitHubOn
    , ghJobs :: M.Map String GitHubJob
    }
  deriving (Show)

newtype GitHubOn = GitHubOn
    { ghBranches :: [String]
    }
  deriving (Show)

data GitHubJob = GitHubJob
    { ghjName            :: String
    , ghjRunsOn          :: String
    , ghjNeeds           :: [String]
    , ghjIf              :: Maybe String
    , ghjContainer       :: Maybe String
    , ghjServices        :: M.Map String GitHubService
    , ghjContinueOnError :: Maybe String
    , ghjMatrix          :: [GitHubMatrixEntry]
    , ghjSteps           :: [GitHubStep]
    , ghjTimeout         :: Natural
    }
  deriving (Show)

data SetupMethod = HVRPPA | GHCUP
  deriving Show

data GitHubMatrixEntry = GitHubMatrixEntry
    { ghmeCompiler     :: CompilerVersion
    , ghmeAllowFailure :: Bool
    , ghmeSetupMethod  :: SetupMethod
    }
  deriving (Show)

data GitHubStep = GitHubStep
    { ghsName :: String
    , ghsStep :: Either GitHubRun GitHubUses
    }
  deriving (Show)

-- | Steps with @run@
data GitHubRun = GitHubRun
    { ghsRun :: [Sh]
    , ghsEnv :: M.Map String String
    }
  deriving (Show)

-- | Steps with @uses@
data GitHubUses = GitHubUses
    { ghsAction :: String
    , ghsIf     :: Maybe String
    , ghsWith   :: M.Map String String
    }
  deriving (Show)

data GitHubService = GitHubService
    { ghServImage   :: String
    , ghServEnv     :: M.Map String String
    , ghServOptions :: Maybe String
    }
  deriving (Show)

-------------------------------------------------------------------------------
-- ToYaml
-------------------------------------------------------------------------------

instance ToYaml GitHub where
    toYaml GitHub {..} = ykeyValuesFilt []
        [ "name" ~> fromString ghName
        , "on"   ~> toYaml ghOn
        , "jobs" ~> ykeyValuesFilt []
            [ ([], j, toYaml job)
            | (j, job) <- M.toList ghJobs
            ]
        ]

instance ToYaml GitHubOn where
    toYaml GitHubOn {..}
        | null ghBranches
        = ylistFilt [] ["push", "pull_request"]
        | otherwise
        = ykeyValuesFilt []
              [ "push"         ~> branches
              , "pull_request" ~> branches
              ]
      where
        branches = ykeyValuesFilt []
            [ "branches" ~> ylistFilt [] (map fromString ghBranches)
            ]

instance ToYaml GitHubJob where
    toYaml GitHubJob {..} = ykeyValuesFilt [] $ buildList $ do
        item $ "name" ~> fromString ghjName
        item $ "runs-on" ~> fromString ghjRunsOn
        item $ "needs" ~> ylistFilt [] (map fromString ghjNeeds)
        for_ ghjIf $ \if_ ->
            item $ "if" ~> fromString if_
        item $ "timeout-minutes" ~> YNumber [] (toInteger ghjTimeout)
        item $ "container" ~> ykeyValuesFilt [] (buildList $
            for_ ghjContainer $ \image -> item $ "image" ~> fromString image)
        item $ "services" ~> toYaml ghjServices
        for_ ghjContinueOnError $ \continueOnError ->
            item $ "continue-on-error" ~> fromString continueOnError
        item $ "strategy" ~> ykeyValuesFilt []
            [ "matrix" ~> ykeyValuesFilt []
                [ "include" ~> ylistFilt [] (map toYaml ghjMatrix)
                ]
            , "fail-fast" ~> YBool [] False
            ]
        item $ "steps" ~> ylistFilt [] (map toYaml $ filter notEmptyStep ghjSteps)

instance ToYaml SetupMethod where
    toYaml HVRPPA = "hvr-ppa"
    toYaml GHCUP  = "ghcup"

instance ToYaml GitHubMatrixEntry where
    toYaml GitHubMatrixEntry {..} = ykeyValuesFilt []
        [ "compiler"        ~> fromString (dispGhcVersion ghmeCompiler)
        , "compilerKind"    ~> fromString (compilerKind ghmeCompiler)
        , "compilerVersion" ~> fromString (compilerVersion ghmeCompiler)
        , "setup-method"    ~> toYaml ghmeSetupMethod
        , "allow-failure"   ~> toYaml ghmeAllowFailure
        ]

instance ToYaml GitHubStep where
    toYaml GitHubStep {..} = ykeyValuesFilt [] $
        [ "name" ~> fromString ghsName
        ] ++ case ghsStep of
            Left GitHubRun {..} ->
                [ "run" ~> fromString (shlistToString ghsRun)
                , "env" ~> mapToYaml ghsEnv
                ]

            Right GitHubUses {..} -> buildList $ do
                item $ "uses" ~> fromString ghsAction
                for_ ghsIf $ \if_ -> item $ "if" ~> fromString if_
                item $ "with" ~> mapToYaml ghsWith

notEmptyStep :: GitHubStep -> Bool
notEmptyStep (GitHubStep _ (Left (GitHubRun [] _))) = False
notEmptyStep _                                      = True

instance ToYaml GitHubService where
    toYaml GitHubService {..} = ykeyValuesFilt [] $ buildList $ do
        item $ "image" ~> fromString ghServImage
        item $ "env"   ~> toYaml (YString [] <$> ghServEnv)
        for_ ghServOptions $ \opt -> item $ "options" ~> fromString opt

-------------------------------------------------------------------------------
-- Helpers
-------------------------------------------------------------------------------

mapToYaml :: M.Map String String -> Yaml [String]
mapToYaml m = ykeyValuesFilt []
    [ k ~> fromString v
    | (k, v) <- M.toList m
    ]
