module Text.XML.HXT.Arrow.Pickle.Xml
where
#if MIN_VERSION_base(4,8,0)
#else
import           Control.Applicative              (Applicative (..))
#endif
import           Control.Arrow.ArrowList
import           Control.Arrow.ListArrows
import           Control.Monad                    ()
#if MIN_VERSION_mtl(2,2,0)
import           Control.Monad.Except             (MonadError (..))
#else
import           Control.Monad.Error              (MonadError (..))
#endif
import           Control.Monad.State              (MonadState (..), gets,
                                                   modify)
import           Data.Char                        (isDigit)
import           Data.List                        (foldl')
import           Data.Map                         (Map)
import qualified Data.Map                         as M
import           Data.Maybe                       (fromJust, fromMaybe)
import           Text.XML.HXT.Arrow.Edit          (xshowEscapeXml)
import           Text.XML.HXT.Arrow.Pickle.Schema
import           Text.XML.HXT.Arrow.ReadDocument  (xread)
import           Text.XML.HXT.Arrow.WriteDocument (writeDocumentToString)
import           Text.XML.HXT.Arrow.XmlState
import           Text.XML.HXT.DOM.Interface
import qualified Text.XML.HXT.DOM.ShowXml         as XN
import qualified Text.XML.HXT.DOM.XmlNode         as XN
data St         = St { attributes :: [XmlTree]
                     , contents   :: [XmlTree]
                     , nesting    :: Int                
                     , pname      :: QName              
                     , pelem      :: Bool
                     } deriving (Show)
data PU a       = PU { appPickle   :: Pickler a         
                     , appUnPickle :: Unpickler a
                     , theSchema   :: Schema
                     }
type Pickler a          = a -> St -> St
newtype Unpickler a     = UP { runUP :: St -> (UnpickleVal a, St) }
type UnpickleVal a      = Either UnpickleErr a
type UnpickleErr        = (String, St)
instance Functor Unpickler where
    fmap f u    = UP $ \ st ->
                  let (r, st') = runUP u st in (fmap f r, st')
instance Applicative Unpickler where
    pure a      = UP $ \ st -> (Right a, st)
    uf <*> ua   = UP $ \ st ->
                  let (f, st') = runUP uf st in
                  case f of
                    Left err -> (Left err, st')
                    Right f' -> runUP (fmap f' ua) st'
instance Monad Unpickler where
    return      = pure
    u >>= f     = UP $ \ st ->
                  let (r, st') = runUP u st in
                  case r of
                    Left err -> (Left err, st')
                    Right v  -> runUP (f v) st'
    fail        = throwMsg                              
instance MonadState St Unpickler where
    get         = UP $ \ st -> (Right st, st)
    put st      = UP $ \ _  -> (Right (), st)
instance MonadError UnpickleErr Unpickler where
    throwError err
                = UP $ \ st -> (Left err, st)
    
    catchError u handler
                = UP $ \ st ->
                  let (r, st') = runUP u st in
                  case r of
                    Left err -> runUP (handler err) st  
                    _        -> (r, st')
throwMsg        :: String -> Unpickler a
throwMsg msg    = UP $ \ st -> (Left (msg, st), st)
mchoice         :: Unpickler a -> (a -> Unpickler b) -> Unpickler b -> Unpickler b
mchoice u f v   = UP $ \ st ->
                  let (r, st') = runUP u st in
                  case r of
                    Right x
                        -> runUP (f x) st'                      
                    Left e@(_msg, st'')
                        -> if nesting st'' == nesting st        
                           then runUP v st                      
                           else (Left e, st')                   
                                                                
                                                                
liftMaybe       :: String -> Maybe a -> Unpickler a
liftMaybe e v  = case v of
                    Nothing -> throwMsg e
                    Just x  -> return x
liftUnpickleVal         :: UnpickleVal a -> Unpickler a
liftUnpickleVal v       = UP $ \ st -> (v, st)
getCont         :: Unpickler XmlTree
getCont         = do cs <- gets contents
                     case cs of
                       []       -> throwMsg "no more contents to be read"
                       (x : xs) -> do modify (\ s -> s {contents = xs})
                                      return x
getAtt          :: QName -> Unpickler XmlTree
getAtt qn       = do as <- gets attributes
                     case findAtt as of
                       Nothing -> throwMsg $ "no attribute value found for " ++ show qn
                       Just (a, as') -> do modify (\ s -> s {attributes = as'})
                                           return $ nonEmptyVal a
    where
      findAtt     = findElem (maybe False (== qn) . XN.getAttrName)
      nonEmptyVal a'
          | null (XN.getChildren a') = XN.setChildren [et] a'
          | otherwise                = a'
          where
            et = XN.mkText ""
getNSAtt        :: String -> Unpickler ()
getNSAtt ns     = do as <- gets attributes
                     case findNS as of
                       Nothing        -> throwMsg $
                                         "no namespace declaration found for namespace " ++ show ns
                       Just (_a, as') -> do modify (\ s -> s {attributes = as'})
                                            return ()
    where
      isNS t    = (fromMaybe False . fmap isNameSpaceName . XN.getAttrName $ t)
                  &&
                  XN.xshow (XN.getChildren t) == ns
      findNS    = findElem isNS
emptySt         :: St
emptySt         =  St { attributes = []
                      , contents   = []
                      , nesting    = 0
                      , pname      = mkName "/"
                      , pelem      = True
                      }
putAtt          :: QName -> [XmlTree] -> St -> St
putAtt qn v s   = s {attributes = x : attributes s}
                  where
                    x = XN.mkAttr qn v
putCont         :: XmlTree -> St -> St
putCont x s     = s {contents = x : contents s}
findElem       :: (a -> Bool) -> [a] -> Maybe (a, [a])
findElem p     = find' id
    where
      find' _ []         = Nothing
      find' prefix (x : xs)
          | p x          = Just (x, prefix xs)
          | otherwise    = find' (prefix . (x:)) xs
formatSt                :: St -> String
formatSt st             = fcx ++
                          fa (attributes st) ++
                          fc (contents   st)
    where
      fcx               = "\n" ++ "context:    " ++
                          ( if pelem st
                            then "element"
                            else "attribute"
                          ) ++
                          " " ++ show (pname st)
      fc []             = ""
      fc cs             = "\n" ++ "contents:   " ++ formatXML cs
      fa []             = ""
      fa as             = "\n" ++ "attributes: " ++ formatXML as
      formatXML         = format 80 . showXML
      showXML           = concat . runLA ( xshowEscapeXml unlistA )
      format n s        = let s' = take (n + 1) s in
                          if length s' <= n then s' else take n s ++ "..."
pickleDoc       :: PU a -> a -> XmlTree
pickleDoc p v   = XN.mkRoot (attributes st) (contents st)
    where
      st        = appPickle p v emptySt
unpickleDoc     :: PU a -> XmlTree -> Maybe a
unpickleDoc p   = either (const Nothing) Just
                  . unpickleDoc' p
unpickleDoc'    :: PU a -> XmlTree -> Either String a
unpickleDoc' p t
    | XN.isRoot t       = mapErr $
                          unpickleElem' p 0              t
    | otherwise         = unpickleDoc'  p (XN.mkRoot [] [t])
    where
      mapErr            = either ( Left .
                                   \ (msg, st) -> msg ++ formatSt st
                                 ) Right
unpickleElem'   :: PU a -> Int -> XmlTree -> UnpickleVal a
unpickleElem' p l t
    = 
      ( fst . runUP (appUnPickle p) )
      $ St { attributes = fromMaybe [] .
                          XN.getAttrl $  t
           , contents   = XN.getChildren t
           , nesting    = l
           , pname      = fromJust .
                          XN.getName  $  t
           , pelem      = XN.isElem      t
           }
showPickled :: (XmlPickler a) => SysConfigList -> a -> String
showPickled a = concat . (pickleDoc xpickle >>> runLA (writeDocumentToString a))
xpZero                  :: String -> PU a
xpZero err              =  PU { appPickle   = const id
                              , appUnPickle = throwMsg err
                              , theSchema   = scNull
                              }
xpUnit                  :: PU ()
xpUnit                  = xpLift ()
xpCheckEmptyContents    :: PU a -> PU a
xpCheckEmptyContents pa =  PU { appPickle   = appPickle pa
                              , appUnPickle = do res <- appUnPickle pa
                                                 cs <- gets contents
                                                 if null cs
                                                    then return res
                                                    else contentsLeft
                              , theSchema   = scNull
                              }
    where
      contentsLeft      = throwMsg
                          "xpCheckEmptyContents: unprocessed XML content detected"
xpCheckEmptyAttributes  :: PU a -> PU a
xpCheckEmptyAttributes pa
                        =  PU { appPickle   = appPickle pa
                              , appUnPickle = do res <- appUnPickle pa
                                                 as <- gets attributes
                                                 if null as
                                                    then return res
                                                    else attributesLeft
                              , theSchema   = scNull
                              }
    where
      attributesLeft    = throwMsg
                          "xpCheckEmptyAttributes: unprocessed XML attribute(s) detected"
xpCheckEmpty            :: PU a -> PU a
xpCheckEmpty            = xpCheckEmptyAttributes . xpCheckEmptyContents
xpLift                  :: a -> PU a
xpLift x                =  PU { appPickle   = const id
                              , appUnPickle = return x
                              , theSchema   = scEmpty
                              }
xpLiftMaybe                     :: Maybe a -> PU a
xpLiftMaybe v                   = (xpLiftMaybe'' v) { theSchema = scOption scEmpty }
    where
    xpLiftMaybe'' Nothing       = xpZero "xpLiftMaybe: got Nothing"
    xpLiftMaybe'' (Just x)      = xpLift x
xpLiftEither                    :: Either String a -> PU a
xpLiftEither v                  = (xpLiftEither'' v) { theSchema = scOption scEmpty }
    where
    xpLiftEither'' (Left err)   = xpZero err
    xpLiftEither'' (Right x)    = xpLift x
xpSeq           :: (b -> a) -> PU a -> (a -> PU b) -> PU b
xpSeq f pa k
    = PU { appPickle  = ( \ b ->
                          let a = f b in
                          appPickle pa a . appPickle (k a) b
                         )
         , appUnPickle = appUnPickle pa >>= (appUnPickle . k)
         , theSchema   = undefined
         }
xpSeq'          :: PU () -> PU a -> PU a
xpSeq' pa       = xpWrap ( snd
                         , \ y -> ((), y)
                         ) .
                  xpPair pa
xpChoice                :: PU b -> PU a -> (a -> PU b) -> Unpickler b
xpChoice pb pa k        = mchoice (appUnPickle pa) (appUnPickle . k) (appUnPickle pb)
xpWrap                  :: (a -> b, b -> a) -> PU a -> PU b
xpWrap (i, j) pa        = (xpSeq j pa (xpLift . i)) { theSchema = theSchema pa }
xpWrapMaybe             :: (a -> Maybe b, b -> a) -> PU a -> PU b
xpWrapMaybe (i, j) pa   = (xpSeq j pa (xpLiftMaybe . i)) { theSchema = theSchema pa }
xpWrapEither             :: (a -> Either String b, b -> a) -> PU a -> PU b
xpWrapEither (i, j) pa   = (xpSeq j pa (xpLiftEither . i)) { theSchema = theSchema pa }
xpPair  :: PU a -> PU b -> PU (a, b)
xpPair pa pb
    = ( xpSeq fst pa (\ a ->
        xpSeq snd pb (\ b ->
        xpLift (a,b)))
      ) { theSchema = scSeq (theSchema pa) (theSchema pb) }
xpTriple        :: PU a -> PU b -> PU c -> PU (a, b, c)
xpTriple pa pb pc
    = xpWrap (toTriple, fromTriple) (xpPair pa (xpPair pb pc))
    where
    toTriple   ~(a, ~(b, c)) = (a,  b, c )
    fromTriple ~(a,   b, c ) = (a, (b, c))
xp4Tuple        :: PU a -> PU b -> PU c -> PU d -> PU (a, b, c, d)
xp4Tuple pa pb pc pd
    = xpWrap (toQuad, fromQuad) (xpPair pa (xpPair pb (xpPair pc pd)))
    where
    toQuad   ~(a, ~(b, ~(c, d))) = (a,  b,  c, d  )
    fromQuad ~(a,   b,   c, d  ) = (a, (b, (c, d)))
xp5Tuple        :: PU a -> PU b -> PU c -> PU d -> PU e -> PU (a, b, c, d, e)
xp5Tuple pa pb pc pd pe
    = xpWrap (toQuint, fromQuint) (xpPair pa (xpPair pb (xpPair pc (xpPair pd pe))))
    where
    toQuint   ~(a, ~(b, ~(c, ~(d, e)))) = (a,  b,  c,  d, e   )
    fromQuint ~(a,   b,   c,   d, e   ) = (a, (b, (c, (d, e))))
xp6Tuple        :: PU a -> PU b -> PU c -> PU d -> PU e -> PU f -> PU (a, b, c, d, e, f)
xp6Tuple pa pb pc pd pe pf
    = xpWrap (toSix, fromSix) (xpPair pa (xpPair pb (xpPair pc (xpPair pd (xpPair pe pf)))))
    where
    toSix   ~(a, ~(b, ~(c, ~(d, ~(e, f))))) = (a,  b,  c,  d,  e, f    )
    fromSix ~(a,   b,   c,   d,   e, f)     = (a, (b, (c, (d, (e, f)))))
xp7Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
            PU f -> PU g -> PU (a, b, c, d, e, f, g)
xp7Tuple a b c d e f g
    = xpWrap ( \ (a, (b, c, d, e, f, g)) -> (a, b, c, d, e, f, g)
             , \ (a, b, c, d, e, f, g)   -> (a, (b, c, d, e, f, g))
             )
      (xpPair a (xp6Tuple b c d e f g))
xp8Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
            PU f -> PU g -> PU h -> PU (a, b, c, d, e, f, g, h)
xp8Tuple a b c d e f g h
    = xpWrap ( \ ((a, b), (c, d, e, f, g, h)) -> (a, b, c, d, e, f, g, h)
             , \ (a, b, c, d, e, f, g, h) -> ((a, b), (c, d, e, f, g, h))
             )
      (xpPair (xpPair a b) (xp6Tuple c d e f g h))
xp9Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
            PU f -> PU g -> PU h -> PU i -> PU (a, b, c, d, e, f, g, h, i)
xp9Tuple a b c d e f g h i
    = xpWrap ( \ ((a, b, c), (d, e, f, g, h, i)) -> (a, b, c, d, e, f, g, h, i)
             , \ (a, b, c, d, e, f, g, h, i) -> ((a, b, c), (d, e, f, g, h, i))
             )
      (xpPair (xpTriple a b c) (xp6Tuple d e f g h i))
xp10Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU (a, b, c, d, e, f, g, h, i, j)
xp10Tuple a b c d e f g h i j
    = xpWrap ( \ ((a, b, c, d), (e, f, g, h, i, j)) -> (a, b, c, d, e, f, g, h, i, j)
             , \ (a, b, c, d, e, f, g, h, i, j) -> ((a, b, c, d), (e, f, g, h, i, j))
             )
      (xpPair (xp4Tuple a b c d) (xp6Tuple e f g h i j))
xp11Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU (a, b, c, d, e, f, g, h, i, j, k)
xp11Tuple a b c d e f g h i j k
    = xpWrap ( \ ((a, b, c, d, e), (f, g, h, i, j, k)) -> (a, b, c, d, e, f, g, h, i, j, k)
             , \ (a, b, c, d, e, f, g, h, i, j, k) -> ((a, b, c, d, e), (f, g, h, i, j, k))
             )
      (xpPair (xp5Tuple a b c d e) (xp6Tuple f g h i j k))
xp12Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU (a, b, c, d, e, f, g, h, i, j, k, l)
xp12Tuple a b c d e f g h i j k l
    = xpWrap ( \ ((a, b, c, d, e, f), (g, h, i, j, k, l)) -> (a, b, c, d, e, f, g, h, i, j, k, l)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l) -> ((a, b, c, d, e, f), (g, h, i, j, k, l))
             )
      (xpPair (xp6Tuple a b c d e f) (xp6Tuple g h i j k l))
xp13Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m)
xp13Tuple a b c d e f g h i j k l m
    = xpWrap ( \ (a, (b, c, d, e, f, g), (h, i, j, k, l, m)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m) -> (a, (b, c, d, e, f, g), (h, i, j, k, l, m))
             )
      (xpTriple a (xp6Tuple b c d e f g) (xp6Tuple h i j k l m))
xp14Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n)
xp14Tuple a b c d e f g h i j k l m n
    = xpWrap ( \ ((a, b), (c, d, e, f, g, h), (i, j, k, l, m, n)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n) -> ((a, b), (c, d, e, f, g, h), (i, j, k, l, m, n))
             )
      (xpTriple (xpPair a b) (xp6Tuple c d e f g h) (xp6Tuple i j k l m n))
xp15Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
xp15Tuple a b c d e f g h i j k l m n o
    = xpWrap ( \ ((a, b, c), (d, e, f, g, h, i), (j, k, l, m, n, o)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) -> ((a, b, c), (d, e, f, g, h, i), (j, k, l, m, n, o))
             )
      (xpTriple (xpTriple a b c) (xp6Tuple d e f g h i) (xp6Tuple j k l m n o))
xp16Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
xp16Tuple a b c d e f g h i j k l m n o p
    = xpWrap ( \ ((a, b, c, d), (e, f, g, h, i, j), (k, l, m, n, o, p)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) -> ((a, b, c, d), (e, f, g, h, i, j), (k, l, m, n, o, p))
             )
      (xpTriple (xp4Tuple a b c d) (xp6Tuple e f g h i j) (xp6Tuple k l m n o p))
xp17Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q)
xp17Tuple a b c d e f g h i j k l m n o p q
    = xpWrap ( \ ((a, b, c, d, e), (f, g, h, i, j, k), (l, m, n, o, p, q)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q) -> ((a, b, c, d, e), (f, g, h, i, j, k), (l, m, n, o, p, q))
             )
      (xpTriple (xp5Tuple a b c d e) (xp6Tuple f g h i j k) (xp6Tuple l m n o p q))
xp18Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
xp18Tuple a b c d e f g h i j k l m n o p q r
    = xpWrap ( \ ((a, b, c, d, e, f), (g, h, i, j, k, l), (m, n, o, p, q, r)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r) -> ((a, b, c, d, e, f), (g, h, i, j, k, l), (m, n, o, p, q, r))
             )
      (xpTriple (xp6Tuple a b c d e f) (xp6Tuple g h i j k l) (xp6Tuple m n o p q r))
xp19Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU s -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
xp19Tuple a b c d e f g h i j k l m n o p q r s
    = xpWrap ( \ (a, (b, c, d, e, f, g), (h, i, j, k, l, m), (n, o, p, q, r, s)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) -> (a, (b, c, d, e, f, g), (h, i, j, k, l, m), (n, o, p, q, r, s))
             )
      (xp4Tuple a (xp6Tuple b c d e f g) (xp6Tuple h i j k l m) (xp6Tuple n o p q r s))
xp20Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU s -> PU t ->
             PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t)
xp20Tuple a b c d e f g h i j k l m n o p q r s t
    = xpWrap ( \ ((a, b), (c, d, e, f, g, h), (i, j, k, l, m, n), (o, p, q, r, s, t)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) -> ((a, b), (c, d, e, f, g, h), (i, j, k, l, m, n), (o, p, q, r, s, t))
             )
      (xp4Tuple (xpPair a b) (xp6Tuple c d e f g h) (xp6Tuple i j k l m n) (xp6Tuple o p q r s t))
xp21Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU s -> PU t ->
             PU u -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
xp21Tuple a b c d e f g h i j k l m n o p q r s t u
    = xpWrap ( \ ((a, b, c), (d, e, f, g, h, i), (j, k, l, m, n, o), (p, q, r, s, t, u)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) -> ((a, b, c), (d, e, f, g, h, i), (j, k, l, m, n, o), (p, q, r, s, t, u))
             )
      (xp4Tuple (xpTriple a b c) (xp6Tuple d e f g h i) (xp6Tuple j k l m n o) (xp6Tuple p q r s t u))
xp22Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU s -> PU t ->
             PU u -> PU v -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v)
