{-# LANGUAGE DataKinds, GADTs, ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards, LambdaCase, QuasiQuotes, OverloadedStrings #-}

module Language.Coformat.Descr.Operations where

import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Control.Monad.Except
import Data.Bifunctor
import Data.Maybe
import Data.String.Interpolate.IsString
import Data.Typeable
import Data.Void
import Text.Read

import Language.Coformat.Descr

filterParsedItems :: [ConfigItemT 'Parsed] -> [ConfigItemT 'Supported]
filterParsedItems = mapMaybe $ \ConfigItem { .. } -> ConfigItem name <$> filterType value
  where
    filterType = \case
                    CTInt () -> Just $ CTInt ()
                    CTUnsigned () -> Just $ CTUnsigned ()
                    CTBool () -> Just $ CTBool ()
                    CTString () -> Nothing
                    CTStringVec () -> Nothing
                    CTRawStringFormats () -> Nothing
                    CTIncludeCats () -> Nothing
                    CTEnum vars () -> Just $ CTEnum vars ()

replaceItemsWith :: [ConfigItemT 'Value] -> [ConfigItemT 'Value] -> [ConfigItemT 'Value]
replaceItemsWith l1 l2 = M.elems $ toMap l2 <> toMap l1
  where
    toMap lst = M.fromList [ (name item, item) | item <- lst ]

subtractMatching :: [ConfigItemT 'Value] -> [ConfigItemT 'Value] -> [ConfigItemT 'Value]
subtractMatching minuend subtrahend = filter f minuend
  where
    f ConfigItem { .. } = (/= Just value) $ HM.lookup name subMap
    subMap = HM.fromList [ (name, value) | ConfigItem { .. } <- subtrahend]

type ParseableConfigState f = (CTData f Void ~ Void, Show (ConfigTypeT f))

parseConfigValue :: (MonadError String m, ParseableConfigState f) => ConfigItemT f -> String -> m (ConfigItemT 'Value)
parseConfigValue cfg str = liftEither $ (\parsed -> ConfigItem { name = name cfg, value = parsed }) <$> eitherParsed
  where
    eitherParsed = case value cfg of
                        CTInt _ -> CTInt <$> readEither'
                        CTUnsigned _ -> CTUnsigned <$> readEither'
                        CTBool _ -> CTBool <$> readEither'
                        CTString val -> absurd val
                        CTStringVec val -> absurd val
                        CTRawStringFormats val -> absurd val
                        CTIncludeCats val -> absurd val
                        CTEnum variants _ | var `elem` variants -> pure $ CTEnum variants var
                                          | otherwise -> throwError [i|Unsupported option `#{var}`, supported ones are `#{T.intercalate "`, `" variants}`|]
                            where var = T.pack str
    readEither' :: forall a. (Typeable a, Read a) => Either String a
    readEither' = first (\err -> [i|Error parsing #{str} as #{typeRep (Proxy :: Proxy a)}: #{err}, expected type: #{value cfg}|]) $ readEither str