module Runner ( run ) where

import Control.Lens
import Data.Foldable as F
import Data.Functor
import Distribution.Nixpkgs.Haskell.FromStack
import Distribution.Nixpkgs.Haskell.FromStack.Package
import Distribution.Nixpkgs.Haskell.Stack
import Distribution.Nixpkgs.Haskell.Stack.PrettyPrinting as PP
import Distribution.Version (Version)
import Distribution.Compiler (AbiTag(..), unknownCompilerInfo)
import Distribution.Package (mkPackageName, pkgName)
import Distribution.Text as Text (display)
import Language.Nix as Nix
import Options.Applicative
import Paths_stackage2nix ( version )
import Runner.Cli
import Stack.Config
import Stack.Types
import Stackage.Types
import System.IO (withFile, IOMode(..), hPutStrLn, hPrint)
import Text.PrettyPrint.HughesPJClass (render)

import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified LtsHaskell as LH


run :: IO ()
run = do
  opts <- execParser pinfo
  stackYaml <- envStackYaml >>= \case
    Just p  -> putStrLn "Getting project config file from STACK_YAML environment" $> p
    Nothing -> pure $ opts ^. optStackYaml
  stackConf <- either fail pure =<< readStackConfig stackYaml
  let buildPlanFile = LH.buildPlanFilePath (opts ^. optLtsHaskellRepo) (stackConf ^. scResolver)
  buildPlan <- LH.loadBuildPlan buildPlanFile
  packageSetConfig <- LH.buildPackageSetConfig
    (opts ^. optAllCabalHashesRepo)
    (opts ^. optNixpkgsRepository)
    buildPlan
  -- generate haskell packages override
  let
    overrideConfig = mkOverrideConfig opts (siGhcVersion $ bpSystemInfo buildPlan)
    stackPackagesConfig = mkStackPackagesConfig opts
  packages <- traverse (packageDerivation stackPackagesConfig (opts ^. optHackageDb))
    $ stackConf ^. scPackages
  let out = PP.overrideHaskellPackages overrideConfig packages
  withFile (opts ^. optOutDerivation) WriteMode $ \h -> do
    hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ stackYaml ^. syFilePath)
    hPrint h out
  let
    reachable = Set.map mkPackageName
      $ F.foldr1 Set.union
      $ nodeDepends . mkNode <$> packages
    s2nLoader mHash pkgId =
      if pkgName pkgId `Set.member` reachable
      then packageLoader packageSetConfig Nothing pkgId
      else packageLoader packageSetConfig mHash pkgId
    s2nPackageSetConfig = packageSetConfig { packageLoader = s2nLoader }
    s2nPackageConfig = PackageConfig
      { enableCheck   = opts ^. optDoCheckStackage
      , enableHaddock = opts ^. optDoHaddockStackage }
  allNodes <- traverse (uncurry (buildNodeM s2nPackageSetConfig s2nPackageConfig))
    $ Map.toList (bpPackages buildPlan)

  -- Find all reachable dependencies in stackage set to stick into
  -- stackage packages file. This is performed on the full stackage
  -- set rather than pruning stackage packages beforehand because
  -- stackage does not concern itself with build tools while cabal2nix
  -- does: pruning only after generating full set of packages allows
  -- us to make sure all those extra dependencies are explicitly
  -- listed as well.
  let nodes = case opts ^. optOutPackagesClosure of
        True -> flip reachableDependencies allNodes
                -- Originally reachable nodes are root nodes
                $ filter (\n -> mkPackageName (nodeName n) `Set.member` reachable) allNodes
        False -> allNodes

  withFile (opts ^. optOutStackagePackages) WriteMode $ \h -> do
    hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ buildPlanFile)
    hPutStrLn h $ render $ pPrintOutPackages (view nodeDerivation <$> nodes)
  withFile (opts ^. optOutStackageConfig) WriteMode $ \h -> do
    hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ buildPlanFile)
    hPutStrLn h $ render $ pPrintOutConfig (bpSystemInfo buildPlan) nodes

mkOverrideConfig :: Options -> Version -> OverrideConfig
mkOverrideConfig opts ghcVersion = OverrideConfig
  { _ocGhc              = ghcVersion
  , _ocStackagePackages = opts ^. optOutStackagePackages
  , _ocStackageConfig   = opts ^. optOutStackageConfig
  , _ocNixpkgs          = opts ^. optNixpkgsRepository }

mkStackPackagesConfig :: Options -> StackPackagesConfig
mkStackPackagesConfig opts = StackPackagesConfig
  { _spcHaskellResolver   = const True
  , _spcNixpkgsResolver   = \i -> Just (Nix.binding # (i, Nix.path # [i]))
  , _spcTargetPlatform    = opts ^. optPlatform
  , _spcTargetCompiler    = unknownCompilerInfo (opts ^. optCompilerId) NoAbiTag
  , _spcFlagAssignment    = []
  , _spcDoCheckPackages   = opts ^. optDoCheckPackages
  , _spcDoHaddockPackages = opts ^. optDoHaddockPackages
  , _spcDoCheckStackage   = opts ^. optDoCheckStackage
  , _spcDoHaddockStackage = opts ^. optDoHaddockStackage }