xp22Tuple a b c d e f g h i j k l m n o p q r s t u v
    = xpWrap ( \ ((a, b, c, d), (e, f, g, h, i, j), (k, l, m, n, o, p), (q, r, s, t, u, v)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) -> ((a, b, c, d), (e, f, g, h, i, j), (k, l, m, n, o, p), (q, r, s, t, u, v))
             )
      (xp4Tuple (xp4Tuple a b c d) (xp6Tuple e f g h i j) (xp6Tuple k l m n o p) (xp6Tuple q r s t u v))
xp23Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU s -> PU t ->
             PU u -> PU v -> PU w -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w)
xp23Tuple a b c d e f g h i j k l m n o p q r s t u v w
    = xpWrap ( \ ((a, b, c, d, e), (f, g, h, i, j, k), (l, m, n, o, p, q), (r, s, t, u, v, w)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w) -> ((a, b, c, d, e), (f, g, h, i, j, k), (l, m, n, o, p, q), (r, s, t, u, v, w))
             )
      (xp4Tuple (xp5Tuple a b c d e) (xp6Tuple f g h i j k) (xp6Tuple l m n o p q) (xp6Tuple r s t u v w))
xp24Tuple :: PU a -> PU b -> PU c -> PU d -> PU e ->
             PU f -> PU g -> PU h -> PU i -> PU j ->
             PU k -> PU l -> PU m -> PU n -> PU o ->
             PU p -> PU q -> PU r -> PU s -> PU t ->
             PU u -> PU v -> PU w -> PU x -> PU (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x)
