module Text.XML.WraXML.Element where

import qualified Text.XML.Basic.Tag as Tag
import qualified Text.XML.Basic.Attribute as Attr
import qualified Text.XML.Basic.Name as Name

import qualified Data.Accessor.Basic as Accessor

import           Data.Foldable as Foldable(Foldable(foldMap))
import           Data.Traversable as Traversable(Traversable(traverse))
import           Control.Applicative (Applicative, )
import qualified Control.Applicative as App
import           Data.Monoid (mconcat, )

import qualified Text.XML.Basic.Format as Format
import qualified Text.Show as Show


data T name str =
   Cons {
      name_       :: Tag.Name name,
      attributes_ :: [Attr.T name str]
   } deriving (Show)

type Filter name str = T name str -> T name str


cons ::
   (Name.Tag name, Name.Attribute name) =>
   Tag.Name name -> [Attr.T name str] -> T name str
cons = Cons

name :: Accessor.T (T name str) (Tag.Name name)
name = Accessor.fromSetGet (\n p -> p{name_ = n}) name_

attributes :: Accessor.T (T name str) [Attr.T name str]
attributes = Accessor.fromSetGet (\n p -> p{attributes_ = n}) attributes_


-- * tests

checkName :: (Tag.Name name -> Bool) -> (T name str -> Bool)
checkName p (Cons tagName _) = p tagName

checkAttributes :: ([Attr.T name str] -> Bool) -> (T name str -> Bool)
checkAttributes p (Cons _ attrs) = p attrs


-- * modification

instance Functor (T name) where
   fmap f (Cons tagName attrs) =
      Cons tagName (Attr.mapValues f attrs)


lift ::
   (Tag.Name name -> [Attr.T name str] -> (Tag.Name name, [Attr.T name str])) ->
   (Filter name str)
lift f (Cons tagName attrs) =
   uncurry Cons (f tagName attrs)


-- | process the attribute list of a specific tag
processAttrs ::
   (Name.Tag name, Name.Attribute name) =>
   (Tag.Name name -> Bool) ->
   ([Attr.T name str] -> [Attr.T name str]) ->
   (Filter name str)
processAttrs p f =
   lift (\tagName ->
      (,) tagName . if p tagName then f else id)

processAttrValue ::
   (Name.Tag name, Name.Attribute name) =>
   (Tag.Name name, Attr.Name name) ->
   (str -> str) ->
   (Filter name str)
processAttrValue (tagName,attrName) f =
   processAttrs (tagName ==)
      (Attr.adjustOn (attrName ==) f)

processAttrValueCond ::
   (Name.Tag name, Name.Attribute name) =>
   (Tag.Name name, Attr.Name name) ->
   ([Attr.T name str] -> Bool) ->
   (str -> str) ->
   (Filter name str)
processAttrValueCond (tagName,attrName) cond f =
   processAttrs (tagName ==)
      (\attrs -> Attr.adjustOn (attrName ==)
         (if cond attrs then f else id)
         attrs)



instance Foldable (T name) where
   foldMap f (Cons _tagName attrs) =
      mconcat $ map (foldMap f) attrs

instance Traversable (T name) where
   traverse f (Cons tagName attrs) =
      App.liftA (Cons tagName) (Attr.mapValuesA f attrs)



-- * monadic modification

-- | process the attribute list of a specific tag
processAttrsA ::
   (Name.Tag name, Name.Attribute name, Applicative m) =>
   (Tag.Name name -> Bool) ->
   ([Attr.T name str] -> m [Attr.T name str]) ->
   (T name str -> m (T name str))
processAttrsA p f =
   liftA (\tagName ->
      App.liftA ((,) tagName) .
      if p tagName then f else App.pure)

processAttrValueA ::
   (Name.Tag name, Name.Attribute name, Applicative m) =>
   (Tag.Name name, Attr.Name name) ->
   (str -> m str) ->
   (T name str -> m (T name str))
processAttrValueA (tagName,attrName) f =
   processAttrsA (tagName==)
      (Attr.adjustOnA (attrName==) f)

processAttrValueCondA ::
   (Name.Tag name, Name.Attribute name, Applicative m) =>
   (Tag.Name name, Attr.Name name) ->
   ([Attr.T name str] -> Bool) ->
   (str -> m str) ->
   (T name str -> m (T name str))
processAttrValueCondA (tagName,attrName) cond f =
   processAttrsA (tagName==)
      (\attrs -> Attr.adjustOnA (attrName==)
         (if cond attrs then f else App.pure)
         attrs)

liftA ::
   (Name.Tag name, Name.Attribute name, Applicative m) =>
   (Tag.Name name -> [Attr.T name str] ->
      m (Tag.Name name, [Attr.T name str])) ->
   (T name str -> m (T name str))
liftA f (Cons tagName attrs) =
   App.liftA (uncurry Cons) (f tagName attrs)



-- * format

format ::
   (Name.Tag name, Name.Attribute name, Format.C string) =>
   (Tag.Name name -> Bool) -> ShowS -> T name string -> [ShowS] -> ShowS
format isCondensed trailingSlash (Cons tagName attrs) formatSubTrees =
   let t = Name.toString tagName
   in  if isCondensed tagName
         then Format.angle
               (showString t . Attr.formatListBlankHead attrs
                 . trailingSlash)
         else Format.angle
               (showString t . Attr.formatListBlankHead attrs)
               . foldr (.) id formatSubTrees
               . Format.angle (Format.slash . showString t)