content: {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ViewPatterns #-}
  module Tinc.Freeze (
    Constraint
  , writeFreezeFile
  , readFreezeFile
  , freezeFile
  , addSourceConstraint
  ) where
  
  import           Data.Aeson
  import qualified Data.ByteString as B
  import           Data.Char
  import           Data.Ord
  import           Data.List
  import           Data.Version (showVersion)
  import qualified Data.Yaml as Yaml
  import           Hpack.Yaml
  import           Data.Aeson.Types
  import           Control.Exception
  import           Control.Monad
  import           System.IO.Error
  import           System.Directory
  
  import           Tinc.Fail
  import           Tinc.Package
  import           Tinc.SourceDependency
  
  type Constraint = String
  
  freezeFile :: FilePath
  freezeFile = "tinc.freeze"
  
  data Dependency = Dependency {
    name :: String
  , version :: String
  } deriving (Eq, Show, Generic)
  
  instance FromJSON Dependency
  
  instance ToJSON Dependency where
    toJSON = genericToJSON defaultOptions
  
  data FreezeFile = FreezeFile {
    dependencies :: [Dependency]
  } deriving (Eq, Show, Generic)
  
  instance FromJSON FreezeFile
  
  instance ToJSON FreezeFile where
    toJSON = genericToJSON defaultOptions
  
  writeFreezeFile :: [Package] -> IO ()
  writeFreezeFile deps = do
    old <- either (const Nothing) Just <$> tryJust (guard . isDoesNotExistError) (B.readFile freezeFile)
    unless (Just contents == old) $ do
      B.writeFile freezeFile contents
    where
      contents = Yaml.encode $ FreezeFile (sortByName $ map toDependency deps)
  
  sortByName :: [Dependency] -> [Dependency]
  sortByName = sortBy $ comparing f
    where
      f :: Dependency -> (String, String)
      f (Dependency n _) = (map toLower n, n)
  
  toDependency :: Package -> Dependency
  toDependency (Package n (Version v _)) = Dependency {name =  n, version = v}
  
  toConstraint :: Dependency -> Constraint
  toConstraint (Dependency n v) = "--constraint=" ++ n ++ " == " ++ v
  
  readFreezeFile :: [SourceDependency] -> IO [Constraint]
  readFreezeFile (map sourceDependencyPackageName -> sourceDependencies) = do
    exists <- doesFileExist freezeFile
    if exists
      then decodeYaml freezeFile >>=
        (either die (return . map toConstraint . removeAddSourceDependencies . dependencies)) . (>>= parseEither parseJSON . snd)
      else return []
    where
      removeAddSourceDependencies = filter ((`notElem` sourceDependencies) . name)
  
  addSourceConstraint :: SourceDependencyWithVersion -> Constraint
  addSourceConstraint (SourceDependency n _, v) = toConstraint (Dependency n $ showVersion v)
  