xp24Tuple a b c d e f g h i j k l m n o p q r s t u v w x
    = xpWrap ( \ ((a, b, c, d, e, f), (g, h, i, j, k, l), (m, n, o, p, q, r), (s, t, u, v, w, x)) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x)
             , \ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x) -> ((a, b, c, d, e, f), (g, h, i, j, k, l), (m, n, o, p, q, r), (s, t, u, v, w, x))
             )
      (xp4Tuple (xp6Tuple a b c d e f) (xp6Tuple g h i j k l) (xp6Tuple m n o p q r) (xp6Tuple s t u v w x))
xpText  :: PU String
xpText  = xpTextDT scString1
xpTextDT        :: Schema -> PU String
xpTextDT sc     = PU { appPickle   = putCont . XN.mkText
                     , appUnPickle = do t <- getCont
                                        liftMaybe "xpText: XML text expected" $ XN.getText t
                     , theSchema   = sc
                     }
xpText0         :: PU String
xpText0         = xpText0DT scString1
xpText0DT       :: Schema -> PU String
xpText0DT sc    = xpWrap (fromMaybe "", emptyToNothing) $
                  xpOption $
                  xpTextDT sc
    where
    emptyToNothing "" = Nothing
    emptyToNothing x  = Just x
xpPrim                  :: (Read a, Show a) => PU a
xpPrim                  = xpWrapEither (readMaybe, show) xpText
    where
    readMaybe           :: Read a => String -> Either String a
    readMaybe str       = val (reads str)
        where
          val [(x,"")]  = Right x
          val _         = Left $ "xpPrim: reading string " ++ show str ++ " failed"
