module Distribution.FieldGrammar.Class (
    FieldGrammar (..),
    uniqueField,
    optionalField,
    optionalFieldDef,
    monoidalField,
    defaultFreeTextFieldDefST,
    ) where

import Distribution.Compat.Lens
import Distribution.Compat.Prelude
import Prelude ()

import Distribution.CabalSpecVersion (CabalSpecVersion)
import Distribution.Compat.Newtype   (Newtype)
import Distribution.Fields.Field
import Distribution.Parsec           (Parsec)
import Distribution.Pretty           (Pretty)
import Distribution.Utils.ShortText

-- | 'FieldGrammar' is parametrised by
--
-- * @s@ which is a structure we are parsing. We need this to provide prettyprinter
-- functionality
--
-- * @a@ type of the field.
--
-- /Note:/ We'd like to have @forall s. Applicative (f s)@ context.
--
class FieldGrammar g where
    -- | Unfocus, zoom out, /blur/ 'FieldGrammar'.
    blurFieldGrammar :: ALens' a b -> g b c -> g a c

    -- | Field which should be defined, exactly once.
    uniqueFieldAla
        :: (Parsec b, Pretty b, Newtype a b)
        => FieldName   -- ^ field name
        -> (a -> b)    -- ^ 'Newtype' pack
        -> ALens' s a  -- ^ lens into the field
        -> g s a

    -- | Boolean field with a default value.
    booleanFieldDef
        :: FieldName     -- ^ field name
        -> ALens' s Bool -- ^ lens into the field
        -> Bool          -- ^ default
        -> g s Bool

    -- | Optional field.
    optionalFieldAla
        :: (Parsec b, Pretty b, Newtype a b)
        => FieldName          -- ^ field name
        -> (a -> b)           -- ^ 'pack'
        -> ALens' s (Maybe a) -- ^ lens into the field
        -> g s (Maybe a)

    -- | Optional field with default value.
    optionalFieldDefAla
        :: (Parsec b, Pretty b, Newtype a b, Eq a)
        => FieldName   -- ^ field name
        -> (a -> b)    -- ^ 'Newtype' pack
        -> ALens' s a  -- ^ @'Lens'' s a@: lens into the field
        -> a           -- ^ default value
        -> g s a

    --  | Free text field is essentially 'optionalFieldDefAla` with @""@
    --  as the default and "accept everything" parser.
    --
    -- @since 3.0.0.0
    freeTextField
        :: FieldName
        -> ALens' s (Maybe String) -- ^ lens into the field
        -> g s (Maybe String)

    --  | Free text field is essentially 'optionalFieldDefAla` with @""@
    --  as the default and "accept everything" parser.
    --
    -- @since 3.0.0.0
    freeTextFieldDef
        :: FieldName
        -> ALens' s String -- ^ lens into the field
        -> g s String

    -- | @since 3.2.0.0
    freeTextFieldDefST
        :: FieldName
        -> ALens' s ShortText -- ^ lens into the field
        -> g s ShortText

    -- | Monoidal field.
    --
    -- Values are combined with 'mappend'.
    --
    -- /Note:/ 'optionalFieldAla' is a @monoidalField@ with 'Last' monoid.
    --
    monoidalFieldAla
        :: (Parsec b, Pretty b, Monoid a, Newtype a b)
        => FieldName   -- ^ field name
        -> (a -> b)    -- ^ 'pack'
        -> ALens' s a  -- ^ lens into the field
        -> g s a

    -- | Parser matching all fields with a name starting with a prefix.
    prefixedFields
        :: FieldName                    -- ^ field name prefix
        -> ALens' s [(String, String)]  -- ^ lens into the field
        -> g s [(String, String)]

    -- | Known field, which we don't parse, neither pretty print.
    knownField :: FieldName -> g s ()

    -- | Field which is parsed but not pretty printed.
    hiddenField :: g s a -> g s a

    -- | Deprecated since
    deprecatedSince
        :: CabalSpecVersion   -- ^ version
        -> String             -- ^ deprecation message
        -> g s a
        -> g s a

    -- | Removed in. If we occur removed field, parsing fails.
    removedIn
        :: CabalSpecVersion   -- ^ version
        -> String             -- ^ removal message
        -> g s a
        -> g s a

    -- | Annotate field with since spec-version.
    availableSince
        :: CabalSpecVersion  -- ^ spec version
        -> a                 -- ^ default value
        -> g s a
        -> g s a

