{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Consume a 'Schema' and emit types intended for code generation.
module Fadno.Xml.EmitTypes
    (
     -- * XSD Schema to types
     emitSchema
     -- * Emit various productions
    ,emitElement,emitSimpleType,emitComplexType,emitCompositor
    ,emitGroup,emitChoice,emitSequence,emitParticle,emitAttrFields
     -- * Emit monad
    ,Emit,Env(..),schema,EmitState(..),types,stack,runEmit,die
    -- * Types
    ,Name(..),Namespace(..),Cardinality(..)
    ,Field(..),fieldName,fieldType,fieldCardinality,fieldXmlEmit,fieldIdx,FieldEmit(..)
    ,Ctor(..),ctorName,ctorFields
    ,Type(..),typeName,typeType,typeDerives,typeImpls,typeEmit,typeCtors,typeEnumValues,coreType
    ,DerivesFamily(..),DataTypeEmit(..),CoreType(..),Impl(..)
    ) where

import Fadno.Xml.ParseXsd
import Data.Monoid
import Control.Lens hiding (Choice,element,elements,anon)

import Control.Monad.State.Strict
import Control.Monad.Reader
import Data.Maybe
import Data.Data
import Control.Exception hiding (handle)
import qualified Data.Map.Strict as M


-- | Field cardinality
data Cardinality =
    One |
    ZeroOrOne |
    Many
    deriving (Eq,Show,Data,Typeable,Ord,Enum,Bounded)

-- | Namespaces for various productions.
data Namespace =
    NSElement |
    NSSimple |
    NSUnion |
    NSComplex |
    NSBuiltIn |
    NSChoice |
    NSSequence |
    NSGroup
    deriving (Eq,Show,Data,Typeable,Ord,Enum,Bounded)

-- | Type name.
data Name = Name {
      nNamespace :: Namespace
    , nName :: QN
    , nIdx :: Int }
            deriving (Eq,Show,Data,Typeable,Ord)

-- | Field xml emit cue.
data FieldEmit =
    FieldAttribute |
    FieldElement |
    FieldText |
    FieldOther
    deriving (Eq,Show,Data,Typeable,Ord)

-- | Type field.
data Field = Field {
      _fieldName :: QN
    , _fieldType :: Type
    , _fieldCardinality :: Cardinality
    , _fieldXmlEmit :: FieldEmit
    , _fieldIdx :: Int
    } deriving (Eq,Data,Typeable,Ord)

instance Show Field where
    show (Field n t c x i) =
        "Field {_fieldName = " ++ show n ++
                    ",_fieldType = {_typeName = " ++ show (_typeName t) ++
                    "},_fieldCardinality = " ++ show c ++
                    ",_fieldXmlEmit = " ++ show x ++
                    ",_fieldIdx = " ++ show i ++
                    "}"

-- | Datatype constructor.
data Ctor = Ctor {
      _ctorName :: String
    , _ctorFields :: [Field]
    } deriving (Eq,Show,Data,Typeable,Ord)

-- | Derive type "families".
data DerivesFamily =
    NewTypeString |
    NewTypeIntegral |
    NewTypeNum |
    DataEnum |
    OtherDerives
    deriving (Eq,Show,Data,Typeable,Ord,Enum,Bounded)

-- | "Impl"s of classes, but also grab-bag of type deets.
data Impl =
    -- | Specifies min and max values
    Bounds (Maybe (Bound String), Maybe (Bound String)) |
    -- | Specifies string pattern
    Pattern String |
    -- | Implement 'Show' for newtype
    NewTypeShow |
    -- | Top-level element, so emit element name as well as contents.
    -- Elements normally inherit the name from the referencing production.
    TopLevel
            deriving (Eq,Show,Data,Typeable,Ord)

-- | newtype base types. Name after CT should be Haskell base type.
data CoreType =
    CTString |
    CTDecimal |
    CTFloat |
    CTDouble |
    CTInt |
    CTBool
    deriving (Eq,Show,Data,Typeable,Ord,Enum,Bounded)

-- | Data type xml emit cue.
data DataTypeEmit =
    DataTypeComplex |
    DataTypeSimple |
    DataTypeCompositor
    deriving (Eq,Show,Data,Typeable,Ord)

-- | Emitted type.
data Type =
    NewType {
      _typeName :: Name
    , _typeType :: Type
    , _typeDerives :: DerivesFamily
    , _typeImpls :: [Impl]
    , _typeDoc :: Maybe Documentation
    } |
    DataType {
      _typeName :: Name
    , _typeCtors :: [Ctor]
    , _typeDerives :: DerivesFamily
    , _typeImpls :: [Impl]
    , _typeEmit :: DataTypeEmit
    , _typeDoc :: Maybe Documentation
    } |
    EnumType {
      _typeName :: Name
    , _typeEnumValues :: [String]
    , _typeDerives :: DerivesFamily
    , _typeImpls :: [Impl]
    , _typeDoc :: Maybe Documentation
    } |
    BuiltIn {
      _typeName :: Name
    , _coreType :: CoreType
    , _typeDerives :: DerivesFamily
    , _typeImpls :: [Impl]
    } deriving (Eq,Show,Data,Typeable,Ord)

$(makeLenses ''Type)
$(makeLenses ''Ctor)
$(makeLenses ''Field)


-- | Reader environment.
data Env = Env {
      _schema :: Schema
}
$(makeLenses ''Env)

-- | State data.
data EmitState = EmitState { _types :: M.Map Name Type, _stack :: [Name] }
$(makeLenses ''EmitState)
instance Semigroup EmitState where
  (EmitState a b) <> (EmitState c d) = EmitState (a<>c) (b<>d)
instance Monoid EmitState where
    mempty = EmitState mempty mempty


-- | Emit monad.
type Emit a = ReaderT Env (StateT EmitState IO) a

-- | Run emit monad.
runEmit :: Env -> EmitState -> Emit a -> IO (a, EmitState)
runEmit env st act = runStateT (runReaderT act env) st

-- | Emit schema types. Starts with element productions
-- and emits all dependent types.
emitSchema :: Schema -> Emit ()
emitSchema s = do
  els <- M.keys <$> mapM emitElement (_elements s)
  types %= M.mapWithKey
            (\k v -> if nName k `elem` els
                     then over typeImpls (TopLevel:) v
                     else v)

-- | String builtin.
builtInString :: Type
builtInString = mkBuiltIn "string" CTString NewTypeString

-- | All built-in types.
builtIns :: M.Map QN Type
builtIns = foldr (\b -> M.insert (nName (_typeName b)) b) mempty
           [builtInInteger,builtInDecimal,builtInDouble,
            builtInFloat,builtInBoolean,builtInString]
    where
      builtInInteger = mkBuiltIn "integer" CTInt NewTypeIntegral
      builtInDecimal = mkBuiltIn "decimal" CTDecimal NewTypeNum
      builtInFloat = mkBuiltIn "float" CTFloat NewTypeNum
      builtInDouble = mkBuiltIn "double" CTDouble NewTypeNum
      builtInBoolean = mkBuiltIn "boolean" CTBool OtherDerives

-- | Smart ctor for built-in types.
mkBuiltIn :: String -> CoreType -> DerivesFamily -> Type
mkBuiltIn n ct df = BuiltIn (Name NSBuiltIn (QN n (Just "xs")) 0) ct df [NewTypeShow]


-- | Emit type for element content; element name production
-- captured in containing field.
emitElement :: Element -> Emit Type
emitElement (ElementType _ t _ doc) = do
  rt <- resolvedRef t
  case rt of
    Left ct -> emitComplexType Nothing ct
    Right st -> emitSimpleType st
emitElement (ElementComplex n c _ doc) = emitComplexType (Just n) c
emitElement (ElementRef {}) = die "ElementRef unsupported"
emitElement (ElementSimple {}) = die "ElementSimple unsupported"

-- | Error out in IO.
die :: MonadIO m => String -> m a
die = liftIO . throwIO . userError


-- | Resolve a ref and obtain the resolved value.
resolvedRef :: (Resolvable (Ref a)) => Ref a -> Emit a
resolvedRef r = do
  s <- view schema
  case firstOf refvalue (resolve s r) of
    Just a -> return a
    Nothing -> die $ "resolvedRef: resolve failed on " ++ show r


-- | Handle simpleType production.
emitSimpleType :: SimpleType -> Emit Type
emitSimpleType t =
    case view simpleTypeName t of
      Nothing -> use stack >>= \s -> die $ "emitSimpleType: anon type: " ++ show t ++
                 ", stack: " ++ show s
      Just stn -> do
          bt <- tryBuiltIn t
          maybe (checkDefinedType NSSimple stn $ doSimpleType t) return bt

doSimpleType :: SimpleType -> Name -> Emit Type
doSimpleType (SimpleTypeRestrict _ (SimpleRestriction base enumz mins maxs patt) doc) n = do
  bt <- resolvedRef base
  if not (null enumz)
  then emitEnum bt n enumz doc
  else do
    btt <- emitSimpleType bt
    return $ NewType n btt (_typeDerives btt)
           ([Bounds (mins,maxs) | isJust mins || isJust maxs] ++
            maybe [] (return.Pattern) patt ++
            _typeImpls btt) doc
doSimpleType (SimpleTypeUnion _ (Union refs sts) doc) n = do
  rts <- mapM resolvedRef refs
  rtts <- mapM emitSimpleType rts
  let doAnon t = checkUniqueType NSUnion Nothing (doSimpleType t)
  atns <- mapM doAnon sts
  let doCtor i t =
          Ctor (_qLocal $ nName (_typeName t))
                   [Field (QN (show i) Nothing) t One FieldAttribute 0]
  return $ fixFields $
         DataType n (zipWith doCtor [(1 :: Int) ..] $ rtts ++ atns)
                  OtherDerives [] DataTypeSimple doc

-- | Check if type is built-in.
tryBuiltIn :: SimpleType -> Emit (Maybe Type)
tryBuiltIn t =
    case view simpleTypeName t of
      Nothing -> return Nothing
      Just tn ->
          case M.lookup tn builtIns of
            Just bt -> return (Just bt)
            Nothing ->
                case firstOf (simpleTypeRestriction.simpleRestrictBase.unresolved) t of
                  Nothing -> return Nothing
                  Just bt | bt == anySimpleTypeName -> return (Just builtInString)
                          | otherwise -> return Nothing




emitEnum :: SimpleType -> Name -> [String] -> Maybe Documentation -> Emit Type
emitEnum _base n vals doc = return $ EnumType n vals DataEnum [] doc


-- | Complex type. 'anon' arg indicates element-defined complex type, therefore unique;
-- otherwise defined type.
emitComplexType :: Maybe QN -> ComplexType -> Emit Type
emitComplexType anon@(Just _) t =
    checkUniqueType NSComplex anon $ doComplexType t
emitComplexType Nothing t = do
  n <- maybe (die $ "emitComplexType: no complex name: " ++ show t) return $
       view complexTypeName t
  checkDefinedType NSComplex n $ doComplexType t


doComplexType :: ComplexType -> Name -> Emit Type
doComplexType (ComplexTypeSimple _ (SimpleContentExtension scb atts) doc) mn = do
  rt <- resolvedRef scb >>= emitSimpleType
  ats <- emitAttrFields atts
  return $ fixFields $ DataType mn
             [Ctor "" (Field (getRefType scb) rt One FieldText 0:ats)]
             OtherDerives [] DataTypeComplex doc
doComplexType (ComplexTypeCompositor _ comp atts doc) mn = do
  ats <- emitAttrFields atts
  c <- maybe (return []) (emitCompositor [nName mn]) comp
  return $ fixFields $
         DataType mn [Ctor "" (ats ++ c)] OtherDerives [] DataTypeComplex doc
doComplexType (ComplexTypeComplex _ (ComplexContentExtension ccb atts comp) doc) mn = do
  ct <- resolvedRef ccb >>= emitComplexType (Just $ nName mn)
  ats <- emitAttrFields atts
  c <- maybe (return []) (emitCompositor [nName mn]) comp
  -- TODO using FieldOther for base content, should be ok?
  return $ fixFields $ DataType mn
             [Ctor "" (Field (getRefType ccb) ct One FieldOther 0:(ats ++ c))]
             OtherDerives [] DataTypeComplex doc

-- | Obtain string name of 'Ref'.
getRefType :: Ref t -> QN
getRefType (Unresolved n) = n
getRefType (Resolved n _) = n
getRefType Final = error "Attempt to resolve ref on final"

-- | Emit compositor field.
emitCompositor :: [QN] -> Compositor -> Emit [Field]
emitCompositor ns (CompositorGroup g) = emitGroup ns g
emitCompositor ns (CompositorChoice c) = emitChoice ns c Nothing
emitCompositor ns (CompositorSequence s) = emitSequence ns Nothing s Nothing

-- | Build up name stack, used in compositor/particle field emittance.
appendNames :: [QN] -> Maybe QN -> [QN]
appendNames ss = maybe ss (:ss)

-- | Emit group. Referenced groups make types, others pass through.
emitGroup :: [QN] -> Group -> Emit [Field]
emitGroup ns (GroupRef r o) = do
    g <- resolvedRef r
    t <- checkDefinedType NSGroup (getRefType r) $ \tn ->
         do
           fs <- emitGroup ns g
           return $ DataType tn [Ctor "" fs]
                  OtherDerives [] DataTypeComplex Nothing
    return $ forOccurs o $ Field (getRefType r) t One FieldOther 0
emitGroup ns (GroupChoice n o c doc) =
    concatMap (forOccurs o) <$> emitChoice (appendNames ns n) c doc
emitGroup ns (GroupSequence n o s doc) =
    concatMap (forOccurs o) <$> emitSequence (appendNames ns n) (Just o) s doc

-- | Choice production.
emitChoice :: [QN] -> Choice -> Maybe Documentation -> Emit [Field]
emitChoice ns (Choice o ps) doc = do
  fss <- mapM (emitParticle ns) ps
  t <- checkUniqueType NSChoice (Just $ head ns) $ \mn ->
       do
         let cctor fs = Ctor (_qLocal (chooseName fs)) fs
             chooseName [f] = _fieldName f
             chooseName fs = _fieldName (head fs) -- ++ show (length fs)
         return $ fixFields $
                DataType mn (map cctor fss) OtherDerives [] DataTypeCompositor doc
  return $ forOccurs o (Field (head ns) t One FieldOther 0)

-- | Guarantee unique constructor fields for a type.
fixFields :: Type -> Type
fixFields = over typeCtors (\cs -> evalState (mapM fixC cs) mempty)
    where fixC :: Ctor -> State (M.Map QN Int) Ctor
          fixC c@(Ctor _ fs) = do
            fs' <- mapM fixF fs
            return $ set ctorFields fs' c
          fixF :: Field -> State (M.Map QN Int) Field
          fixF f@(Field fn _ _ _ _) = do
            seen <- M.lookup fn <$> get
            modify (M.insertWith (+) fn 1)
            return $ maybe f (\i -> set fieldIdx i f) seen


-- | Sequence production.
emitSequence :: [QN] -> Maybe Occurs -> Sequence -> Maybe Documentation -> Emit [Field]
emitSequence ns parentO (Sequence o ps) doc = do
  fs <- concat <$> mapM (emitParticle ns) ps
  -- don't emit new type for 'One' cardinality
  case (occursToCardinality o,fmap occursToCardinality parentO) of
    (One,Nothing) -> return fs
    (One,Just One) -> return fs
    _ -> do
      t <- checkUniqueType NSSequence (Just $ head ns) $ \mn ->
           return $ fixFields $
                  DataType mn [Ctor "" fs] OtherDerives [] DataTypeCompositor doc
      return $ forOccurs o (Field (head ns) t One FieldOther 0)

-- | Particle field production.
-- Element fields emit containing element.
emitParticle :: [QN] -> Particle -> Emit [Field]
emitParticle _ (PartElement e) = do
  et <- emitElement e
  fn <- maybe (die "emitParticle: emitted element must have name") return $
        firstOf elementName e
  let o = fromMaybe (Occurs Nothing Nothing) $ firstOf elementOccurs e
  return $ forOccurs o $ Field fn et One FieldElement 0
emitParticle ns (PartGroup g) = emitGroup ns g
emitParticle ns (PartChoice c) = emitChoice ns c Nothing
emitParticle ns (PartSequence s) = emitSequence ns Nothing s Nothing


-- | Modify a field cardinality per occurs.
-- TODO: musicxml uses max occurs only for >1 or unbounded, but could be 1 ...
-- TODO use occursToCardinality
forOccurs :: Occurs -> Field -> [Field]
forOccurs (Occurs Nothing Nothing) f = [f]
forOccurs (Occurs (Just "0") Nothing) f = [set fieldCardinality ZeroOrOne f]
forOccurs (Occurs _ _) f = [set fieldCardinality Many f]

-- | Imprecise mapping of occurs-* to cardinality.
occursToCardinality :: Occurs -> Cardinality
occursToCardinality (Occurs Nothing Nothing) = One
occursToCardinality (Occurs Nothing (Just "1")) = One
occursToCardinality (Occurs (Just "0") Nothing) = ZeroOrOne
occursToCardinality (Occurs (Just "1") Nothing) = One
occursToCardinality (Occurs (Just "1") (Just "1")) = One
occursToCardinality (Occurs _ _) = Many


-- | Handle attribute and attribute group productions as fields.
emitAttrFields :: Attributes -> Emit [Field]
emitAttrFields  = doAttrs
    where doAttrs (Attributes as ags) =
              (++) <$> (catMaybes <$> mapM resolveAttr as) <*>
              (concat <$> mapM resolveAttrGroup ags)
          forUse Prohibited _ = Nothing
          forUse Optional a = Just $ set fieldCardinality ZeroOrOne a
          forUse Required a = Just $ set fieldCardinality One a
          resolveAttr (AttributeRef r u _) = do
              a <- resolvedRef r
              maybe Nothing (forUse u) <$> resolveAttr a
          resolveAttr (AttributeSimpleType n t) = do
              a <- checkDefinedType NSSimple n (doSimpleType t) -- TODO why not just doSimpleType, or emit??
              return $ Just $ Field n a One FieldAttribute 0
          resolveAttr (AttributeType n r u _) =
              resolvedRef r >>= emitSimpleType >>= \t ->
                  return $ forUse u $ Field n t One FieldAttribute 0
          resolveAttrGroup (AttributeGroup _ as doc) = doAttrs as
          resolveAttrGroup (AttributeGroupRef r) = resolvedRef r >>= resolveAttrGroup

-- | register a unique type, where namespace collisions will
-- | increment 'nIdx' on the namespace.
checkUniqueType :: Namespace -> Maybe QN -> (Name -> Emit Type) -> Emit Type
checkUniqueType ns mtn act = do
  n <- case mtn of
          Just t -> return t
          Nothing -> fmap (take 1) (use stack) >>= \h ->
                     case h of [Name _ a _] -> return a
                               _ -> die $ "checkType: empty stack on anon type: " ++ show ns
  let find i = do
        let cand = Name ns n i
        mt <- M.lookup cand <$> use types
        maybe (return cand) (\_ -> find (succ i)) mt
  mn <- find 0
  buildType mn act


-- | register or lookup a pre-defined type; the 'Namespace' argument
-- | should uniquely idenfify.
checkDefinedType :: Namespace -> QN -> (Name -> Emit Type) -> Emit Type
checkDefinedType ns tn act = do
  let mn = Name ns tn 0
  ts <- M.lookup mn <$> use types
  case ts of
    Just t -> return t
    Nothing -> buildType mn act

-- | Update state for new type.
buildType :: Name -> (Name -> Emit Type) -> Emit Type
buildType mn act = do
  stack %= (mn:)
  nt <- act mn
  stack %= tail
  types %= M.insert mn nt
  return nt