xpInt                   :: PU Int
xpInt                   = xpWrapEither (readMaybe, show) xpText
    where
      readMaybe xs@(_:_)
          | all isDigit xs = Right . foldl' (\ r c -> 10 * r + (fromEnum c  fromEnum '0')) 0 $ xs
      readMaybe ('-' : xs) = fmap (0 ) . readMaybe $ xs
      readMaybe ('+' : xs) =              readMaybe $ xs
      readMaybe        xs  = Left $ "xpInt: reading an Int from string " ++ show xs ++ " failed"
xpTree          :: PU XmlTree
xpTree          = PU { appPickle   = putCont
                     , appUnPickle = getCont
                     , theSchema   = Any
                     }
xpTrees         :: PU [XmlTree]
xpTrees         = (xpList xpTree) { theSchema = Any }
xpXmlText       :: PU String
xpXmlText       = xpWrap ( showXML, readXML ) $ xpTrees
    where
      showXML   = concat . runLA ( xshowEscapeXml unlistA )
      readXML   = runLA xread
xpOption        :: PU a -> PU (Maybe a)
xpOption pa     = PU { appPickle  = ( \ a ->
                                      case a of
                                        Nothing -> id
                                        Just x  -> appPickle pa x
                                    )
                     , appUnPickle = xpChoice (xpLift Nothing) pa (xpLift . Just)
                     , theSchema   = scOption (theSchema pa)
                     }