-- | Field which can be defined at most once.
uniqueField
    :: (FieldGrammar g, Parsec a, Pretty a)
    => FieldName   -- ^ field name
    -> ALens' s a  -- ^ lens into the field
    -> g s a
uniqueField :: FieldName -> ALens' s a -> g s a
uniqueField FieldName
fn = FieldName -> (a -> Identity a) -> ALens' s a -> g s a
forall (g :: * -> * -> *) b a s.
(FieldGrammar g, Parsec b, Pretty b, Newtype a b) =>
FieldName -> (a -> b) -> ALens' s a -> g s a
uniqueFieldAla FieldName
fn a -> Identity a
forall a. a -> Identity a
Identity

-- | Field which can be defined at most once.
optionalField
    :: (FieldGrammar g, Parsec a, Pretty a)
    => FieldName          -- ^ field name
    -> ALens' s (Maybe a) -- ^ lens into the field
    -> g s (Maybe a)
optionalField :: FieldName -> ALens' s (Maybe a) -> g s (Maybe a)
optionalField FieldName
fn = FieldName
-> (a -> Identity a) -> ALens' s (Maybe a) -> g s (Maybe a)
forall (g :: * -> * -> *) b a s.
(FieldGrammar g, Parsec b, Pretty b, Newtype a b) =>
FieldName -> (a -> b) -> ALens' s (Maybe a) -> g s (Maybe a)
optionalFieldAla FieldName
fn a -> Identity a
forall a. a -> Identity a
Identity

-- | Optional field with default value.
optionalFieldDef
    :: (FieldGrammar g, Functor (g s), Parsec a, Pretty a, Eq a)
    => FieldName   -- ^ field name
    -> ALens' s a  -- ^ @'Lens'' s a@: lens into the field
    -> a           -- ^ default value
    -> g s a
optionalFieldDef :: FieldName -> ALens' s a -> a -> g s a
optionalFieldDef FieldName
fn = FieldName -> (a -> Identity a) -> ALens' s a -> a -> g s a
forall (g :: * -> * -> *) b a s.
(FieldGrammar g, Parsec b, Pretty b, Newtype a b, Eq a) =>
FieldName -> (a -> b) -> ALens' s a -> a -> g s a
optionalFieldDefAla FieldName
fn a -> Identity a
forall a. a -> Identity a
Identity

-- | Field which can be define multiple times, and the results are @mappend@ed.
monoidalField
    :: (FieldGrammar g, Parsec a, Pretty a, Monoid a)
    => FieldName   -- ^ field name
    -> ALens' s a  -- ^ lens into the field
    -> g s a
monoidalField :: FieldName -> ALens' s a -> g s a
monoidalField FieldName
fn = FieldName -> (a -> Identity a) -> ALens' s a -> g s a
forall (g :: * -> * -> *) b a s.
(FieldGrammar g, Parsec b, Pretty b, Monoid a, Newtype a b) =>
FieldName -> (a -> b) -> ALens' s a -> g s a
monoidalFieldAla FieldName
fn a -> Identity a
forall a. a -> Identity a
Identity

-- | Default implementation for 'freeTextFieldDefST'.
defaultFreeTextFieldDefST
    :: (Functor (g s), FieldGrammar g)
    => FieldName
    -> ALens' s ShortText -- ^ lens into the field
    -> g s ShortText
defaultFreeTextFieldDefST :: FieldName -> ALens' s ShortText -> g s ShortText
defaultFreeTextFieldDefST FieldName
fn ALens' s ShortText
l =
    String -> ShortText
toShortText (String -> ShortText) -> g s String -> g s ShortText
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FieldName -> ALens' s String -> g s String
forall (g :: * -> * -> *) s.
FieldGrammar g =>
FieldName -> ALens' s String -> g s String
freeTextFieldDef FieldName
fn (ALens' s ShortText
-> LensLike (Pretext String String) s s ShortText ShortText
forall (f :: * -> *) s t a b.
Functor f =>
ALens s t a b -> LensLike f s t a b
cloneLens ALens' s ShortText
l LensLike (Pretext String String) s s ShortText ShortText
-> ((String -> Pretext String String String)
    -> ShortText -> Pretext String String ShortText)
-> ALens' s String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Pretext String String String)
-> ShortText -> Pretext String String ShortText
Lens' ShortText String
st)
  where
    st :: Lens' ShortText String
    st :: LensLike f ShortText ShortText String String
st String -> f String
f ShortText
s = String -> ShortText
toShortText (String -> ShortText) -> f String -> f ShortText
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> f String
f (ShortText -> String
fromShortText ShortText
s)