{-# LANGUAGE Rank2Types #-}

module Distribution.Nixpkgs.Haskell.Stack.PrettyPrinting where

import           Control.Lens
import           Data.Foldable as F
import           Data.List as L
import           Data.List.NonEmpty as NE
import           Data.Maybe
import           Data.String
import           Distribution.Nixpkgs.Haskell.Derivation
import           Distribution.Nixpkgs.Haskell.Packages.PrettyPrinting as PP
import           Distribution.Package
import           Distribution.Text
import           Distribution.Version (Version)
import qualified Language.Nix.FilePath as Nix
import           Language.Nix.PrettyPrinting as PP


data OverrideConfig = OverrideConfig
  { _ocGhc              :: !Version
  , _ocStackagePackages :: !FilePath
  , _ocStackageConfig   :: !FilePath
  , _ocNixpkgs          :: !FilePath
  }

makeLenses ''OverrideConfig

systemNixpkgs :: Doc
systemNixpkgs = "<nixpkgs>"

hasField :: Lens' a (Maybe b) -> a -> Bool
hasField p = views p isJust

overridePackages :: (Foldable t, Functor t) => t Derivation -> Doc
overridePackages = PP.packageSetConfig . PP.cat . F.toList . fmap callPackage
  where
    drvNameQuoted   = PP.doubleQuotes . disp . pkgName . view pkgid
    callPackage drv = hang
      (drvNameQuoted drv <> " = callPackage") 2
      (PP.parens (PP.pPrint drv) <+> "{};")

importStackagePackages :: FilePath -> Doc
importStackagePackages path = hsep
  [ funarg "self",  "import", disp (fromString path :: Nix.FilePath), "{"
  , "inherit pkgs stdenv;"
  , "inherit (self) callPackage;"
  , "}"
  ]

callStackageConfig :: FilePath -> Doc
callStackageConfig path = hsep
  [ "callPackage", disp (fromString path :: Nix.FilePath), "{}"]

overrideHaskellPackages :: OverrideConfig -> NonEmpty Derivation -> Doc
overrideHaskellPackages oc packages =
  let
    nixpkgs = if oc ^. ocNixpkgs . to fromString == systemNixpkgs
      then systemNixpkgs
      else (disp . (fromString :: FilePath -> Nix.FilePath)) (oc ^. ocNixpkgs)
  in vcat
  [ funargs
    [ "nixpkgs ? import " <> nixpkgs <> " {}"
    ]
  , ""
  , "with nixpkgs;"
  , "let"
  , nest 2 "inherit (stdenv.lib) extends;"
  , nest 2 $ vcat
    [ attr "stackagePackages" . importStackagePackages $ oc ^. ocStackagePackages
    , attr "stackageConfig" . callStackageConfig $ oc ^. ocStackageConfig ]
  , nest 2 $ vcat
    [ "stackPackages ="
    , nest 2 $ overridePackages packages <> semi
    , ""
    , "pkgOverrides = self: stackPackages {"
    , nest 2 "inherit pkgs stdenv;"
    , nest 2 "inherit (self) callPackage;"
    , "};"
    , ""
    ]
  , "in callPackage (nixpkgs.path + \"/pkgs/development/haskell-modules\") {"
  , nest 2 $ vcat
    [ attr "ghc" ("pkgs.haskell.compiler." <> toNixGhcVersion (oc ^. ocGhc))
    , attr "compilerConfig" "self: extends pkgOverrides (extends stackageConfig (stackagePackages self))"
    , attr "haskellLib" "callPackage (nixpkgs.path + \"/pkgs/development/haskell-modules/lib.nix\") {}"
    ]
  , "}"
  ]

toNixGhcVersion :: Version -> Doc
toNixGhcVersion =
  (<>) "ghc" . text . L.filter (/= '.') . show . disp