xpDefault       :: (Eq a) => a -> PU a -> PU a
xpDefault df    = xpWrap ( fromMaybe df
                         , \ x -> if x == df then Nothing else Just x
                         ) .
                  xpOption
xpList          :: PU a -> PU [a]
xpList pa       = PU { appPickle  = ( \ a ->
                                      case a of
                                        []  -> id
                                        _:_ -> appPickle pc a
                                    )
                     , appUnPickle = xpChoice
                                     (xpLift [])
                                     pa
                                     (\ x -> xpSeq id (xpList pa) (\xs -> xpLift (x:xs)))
                     , theSchema   = scList (theSchema pa)
                     }
      where
      pc        = xpSeq head  pa         (\ x  ->
                  xpSeq tail (xpList pa) (\ xs ->
                  xpLift (x:xs)          ))
xpList1         :: PU a -> PU [a]
xpList1 pa      = ( xpWrap (\ (x, xs) -> x : xs
                           ,\ x -> (head x, tail x)
                           ) $
                    xpPair pa (xpList pa)
                  ) { theSchema = scList1 (theSchema pa) }
xpMap           :: Ord k => String -> String -> PU k -> PU v -> PU (Map k v)
xpMap en an xpk xpv
                = xpWrap ( M.fromList
                         , M.toList
                         ) $
                  xpList $
                  xpElem en $
                  xpPair ( xpAttr an $ xpk ) xpv
