module MicroCabal.StackageList(
  StackageList,
  StackagePackage(..),
  PackageName, FlagName,
  showPackage,
  readPackage,
  yamlToStackageList,
  ) where
import Data.Maybe
import Data.Version
import Text.Read
import MicroCabal.YAML

type StackageList = [StackagePackage]
type PackageName = String
type FlagName = String

data StackagePackage = StackagePackage {
  stName    :: PackageName,
  stVersion :: Version,
  stHidden  :: Bool,
  stFlags   :: [(FlagName, Bool)]
  }
  deriving (Show)

showPackage :: StackagePackage -> String
showPackage st = unwords $ stName st : showVersion (stVersion st) : show (stHidden st) : map flag (stFlags st)
  where flag (n,b) = n ++ "=" ++ show b

readPackage :: String -> StackagePackage
readPackage spkg =
  case words spkg of
    name : vers : hide : flgs ->
      StackagePackage { stName = name, stVersion = readVersion vers, stHidden = read hide, stFlags = map flag flgs }
    _ -> error "readPackage"
  where flag s = (n, read (drop 1 b)) where (n, b) = span (/= '=') s

yamlToStackageList :: YAMLValue -> [StackagePackage]
yamlToStackageList (YRecord flds) =
  let lookf s = fromMaybe (error $ "yamlToStackageList: no " ++ s) $ lookup s flds
  in  case (lookf "flags", lookf "hidden", lookf "packages") of
        (YRecord flags, YRecord hidden, YArray packages) -> 
          map (addFlags flags . addHidden hidden . decodePackage) packages
        _ -> error "Unrecognized Stackage package list format"
yamlToStackageList _ = error "Unrecognized Stackage package list format"

addFlags :: [(YAMLFieldName, YAMLValue)] -> StackagePackage -> StackagePackage
addFlags flags st = maybe st (\ f -> st{ stFlags = yflags f }) $ lookup (stName st) flags
  where yflags (YRecord r) = [(n, b) | (n, YBool b) <- r]
        yflags _ = error "addFlags"

addHidden :: [(YAMLFieldName, YAMLValue)] -> StackagePackage -> StackagePackage
addHidden hidden st = maybe st (\ f -> st{ stHidden = ybool f }) $ lookup (stName st) hidden
  where ybool (YBool b) = b
        ybool _ = error "addHidden"

decodePackage :: YAMLValue -> StackagePackage
decodePackage (YRecord (("hackage", YString fs):_)) = StackagePackage { stName = n, stVersion = v, stHidden = False, stFlags = [] }
  where
    s = takeWhile (/= '@') fs
    (n, v) =
      case span (/= '-') (reverse s) of
        (rv, rn) -> (reverse (drop 1 rn), readVersion (reverse rv))
decodePackage y = error $ "Bad package desc " ++ show y

readVersion :: String -> Version
readVersion s = fromMaybe (error $ "decodePackage: bad version " ++ s) $ readVersionM s

readVersionM :: String -> Maybe Version
readVersionM s = makeVersion <$> (mapM readMaybe . words . map (\ c -> if c == '.' then ' ' else c) $ s)
