{-# LANGUAGE TypeSynonymInstances #-}
module Text.XML.HaXml.Schema.PrimitiveTypes
  ( -- * Type class for parsing simpleTypes
    SimpleType(..)
  , module Text.Parse
  , -- * Primitive XSD datatypes
    XsdString(..)
  , Boolean(..)
  , Base64Binary(..)
  , HexBinary(..)
  , Float(..)
  , Decimal(..)
  , Double(..)
  , AnyURI(..)
  , QName(..)
  , NOTATION(..)
  , Duration(..)
  , DateTime(..)
  , Time(..)
  , Date(..)
  , GYearMonth(..)
  , GYear(..)
  , GMonthDay(..)
  , GDay(..)
  , GMonth(..)
  , -- * Derived, yet builtin, datatypes
    NormalizedString(..)
  , Token(..)
  , Language(..)
  , Name(..)
  , NCName(..)
  , ID(..)
  , IDREF(..)
  , IDREFS(..)
  , ENTITY(..)
  , ENTITIES(..)
  , NMTOKEN(..)
  , NMTOKENS(..)
  , Integer(..)
  , NonPositiveInteger(..)
  , NegativeInteger(..)
  , Long(..)
  , Int(..)
  , Short(..)
  , Byte(..)
  , NonNegativeInteger(..)
  , UnsignedLong(..)
  , UnsignedInt(..)
  , UnsignedShort(..)
  , UnsignedByte(..)
  , PositiveInteger(..)
  ) where

import Text.Parse
import Data.Char as Char
--import Data.Time.LocalTime -- for dates and times?
import Text.XML.HaXml.Types (QName(..))
import Data.Int
import Data.Word

-- | Ultimately, an XML parser will find some plain text as the content
--   of a simpleType, which will need to be parsed.  We use a TextParser,
--   because values of simpleTypes can also be given elsewhere, e.g. as
--   attribute values in an XSD definition, e.g. to restrict the permissible
--   values of the simpleType.  Such restrictions are therefore implemented
--   as layered parsers.
class SimpleType a where
  acceptingParser :: TextParser a
  simpleTypeText  :: a -> String

-- * Primitive types

type Boolean      = Bool
newtype XsdString = XsdString    String    deriving (Eq,Show)
data Base64Binary = Base64Binary String    deriving (Eq,Show)
data HexBinary    = HexBinary    String    deriving (Eq,Show)
data AnyURI       = AnyURI       String    deriving (Eq,Show)
--data QName
data NOTATION     = NOTATION String -- or re-use NOTATION from HaXml.Types?
                                           deriving (Eq,Show)
data Decimal      = Decimal Double         deriving (Eq,Show)
--data Float
--data Double
data Duration     = Duration Bool Int Int Int Int Int Float  deriving (Eq,Show)

-- * All of the following temporal types are incompletely specified for now.
--   They should probably be mapped to something appropriate from the time
--   package?

data DateTime     = DateTime String        deriving (Eq,Show) -- LocalTime ?
data Time         = Time String            deriving (Eq,Show) -- TimeOfDay ?
data Date         = Date String            deriving (Eq,Show) -- Day ?
data GYearMonth   = GYearMonth String      deriving (Eq,Show) -- ??
data GYear        = GYear String           deriving (Eq,Show) -- ??
data GMonthDay    = GMonthDay String       deriving (Eq,Show) -- ??
data GDay         = GDay String            deriving (Eq,Show) -- ??
data GMonth       = GMonth String          deriving (Eq,Show) -- ??

isNext :: Char -> TextParser Char
isNext c = do d <- next
              if c==d then return c else fail ("expected "++c:", got "++d:".")

instance SimpleType Bool where
    acceptingParser = do w <- word
                         case w of "true"  -> return True;
                                   "false" -> return False
                                   "0"     -> return False;
                                   "1"     -> return True
                                   _       -> fail ("Not a bool: "++w)
    simpleTypeText False = "false"
    simpleTypeText True  = "true"
instance SimpleType XsdString where
    acceptingParser = fmap XsdString (many next)
    simpleTypeText (XsdString s) = s
instance SimpleType Base64Binary where
    acceptingParser = fmap Base64Binary (many (satisfy isAlphaNum `onFail`
                                               satisfy isSpace `onFail`
                                               satisfy (`elem`"+/=")))
    simpleTypeText (Base64Binary s) = s
instance SimpleType HexBinary where
    acceptingParser = fmap HexBinary (many (satisfy Char.isHexDigit))
    simpleTypeText (HexBinary s) = s
instance SimpleType AnyURI where
    acceptingParser = fmap AnyURI (many next) -- not very satisfactory
    simpleTypeText (AnyURI s) = s
instance SimpleType NOTATION where
    acceptingParser = fmap NOTATION (many next) -- not very satisfactory
    simpleTypeText (NOTATION s) = s

instance SimpleType Decimal where
    acceptingParser = fmap Decimal parse
    simpleTypeText (Decimal s) = show s -- XXX FIXME: showGFloat?
instance SimpleType Float where
    acceptingParser  = parse
    simpleTypeText x = show x           -- XXX FIXME: showGFloat?
instance SimpleType Double where
    acceptingParser  = parse
    simpleTypeText x = show x           -- XXX FIXME: showGFloat?

instance SimpleType Duration where
    acceptingParser = return Duration `apply` (do isNext '-'; return False
                                               `onFail` return True)
                                      `discard` isNext 'P'
                                      `apply` ((parseDec `discard` isNext 'Y')
                                               `onFail` return 0)
                                      `apply` ((parseDec `discard` isNext 'M')
                                               `onFail` return 0)
                                      `apply` ((parseDec `discard` isNext 'D')
                                               `onFail` return 0)
                                      `discard` (isNext 'T'`onFail`return 'T')
                                      -- fix: T absent iff H:M:S absent also
                                      `apply` ((parseDec `discard` isNext 'H')
                                               `onFail` return 0)
                                      `apply` ((parseDec `discard` isNext 'M')
                                               `onFail` return 0)
                                      `apply` ((parseFloat `discard` isNext 'S')
                                               `onFail` return 0)
    simpleTypeText (Duration pos 0 0 0 0 0 0) = (if pos then "" else "-")++"P0S"
    simpleTypeText (Duration pos y m d h n s) =
        (if pos then "" else "-")++"P"++showUnit y 'Y'++showUnit m 'M'++showUnit d 'D'++showTime
      where
        showUnit :: (Num a,Eq a,Show a) => a -> Char -> String
        showUnit n u = if n == 0 then "" else show n ++ [u]
        showTime = if (h,n,s) == (0,0,0) then "" else "T"++showUnit h 'H'++showUnit n 'M'++showUnit s 'S'
instance SimpleType DateTime where
    acceptingParser = fmap DateTime (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for DateTime"
    simpleTypeText (DateTime x) = x
instance SimpleType Time where
    acceptingParser = fmap Time (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for Time"
    simpleTypeText (Time x) = x
instance SimpleType Date where
    acceptingParser = fmap Date (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for Date"
    simpleTypeText (Date x) = x
instance SimpleType GYearMonth where
    acceptingParser = fmap GYearMonth (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for GYearMonth"
    simpleTypeText (GYearMonth x) = x
instance SimpleType GYear where
    acceptingParser = fmap GYear (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for GYear"
    simpleTypeText (GYear x) = x
instance SimpleType GMonthDay where
    acceptingParser = fmap GMonthDay (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for GMonthDay"
    simpleTypeText (GMonthDay x) = x
instance SimpleType GDay where
    acceptingParser = fmap GDay (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for GDay"
    simpleTypeText (GDay x) = x
instance SimpleType GMonth where
    acceptingParser = fmap GMonth (many next)
 -- acceptingParser = fail "not implemented: simpletype parser for GMonth"
    simpleTypeText (GMonth x) = x

-- * Derived builtin types

newtype NormalizedString = Normalized String    deriving (Eq,Show)
newtype Token    = Token    String              deriving (Eq,Show)
newtype Language = Language String              deriving (Eq,Show)
newtype Name     = Name     String              deriving (Eq,Show)
newtype NCName   = NCName   String              deriving (Eq,Show)
newtype ID       = ID       String              deriving (Eq,Show)
newtype IDREF    = IDREF    String              deriving (Eq,Show)
newtype IDREFS   = IDREFS   String              deriving (Eq,Show)
newtype ENTITY   = ENTITY   String              deriving (Eq,Show)
newtype ENTITIES = ENTITIES String              deriving (Eq,Show)
newtype NMTOKEN  = NMTOKEN  String              deriving (Eq,Show)
newtype NMTOKENS = NMTOKENS String              deriving (Eq,Show)

instance SimpleType NormalizedString where
    acceptingParser = fmap Normalized (many next)
    simpleTypeText (Normalized x) = x
instance SimpleType Token where
    acceptingParser = fmap Token (many next)
    simpleTypeText (Token x) = x
instance SimpleType Language where
    acceptingParser = fmap Language (many next)
    simpleTypeText (Language x) = x
instance SimpleType Name where
    acceptingParser = fmap Name (many next)
    simpleTypeText (Name x) = x
instance SimpleType NCName where
    acceptingParser = fmap NCName (many next)
    simpleTypeText (NCName x) = x
instance SimpleType ID where
    acceptingParser = fmap ID (many next)
    simpleTypeText (ID x) = x
instance SimpleType IDREF where
    acceptingParser = fmap IDREF (many next)
    simpleTypeText (IDREF x) = x
instance SimpleType IDREFS where
    acceptingParser = fmap IDREFS (many next)
    simpleTypeText (IDREFS x) = x
instance SimpleType ENTITY where
    acceptingParser = fmap ENTITY (many next)
    simpleTypeText (ENTITY x) = x
instance SimpleType ENTITIES where
    acceptingParser = fmap ENTITIES (many next)
    simpleTypeText (ENTITIES x) = x
instance SimpleType NMTOKEN where
    acceptingParser = fmap NMTOKEN (many next)
    simpleTypeText (NMTOKEN x) = x
instance SimpleType NMTOKENS where
    acceptingParser = fmap NMTOKENS (many next)
    simpleTypeText (NMTOKENS x) = x

--data Integer
newtype NonPositiveInteger = NonPos   Integer   deriving (Eq,Show)
newtype NegativeInteger    = Negative Integer   deriving (Eq,Show)
newtype Long               = Long     Int64     deriving (Eq,Show)
--data Int
newtype Short              = Short    Int16     deriving (Eq,Show)
newtype Byte               = Byte     Int8      deriving (Eq,Show)
newtype NonNegativeInteger = NonNeg   Integer   deriving (Eq,Show)
newtype UnsignedLong       = ULong    Word64    deriving (Eq,Show)
newtype UnsignedInt        = UInt     Word32    deriving (Eq,Show)
newtype UnsignedShort      = UShort   Word16    deriving (Eq,Show)
newtype UnsignedByte       = UByte    Word8     deriving (Eq,Show)
newtype PositiveInteger    = Positive Integer   deriving (Eq,Show)

instance SimpleType Integer where
    acceptingParser = parse
    simpleTypeText  = show
instance SimpleType NonPositiveInteger where
    acceptingParser = fmap NonPos parse
    simpleTypeText (NonPos x) = show x
instance SimpleType NegativeInteger where
    acceptingParser = fmap Negative parse
    simpleTypeText (Negative x) = show x
instance SimpleType Long where
    acceptingParser = fmap (Long . fromInteger) parse
    simpleTypeText (Long x) = show x
instance SimpleType Int where
    acceptingParser = parse
    simpleTypeText  = show
instance SimpleType Short where
    acceptingParser = fmap (Short . fromInteger) parse
    simpleTypeText (Short x) = show x
instance SimpleType Byte where
    acceptingParser = fmap (Byte . fromInteger) parse
    simpleTypeText (Byte x) = show x
instance SimpleType NonNegativeInteger where
    acceptingParser = fmap NonNeg parse
    simpleTypeText (NonNeg x) = show x
instance SimpleType UnsignedLong where
    acceptingParser = fmap (ULong . fromInteger) parse
    simpleTypeText (ULong x) = show x
instance SimpleType UnsignedInt where
    acceptingParser = fmap (UInt . fromInteger) parse
    simpleTypeText (UInt x) = show x
instance SimpleType UnsignedShort where
    acceptingParser = fmap (UShort . fromInteger) parse
    simpleTypeText (UShort x) = show x
instance SimpleType UnsignedByte where
    acceptingParser = fmap (UByte . fromInteger) parse
    simpleTypeText (UByte x) = show x
instance SimpleType PositiveInteger where
    acceptingParser = fmap Positive parse
    simpleTypeText (Positive x) = show x
