{-# LANGUAGE CPP #-}
module Text.XML.HaXml.DtdToHaskell.TypeDef
  ( 
    TypeDef(..)
  , Constructors
  , AttrFields
  , StructType(..)
  
  , ppTypeDef
  , ppHName
  , ppXName
  , ppAName
  
  , Name(..)
  , name, name_, name_a, name_ac, name_f, mangle, manglef
  ) where
#if MIN_VERSION_base(4,11,0)
import Prelude hiding ((<>))
#endif
import Data.Char (isLower, isUpper, toLower, toUpper, isDigit)
import Data.List (intersperse)
import Text.PrettyPrint.HughesPJ
data Name = Name { xName :: String       
                 , hName :: String       
                 }
          deriving Eq
data TypeDef =
      DataDef Bool Name AttrFields Constructors 
    | EnumDef Name [Name]
    deriving Eq
type Constructors = [(Name,[StructType])]
type AttrFields   = [(Name, StructType)]
data StructType =
      Maybe StructType
    | Defaultable StructType String     
    | List StructType
    | List1 StructType                  
    | Tuple [StructType]
    | OneOf [StructType]
    | Any                               
    | StringMixed                       
    | String                            
    | Defined Name
    deriving Eq
instance Show StructType where
    showsPrec p (Maybe s)         = showsPrec (p+1) s . showChar '?'
    showsPrec _ (Defaultable s _) = shows s
    showsPrec p (List s)          = showsPrec (p+1) s . showChar '*'
    showsPrec p (List1 s)         = showsPrec (p+1) s . showChar '+'
    showsPrec _ (Tuple ss)        = showChar '('
                                    . foldr1 (.) (intersperse (showChar ',')
                                                              (map shows ss))
                                    . showChar ')'
    showsPrec _ (OneOf ss)        = showChar '('
                                    . foldr1 (.) (intersperse (showChar '|')
                                                              (map shows ss))
                                    . showChar ')'
    showsPrec _ (Any)             = showString "ANY"
    showsPrec _ (StringMixed)     = showString "#PCDATA"
    showsPrec _ (String)          = showString "#PCDATA"
    showsPrec _ (Defined (Name n _)) = showString n
ppTypeDef :: TypeDef -> Doc
ppTypeDef (DataDef _ n [] []) =
    let nme = ppHName n in
    text "data" <+> nme <+> text "=" <+> nme <+> text "\t\t" <> derives
ppTypeDef (DataDef _ n [] [c@(_,[_])]) =
    text "newtype" <+> ppHName n <+> text "=" <+> ppC c <+> text "\t\t" <> derives
ppTypeDef (DataDef _ n [] cs) =
    text "data" <+> ppHName n <+>
           ( text "=" <+> ppC (head cs) $$
             vcat (map (\c-> text "|" <+> ppC c) (tail cs)) $$
             derives )
ppTypeDef (DataDef _ n fs []) =
    let nme = ppHName n in
    text "data" <+> nme <+> text "=" <+> nme $$
    nest 4 ( text "{" <+> ppF (head fs) $$
             vcat (map (\f-> text "," <+> ppF f) (tail fs)) $$
             text "}" <+> derives )
ppTypeDef (DataDef _ n fs cs) =
    let attr = ppAName n in
    text "data" <+> ppHName n <+>
           ( text "=" <+> ppAC attr (head cs) $$
             vcat (map (\c-> text "|" <+> ppAC attr c) (tail cs)) $$
             derives )  $$
    text "data" <+> attr <+> text "=" <+> attr $$
    nest 4 ( text "{" <+> ppF (head fs) $$
             vcat (map (\f-> text "," <+> ppF f) (tail fs)) $$
             text "}" <+> derives )
ppTypeDef (EnumDef n es) =
    text "data" <+> ppHName n <+>
    ( text "=" <+>
      fsep (intersperse (text " | ") (map ppHName es))
    $$ derives )
ppST :: StructType -> Doc
ppST (Defaultable st _)  = parens (text "Defaultable" <+> ppST st)
ppST (Maybe st)  = parens (text "Maybe" <+> ppST st)
ppST (List st)   = text "[" <> ppST st <> text "]"
ppST (List1 st)  = parens (text "List1" <+> ppST st)
ppST (Tuple sts) = parens (commaList (map ppST sts))
ppST (OneOf sts) = parens (text "OneOf" <> text (show (length sts)) <+>
                           hsep (map ppST sts))
ppST  StringMixed= text "String"
ppST  String     = text "String"
ppST  Any        = text "ANYContent"
ppST (Defined n) = ppHName n
ppC :: (Name,[StructType]) -> Doc
ppC (n,sts) = ppHName n <+> fsep (map ppST sts)
ppF :: (Name,StructType) -> Doc
ppF (n,st) = ppHName n <+> text "::" <+> ppST st
ppAC :: Doc -> (Name,[StructType]) -> Doc
ppAC atype (n,sts) = ppHName n <+> fsep (atype: map ppST sts)
ppHName :: Name -> Doc
ppHName (Name _ s) = text s
ppXName :: Name -> Doc
ppXName (Name s _) = text s
ppAName :: Name -> Doc
ppAName (Name _ s) = text s <> text "_Attrs"
derives :: Doc
derives = text "deriving" <+> parens (commaList (map text ["Eq","Show"]))
name :: String -> Name
name n     = Name { xName = n
                  , hName = mangle n }
name_ :: String -> Name
name_ n    = Name { xName = n
                  , hName = mangle n ++ "_" }
name_a :: String -> String -> Name
name_a e n = Name { xName = n
                  , hName = mangle e ++ "_" ++ map decolonify n }
name_ac :: String -> String -> String -> Name
name_ac e t n = Name { xName = n
                     , hName = mangle e ++ "_" ++ map decolonify t
                                        ++ "_" ++ map decolonify n }
name_f :: String -> String -> Name
name_f e n = Name { xName = n
                  , hName = manglef e ++ mangle n }
mangle :: String -> String
mangle (n:ns)
    | isLower n   = notPrelude (toUpper n: map decolonify ns)
    | isDigit n   = 'I': n: map decolonify ns
    | otherwise   = notPrelude (n: map decolonify ns)
notPrelude :: String -> String
notPrelude "Bool"    = "ABool"
notPrelude "Bounded" = "ABounded"
notPrelude "Char"    = "AChar"
notPrelude "Double"  = "ADouble"
notPrelude "Either"  = "AEither"
notPrelude "Enum"    = "AEnum"
notPrelude "Eq"      = "AEq"
notPrelude "FilePath"= "AFilePath"
notPrelude "Float"   = "AFloat"
notPrelude "Floating"= "AFloating"
notPrelude "Fractional"= "AFractional"
notPrelude "Functor" = "AFunctor"
notPrelude "IO"      = "AIO"
notPrelude "IOError" = "AIOError"
notPrelude "Int"     = "AInt"
notPrelude "Integer" = "AInteger"
notPrelude "Integral"= "AIntegral"
notPrelude "List1"   = "AList1" 
notPrelude "Maybe"   = "AMaybe"
notPrelude "Monad"   = "AMonad"
notPrelude "Num"     = "ANum"
notPrelude "Ord"     = "AOrd"
notPrelude "Ordering"= "AOrdering"
notPrelude "Rational"= "ARational"
notPrelude "Read"    = "ARead"
notPrelude "ReadS"   = "AReadS"
notPrelude "Real"    = "AReal"
notPrelude "RealFloat" = "ARealFloat"
notPrelude "RealFrac"= "ARealFrac"
notPrelude "Show"    = "AShow"
notPrelude "ShowS"   = "AShowS"
notPrelude "String"  = "AString"
notPrelude n         = n
manglef :: String -> String
manglef (n:ns)
    | isUpper n   = toLower n: map decolonify ns
    | isDigit n   = '_': n: map decolonify ns
    | otherwise   = n: map decolonify ns
decolonify :: Char -> Char
decolonify ':' = '\''   
decolonify '-' = '_'
decolonify '.' = '_'
decolonify  c  = c
commaList :: [Doc] -> Doc
commaList = hcat . intersperse comma