-- |
-- License: GPL-3.0-or-later
-- Copyright: Oleg Grenrus
{-# LANGUAGE DeriveFunctor             #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes                #-}
module CabalFmt.Fields (
    FieldDescrs,
    fieldDescrLookup,
    coerceFieldDescrs,
    singletonF,
    ) where

import qualified Data.Map.Strict                 as Map
import qualified Distribution.Compat.CharParsing as C
import qualified Distribution.FieldGrammar       as C
import qualified Distribution.Fields.Field       as C
import qualified Distribution.Parsec             as C
import qualified Distribution.Pretty             as C
import qualified Text.PrettyPrint                as PP

import CabalFmt.Prelude

-------------------------------------------------------------------------------
-- FieldDescr variant
-------------------------------------------------------------------------------

-- strict pair
data SP = forall f. SP
    { _pPretty :: !(f -> PP.Doc)
    , _pParse  :: !(forall m. C.CabalParsing m => m f)
    }

-- | Lookup both pretty-printer and value parser.
--
-- As the value of the field is unknown, we have to work with it universally.
--
fieldDescrLookup
    :: C.CabalParsing m
    => FieldDescrs s a
    -> C.FieldName
    -> (forall f. m f -> (f -> PP.Doc) -> r)
    -> Maybe r
fieldDescrLookup (F m) fn kont = kont' <$> Map.lookup fn m where
    kont' (SP a b) = kont b a

-- | A collection field parsers and pretty-printers.
newtype FieldDescrs s a = F { runF :: Map.Map C.FieldName SP }
  deriving (Functor)

coerceFieldDescrs :: FieldDescrs s a -> FieldDescrs () ()
coerceFieldDescrs (F a) = F a

instance Semigroup (FieldDescrs s a) where
    F a <> F b = F (a <> b)

instance Monoid (FieldDescrs s a) where
    mempty  = F Map.empty
    mappend = (<>)

instance Applicative (FieldDescrs s) where
    pure _  = F mempty
    f <*> x = F (mappend (runF f) (runF x))

singletonF
    :: C.FieldName
    -> (f -> PP.Doc)
    -> (forall m. C.CabalParsing m => m f)
    -> FieldDescrs s a
singletonF fn f g = F $ Map.singleton fn (SP f g)

instance C.FieldGrammar FieldDescrs where
    blurFieldGrammar _ (F m) = F m

    booleanFieldDef fn _ _def = singletonF fn f C.parsec where
        f :: Bool -> PP.Doc
        f s = PP.text (show s)

    uniqueFieldAla fn _pack _ =
        singletonF fn (C.pretty . pack' _pack) (unpack' _pack <$> C.parsec)

    optionalFieldAla fn _pack _ =
        singletonF fn (C.pretty . pack' _pack) (unpack' _pack <$> C.parsec)

    optionalFieldDefAla fn _pack _ def =
        singletonF fn f (unpack' _pack <$> C.parsec)
      where
        f s | s == def  = PP.empty
            | otherwise = C.pretty (pack' _pack s)

    monoidalFieldAla fn _pack _ =
        singletonF fn (C.pretty . pack' _pack) (unpack' _pack <$> C.parsec)

    freeTextField fn _ = singletonF fn
        PP.text
        (C.munch $ const True)

    freeTextFieldDef fn _ = singletonF fn
        PP.text
        (C.munch $ const True)

    freeTextFieldDefST fn _ = singletonF fn
        PP.text
        (C.munch $ const True)

    prefixedFields _fnPfx _l = F mempty
    knownField _           = pure ()
    deprecatedSince _  _ x = x
    removedIn _ _ x        = x
    availableSince _ _     = id
    hiddenField _          = F mempty