xpAlt           :: (a -> Int) -> [PU a] -> PU a
xpAlt tag ps    = PU { appPickle   = \ a ->
                                     appPickle (ps !! tag a) a
                     , appUnPickle = case ps of
                                       []     -> throwMsg "xpAlt: no matching unpickler found for a sum datatype"
                                       pa:ps1 -> xpChoice (xpAlt tag ps1) pa xpLift
                     , theSchema   = scAlts (map theSchema ps)
                     }
xpElemQN        :: QName -> PU a -> PU a
xpElemQN qn pa  = PU { appPickle   = ( \ a ->
                                       let st' = appPickle pa a emptySt in
                                       putCont (XN.mkElement qn (attributes st') (contents st'))
                                     )
                     , appUnPickle = upElem
                     , theSchema   = scElem (qualifiedName qn) (theSchema pa)
                     }
      where
      upElem    = do t <- getCont
                     n <- liftMaybe "xpElem: XML element expected" $ XN.getElemName t
                     if n /= qn
                        then throwMsg ("xpElem: got element name " ++ show n ++ ", but expected " ++ show qn)
                        else do l <- gets nesting
                                liftUnpickleVal $ unpickleElem' (xpCheckEmpty pa) (l + 1) t
xpElem          :: String -> PU a -> PU a
xpElem          = xpElemQN . mkName
xpElemNS        :: String -> String -> String -> PU a -> PU a
xpElemNS ns px lp
                = xpElemQN $ mkQName px lp ns
