    {-# OPTIONS_GHC -fglasgow-exts #-} -- for deriving Typeable
    {-# LANGUAGE PatternGuards #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Text.CSL.Style
    -- Copyright   :  (c) Andrea Rossato
    -- License     :  BSD-style (see LICENSE)
    --
    -- Maintainer  :  Andrea Rossato <andrea.rossato@unitn.it>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- The Style types
    --
    -----------------------------------------------------------------------------

    module Text.CSL.Style where

    import Data.Generics

    -- | The representation of a parsed CSL style.
    data Style
        = Style
          { styleClass  ::  String
          , styleInfo   ::  Maybe CSInfo
          , styleLang   ::  String
          , styleLocale ::  String
          , csTerms     :: [TermMap]
          , csMacros    :: [MacroMap]
          , citation    ::  Citation
          , biblio      ::  Maybe Bibliography
          } deriving ( Show, Typeable, Data )

    type TermMap
        = ((String,Form),(String,String))

    type MacroMap
        = (String,[Element])

    data Citation
        = Citation
          { citOptions :: [Option]
          , citSort    :: [Sort]
          , citLayout  ::  Layout
          } deriving ( Show, Typeable, Data )

    data Bibliography
        = Bibliography
          { bibOptions :: [Option]
          , bibSort    :: [Sort]
          , bibLayout  ::  Layout
          } deriving ( Show, Typeable, Data )

    type Option = (String,String)

    data Layout
        = Layout
          { layFormat ::  Formatting
          , layDelim  ::  Delimiter
          , elements  :: [Element]
          } deriving ( Show, Typeable, Data )

    data Element
        = Choose       IfThen   [IfThen]     [Element]
        | Macro        String     Form        Formatting
        | Const        String                 Formatting
        | PointLocator String     Form        Formatting
        | Variable    [String]    Form        Formatting Delimiter
        | Term         String     Form        Formatting Bool Bool
        | Label        String     Form        Formatting Bool Bool
        | Number       String     NumericForm Formatting
        | ShortNames  [String]                Formatting Delimiter
        | Names       [String]   [Name]       Formatting Delimiter [Element]
        | Substitute  [Element]
        | Group        Formatting Delimiter   String    [Element]
        | Date        [String]    Formatting  Delimiter [DatePart]
          deriving ( Show, Eq, Typeable, Data )

    data IfThen
        = IfThen Condition Match [Element]
          deriving ( Eq, Show, Typeable, Data )

    data Condition
        = Condition
          { isType         :: [String]
          , isSet          :: [String]
          , isNumeric      :: [String]
          , isDate         :: [String]
          , isPosition     :: [String]
          , disambiguation :: [String]
          , isLocator      :: [String]
          }
          deriving ( Eq, Show, Typeable, Data )

    type Delimiter
        = String

    data Match
        = Any
        | All
        | None
          deriving ( Show, Read, Eq, Typeable, Data )

    match :: Match -> [Bool] -> Bool
    match All  = and
    match Any  = or
    match None = and . map not

    data DatePart
        = DatePart String String Formatting
          deriving ( Show, Eq, Typeable, Data )

    defaultDate :: [DatePart]
    defaultDate
        = [ DatePart "year"  "" emptyFormatting
          , DatePart "month" "" emptyFormatting
          , DatePart "day"   "" emptyFormatting]

    data Sort
        = SortVariable String Sorting
        | SortMacro    String Sorting
          deriving ( Eq, Show, Typeable, Data )

    data Sorting
        = Ascending  String
        | Descending String
          deriving ( Eq, Read, Typeable, Data )

    instance Show Sorting where
        show (Ascending  s) = s
        show (Descending s) = s

    instance Ord Sorting where
        compare (Ascending  a) (Ascending  b) = compare a b
        compare (Descending a) (Descending b) = compare b a
        compare             _              _  = EQ

    data Form
        = Long
        | Verb
        | Short
        | VerbShort
        | Symbol
          deriving ( Eq, Show, Read, Typeable, Data )

    data NumericForm
        = Numeric
        | Ordinal
        | Roman
          deriving ( Eq, Show, Read, Typeable, Data )

    data Name
        = Name      Form Formatting NameFormatting Delimiter
        | NameLabel Form Formatting Bool Bool
          deriving ( Eq, Show, Typeable, Data )

    isName :: Name -> Bool
    isName x = case x of Name {} -> True; _ -> False

    data NameFormatting
        = NameFormatting
          { andConnector          :: String
          , delimiterPrecedesLast :: String
          , nameAsSortOrder       :: String
          , sortSeparator         :: String
          , initializeWith        :: String
          } deriving ( Eq, Show, Typeable, Data )

    defaultNameFormatting :: NameFormatting
    defaultNameFormatting
        = NameFormatting
          { andConnector          = "text"
          , delimiterPrecedesLast = ""
          , nameAsSortOrder       = ""
          , sortSeparator         = ""
          , initializeWith        = ""
          }

    data Formatting
        = Formatting
          { prefix         :: String
          , suffix         :: String
          , fontFamily     :: String
          , fontStyle      :: String
          , fontVariant    :: String
          , fontWeight     :: String
          , textDecoration :: String
          , verticalAlign  :: String
          , textCase       :: String
          , display        :: String
          , quotes         :: Bool
          } deriving ( Eq, Ord, Read, Show, Typeable, Data )

    emptyFormatting :: Formatting
    emptyFormatting
        = Formatting [] [] [] [] [] [] [] [] [] [] False

    data CSInfo
        = CSInfo
          { csiTitle      :: String
          , csiAuthor     :: CSAuthor
          , csiCategories :: [CSCategory]
          , csiId         :: String
          , csiUpdated    :: String
          } deriving ( Show, Read, Typeable, Data )

    data CSAuthor   = CSAuthor   String String String deriving ( Show, Read, Eq, Typeable, Data )
    data CSCategory = CSCategory String String String deriving ( Show, Read, Eq, Typeable, Data )

    -- | The formatted output, produced after post-processing the
    -- evaluated citations.
    data FormattedOutput
        = FO String Formatting [FormattedOutput]
        | Delimiter String
          deriving ( Eq, Show )

    -- | The 'Output' generated by the evaluation of a style. Must be
    -- further processed for disambiguation and collapsing.
    data Output
        = OStr     String             Formatting -- ^ A simple 'String'
        | OName    String  [String]   Formatting -- ^ A (family) name with the list of given names.
        | OYear    String   String    Formatting -- ^ The year and a suffix if needed
        | OCitNum  Int                Formatting -- ^ The citation number
        | Output  [Output]            Formatting -- ^ Some nested 'Output'
        | OContrib String  [Output]   [[Output]] -- ^ The citation key with the contributor(s), and everything
                                                 -- used for disambiguation.
        | ODel     String                        -- ^ A delimiter string.
          deriving ( Eq, Ord, Show, Typeable, Data )

    -- | A citation group: a list of evaluated citations, the 'Formatting'
    -- to be applied to them, and the 'Delimiter' between individual
    -- citations.
    data CitationGroup = CG Formatting String [Output] deriving ( Show, Eq, Typeable, Data )

    data BiblioData
        = BD
          { citations    :: [[FormattedOutput]]
          , bibliography :: [[FormattedOutput]]
          } deriving ( Show )

    -- | A record with all the data to produce the 'FormattedOutput' of a
    -- citation: the citation key, the part of the citation that may be
    -- colliding with other citations (the list of contributors for the
    -- same year), the data to disambiguate it (all possible contributors
    -- and all possible given names), and the disambiguated citation and
    -- its year.
    data CiteData
        = CD
          { key        ::   String
          , collision  ::  [Output]
          , disambData :: [[Output]]
          , disambed   ::  [Output]
          , citYear    ::   String
          } deriving ( Show, Typeable, Data )

    instance Eq CiteData where
        (==) (CD ka ca _ _ _)
             (CD kb cb _ _ _) = ka == kb && ca == cb

    -- | Convert evaluated 'Output' into 'FormattedOutput', ready for the
    -- output filters.
    formatOutput :: Output -> FormattedOutput
    formatOutput o
        | ODel     s     <- o = Delimiter s
        | OStr     s   f <- o = FO s  f []
        | OName    s _ f <- o = FO s  f []
        | OYear    s _ f <- o = FO s  f []
        | Output   os  f <- o = FO "" f               (format os)
        | OContrib _ s _ <- o = FO "" emptyFormatting (format  s)
        | OCitNum  i   f <- o = FO (show i) f []
        | otherwise     = FO "" emptyFormatting []
        where
          format  = map formatOutput

    -- | Map the evaluated output of a citation group.
    mapGroupOutput :: (Output -> [a]) -> CitationGroup -> [a]
    mapGroupOutput f (CG _ _ os) = concatMap f os

    -- | A generic processing function.
    proc :: (Typeable a, Data b) => (a -> a) -> b -> b
    proc f = everywhere (mkT f)

    -- | A generic query function.
    query :: (Typeable a, Data b) => (a -> [c]) -> b -> [c]
    query f = everything (++) ([] `mkQ` f)

    -- | Removes all given names form a 'OName' element with 'proc'.
    rmGivenNames :: Output -> Output
    rmGivenNames o
        | OName s _ f <- o = OName s [] f
        | otherwise     = o

    -- | Removes all contributors' names.
    rmContribs :: Output -> Output
    rmContribs o
        | OContrib s _ _ <- o = OContrib s [] []
        | otherwise           = o

    -- | Add, with 'proc', a give name to the family name. Needed for
    -- disambiguation.
    addGivenNames :: [Output] -> [Output]
    addGivenNames
        = reverse . addGN True . reverse
        where
          addGN _ [] = []
          addGN b (o:os)
              | OName _ xs f <- o
              , xs /= []  = if b then OName (head xs) (tail xs) f : addGN False os else o:os
              | otherwise = o : addGN b os

    -- | Add the year suffix to the year. Needed for disambiguation.
    addYearSuffix :: Output -> Output
    addYearSuffix o
        | OYear y s f <- o = OYear (y ++ s) s f
        | otherwise        = o