{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Distribution.Nixpkgs.Haskell.FromCabal.Configuration ( Configuration(..), readConfiguration, assertConsistency ) where

import Control.Exception ( throwIO )
import Control.DeepSeq
import Control.Lens
import Control.Monad
import Data.Aeson
import Data.Map as Map
import Data.Set as Set
import Data.Text as T
import Data.Yaml
import Distribution.Compiler
import Distribution.Nixpkgs.Haskell.Constraint
import Distribution.Package
import Distribution.System
import GHC.Generics ( Generic )
import Language.Nix.Identifier

data Configuration = Configuration
  {
  -- |Target compiler. Used by 'finalizePackageDescription' to choose
  -- appropriate flags and dependencies.
    compilerInfo :: CompilerInfo

  -- |Compiler core packages that are also found on Hackage.
  , corePackages :: Set PackageIdentifier

  -- |These packages replace the latest respective version during
  -- dependency resolution.
  , defaultPackageOverrides :: [Constraint]

  -- |These packages are added to the generated set, but the play no
  -- role during dependency resolution.
  , extraPackages :: [Constraint]

  -- |We know that these packages won't build, so we give them an empty
  -- meta.hydraPlatforms attribute to avoid cluttering our Hydra output with
  -- lots of failure messages.
  , dontDistributePackages :: Map PackageName (Set Platform)

  -- |This information is used by the @hackage2nix@ utility to determine the
  -- 'maintainers' for a given Haskell package.
  , packageMaintainers :: Map Identifier (Set PackageName)
  }
  deriving (Show, Generic)

instance NFData Configuration

instance FromJSON Configuration where
  parseJSON (Object o) = Configuration
        <$> o .:? "compiler" .!= unknownCompilerInfo buildCompilerId NoAbiTag
        <*> o .:? "core-packages" .!= mempty
        <*> o .:? "default-package-overrides" .!= mempty
        <*> o .:? "extra-packages" .!= mempty
        <*> o .:? "dont-distribute-packages" .!= mempty
        <*> o .:? "package-maintainers" .!= mempty
  parseJSON _ = error "invalid Configuration"

instance FromJSON Identifier where
  parseJSON (String s) = pure (review ident (T.unpack s))
  parseJSON s = fail ("parseJSON: " ++ show s ++ " is not a valid Nix identifier")

#if MIN_VERSION_aeson(1,0,0)

instance FromJSONKey Identifier where
  fromJSONKey = FromJSONKeyText parseKey

instance FromJSONKey PackageName where
  fromJSONKey = FromJSONKeyText parseKey

#elif MIN_VERSION_aeson(0,11,0)

instance (FromJSON v) => FromJSON (Map Identifier v) where
  parseJSON = fmap (Map.mapKeys parseKey) . parseJSON

instance (FromJSON v) => FromJSON (Map PackageName v) where
  parseJSON = fmap (Map.mapKeys parseKey) . parseJSON

#else

instance (Ord k, FromJSON k, FromJSON v) => FromJSON (Map k v) where
  parseJSON  = fmap (Map.mapKeys parseKey) . parseJSON

#endif

parseKey :: FromJSON k => Text -> k
parseKey s = either error id (parseEither parseJSON (String s))

readConfiguration :: FilePath -> IO Configuration
readConfiguration path =
  decodeFileEither path >>= either throwIO assertConsistency

assertConsistency :: Monad m => Configuration -> m Configuration
assertConsistency cfg@Configuration {..} = do
  let report msg = fail ("*** configuration error: " ++ msg)

      maintainedPackages = Set.unions (Map.elems packageMaintainers)
      disabledPackages = Map.keysSet dontDistributePackages
      disabledMaintainedPackages = maintainedPackages `Set.intersection` disabledPackages
  unless (Set.null disabledMaintainedPackages) $
    report ("disabled packages that have a maintainer: " ++ show disabledMaintainedPackages)

  return cfg