xpElemWithAttrValue     :: String -> String -> String -> PU a -> PU a
xpElemWithAttrValue name an av pa
                = xpElem name $
                  xpAddFixedAttr an av $
                  pa
xpAttrQN        :: QName -> PU a -> PU a
xpAttrQN qn pa  = PU { appPickle   = ( \ a ->
                                       let st' = appPickle pa a emptySt in
                                       putAtt qn (contents st')
                                     )
                     , appUnPickle = upAttr
                     , theSchema   = scAttr (qualifiedName qn) (theSchema pa)
                     }
      where
      upAttr    = do a <- getAtt qn
                     l <- gets nesting
                     liftUnpickleVal $ unpickleElem' (xpCheckEmptyContents pa) l a
xpAttr          :: String -> PU a -> PU a
xpAttr          = xpAttrQN . mkName
xpAttrNS        :: String -> String -> String -> PU a -> PU a
xpAttrNS ns px lp
                = xpAttrQN (mkQName px lp ns)
xpTextAttr      :: String -> PU String
xpTextAttr      = flip xpAttr xpText
xpAttrImplied   :: String -> PU a -> PU (Maybe a)
xpAttrImplied name pa
                = xpOption $ xpAttr name pa
xpAttrFixed     :: String -> String -> PU ()
xpAttrFixed name val
                = ( xpWrapEither ( \ v ->
                                   if v == val
                                   then Right ()
                                   else Left ( "xpAttrFixed: value "
                                               ++ show val
                                               ++ " expected, but got "
                                               ++ show v
                                             )
                                 , const val
                                 ) $
                    xpAttr name xpText
                  ) { theSchema   = scAttr name (scFixed val) }
xpAddFixedAttr  :: String -> String -> PU a -> PU a
xpAddFixedAttr name val
                = xpSeq' $ xpAttrFixed name val
xpAddNSDecl  :: String -> String -> PU a -> PU a
xpAddNSDecl name val
                = xpSeq' $ xpAttrNSDecl name' val
    where
      name'
          | null name = "xmlns"
          | otherwise = "xmlns:" ++ name
xpAttrNSDecl     :: String -> String -> PU ()
xpAttrNSDecl name ns
                 = PU { appPickle   = const $ putAtt (mkName name) [XN.mkText ns]
                      , appUnPickle = getNSAtt ns
                      , theSchema   = scAttr name (scFixed ns)
                      }
xpIgnoreCont    :: LA XmlTree XmlTree -> PU ()
xpIgnoreCont    = xpIgnoreInput $ \ mf s -> s {contents   = mf $ contents   s}
xpIgnoreAttr    :: LA XmlTree XmlTree -> PU ()
xpIgnoreAttr    = xpIgnoreInput $ \ mf s -> s {attributes = mf $ attributes s}
xpFilterCont    :: LA XmlTree XmlTree -> PU a -> PU a
xpFilterCont f  = xpSeq' $ xpIgnoreCont f
xpFilterAttr    :: LA XmlTree XmlTree -> PU a -> PU a
xpFilterAttr f  = xpSeq' $ xpIgnoreAttr f
xpIgnoreInput   :: (([XmlTree] -> [XmlTree]) -> St -> St) -> LA XmlTree XmlTree -> PU ()
xpIgnoreInput m f
                =  PU { appPickle   = const id
                      , appUnPickle = do modify (m filterCont)
                                         return ()
                      , theSchema   = scNull
                      }
    where
      filterCont = runLA (unlistA >>> f)
class XmlPickler a where
    xpickle :: PU a
instance XmlPickler Int where
    xpickle = xpPrim
instance XmlPickler Integer where
    xpickle = xpPrim
instance XmlPickler () where
    xpickle = xpUnit
instance (XmlPickler a, XmlPickler b) => XmlPickler (a,b) where
    xpickle = xpPair xpickle xpickle
instance (XmlPickler a, XmlPickler b, XmlPickler c) => XmlPickler (a,b,c) where
    xpickle = xpTriple xpickle xpickle xpickle
instance (XmlPickler a, XmlPickler b, XmlPickler c, XmlPickler d) => XmlPickler (a,b,c,d) where
    xpickle = xp4Tuple xpickle xpickle xpickle xpickle
instance (XmlPickler a, XmlPickler b, XmlPickler c, XmlPickler d, XmlPickler e) => XmlPickler (a,b,c,d,e) where
    xpickle = xp5Tuple xpickle xpickle xpickle xpickle xpickle
instance XmlPickler a => XmlPickler [a] where
    xpickle = xpList xpickle
instance XmlPickler a => XmlPickler (Maybe a) where
    xpickle = xpOption xpickle