{-# LANGUAGE BangPatterns#-}
{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}

-- ----------------------------------------

module Main
where

import Text.XML.HXT.Core hiding (trace)
-- import Text.XML.HXT.TagSoup
-- import Text.XML.HXT.Expat

import Data.Char (isDigit)
import Data.List (foldl')

import Data.String.Unicode
    ( unicodeToXmlEntity
    )

import Control.Monad.State.Strict hiding (when)

import Control.DeepSeq
import Control.FlatSeq

import Data.Maybe

import System.IO  hiding (utf8)                      -- import the IO and commandline option stuff
import System.Environment

import qualified Text.XML.HXT.DOM.XmlNode as XN
import qualified Data.Tree.Class as T
import Data.Tree.NTree.TypeDefs -- as T

import           Debug.Trace

-- ------------------------------------------------------------

main    :: IO ()
main
    = do
      p <- getProgName
      (is : _) <- getArgs
      let i = (read is)::Int
      main' p i
    where
    main' p' = fromMaybe main0 . lookup (pn p') $ mpt
    mpt = [ ("GenDoc",     main1)
          , ("ReadDoc",    main2)
          , ("PruneRight", main3 False)
          , ("PruneLeft",  main3 True)
          , ("MemTest",    main4 True)
          , ("MemTest1",   main4 False)
         ]

-- ----------------------------------------

-- generate a document containing a binary tree of 2^i leafs (= 2^(i-1) XML elements)

main1   :: Int -> IO ()
main1 i
    = runX (genDoc i (fn i))
      >> return ()

-- ----------------------------------------

-- read a document containing a binary tree of 2^i leafs

main2   :: Int -> IO ()
main2 i
    = do
      [x] <- runX (setTraceLevel 2
                   >>>
                   readDoc (fn i)
                   >>>
{-
                   traceMsg 1 "start rnfA"
                   >>>
                   rnfA this
                   >>>
-}
                   traceMsg 1 "start unpickle"
                   >>>
                   unpickleTree
                   >>>
                   traceMsg 1 "start fold"
                   >>>
                   arr (foldT1 max)
                  )
      putStrLn ( "maximum value in tree is " ++ show x ++
                 ", expected value was " ++ show ((2::Int)^i)
               )

-- ----------------------------------------

-- test on lazyness, is the whole tree read or only the first child of every child node?

main3   :: Bool -> Int -> IO ()
main3 l i
    = do
      [t] <- runX ( readDoc (fn i)
                    >>>
                    fromLA (xshow ( getChildren
                                    >>>
                                    if l then pruneForkLeft else pruneForkRight
                                  )
                           )
                  )
      putStrLn ("pruned binary tree is : " ++ show t)

-- ----------------------------------------

main4   :: Bool -> Int -> IO ()
main4 wnf i
    = do
      [x] <- runX ( setTraceLevel 1
                    >>>
                    traceMsg 1 ("generate tree of depth " ++ show i)
                    >>>
                    ( if wnf
                      then fromLA (genTree'' 0 i)
                      else fromLA (rnfA $ genTree   0 i)
                    )
                    >>>
                    perform ( traceMsg 1 ("deep hasAttrValue")
                              >>>
                              deep (hasName "leaf" >>> hasAttrValue "value" (== "1"))
                              >>>
                              getAttrValue "value"
                              >>>
                              arrIO putStrLn
                            )
                    >>>
                    perform ( traceMsg 1 ("deep hasAttrValue")
                              >>>
                              deep (hasName "leaf" >>> hasAttrValue "value" (== "1"))
                              >>>
                              getAttrValue "value"
                              >>>
                              arrIO putStrLn
                            )
                    {-
                    perform (traceMsg 1 ("write doc")
                             >>>
                             putDoc "./tmp.xml"
                            )
                    >>>
                    traceMsg 1 ("compute maximum and minimum")
                    >>>
                    fromLA ( foldBTree maximum &&& foldBTree minimum )  -- 2 traversals: complete tree in mem
                    >>>
                    arr2 (\ ma mi -> "maximum value = " ++ show ma ++ ", minimum = " ++ show mi )
                    >>>
                    traceValue 1 id
                     -}
                    >>>
                    traceMsg 1 "done"
                    >>>
                    constA "0"
                  )
      putStrLn x

foldBTree       :: ([Int] -> Int) -> LA XmlTree Int
foldBTree f
    = choiceA
      [ hasName "leaf" :-> ( getAttrValue "value" >>^ read )
      , hasName "fork" :-> ( (getChildren >>> foldBTree f) >. f )
      , this           :-> none
      ]

-- ----------------------------------------

-- just to check how much memory is used for the tree

main0   :: Int -> IO ()
main0 i
    = do
      let t = mkBTree i
      let m = show . foldT1 max $ t
      putStrLn ("maximum value in tree is " ++ m ++ ", minimum value is " ++ show (foldT1 min t))
      return ()

-- ----------------------------------------

pn      :: String -> String
pn      = reverse . takeWhile (/= '/') . reverse

fn      :: Int -> String
fn      = ("tree-" ++) . (++ ".xml") . reverse . take 4 . reverse . ((replicate 4 '0') ++ ) . show

-- ----------------------------------------

genTree :: Int -> Int -> LA XmlTree XmlTree
genTree !n !d
    | d == 0    = aelem "leaf" [sattr "value" (show (n + 1))]
    | otherwise = selem "fork" [ genTree (2*n)   (d-1)
                               , genTree (2*n+1) (d-1)
                               ]

-- ----------------------------------------

genTree'        :: Int -> Int -> LA XmlTree XmlTree
genTree' !n !d
    | d == 0    = aelem' "leaf" [sattr' "value" (show (n + 1))]
    | otherwise = selem' "fork" [ genTree' (2*n)   (d-1)
                                , genTree' (2*n+1) (d-1)
                                ]

-- ----------------------------------------

genTree''       :: Int -> Int -> LA XmlTree XmlTree
genTree'' !n !d
    | d == 0    = rwnfA $
                  -- trace ("+leaf " ++ show (n+1)) $
                  aelem "leaf" $
                  -- trace ("++leaf " ++ show (n+1)) $
                  [rwnf2A $
                   -- trace ("+valu " ++ show (n+1)) $
                   sattr "value" $
                   show $
                   -- trace ("++valu " ++ show (n+1)) $
                   (n + 1)
                  ]
    | otherwise = rwnfA $
                  -- trace ("+fork " ++ show (n+1)) $
                  selem "fork" $
                  -- trace ("++fork " ++ show (n+1)) $
                  [ genTree'' (2*n)   (d-1)
                  , genTree'' (2*n+1) (d-1)
                  ]

-- ----------------------------------------

-- (hopefully) strict constructors

mkText' s               = rwnf s `seq`
                          T.mkLeaf tn
    where
    tn = XN.mkText s

mkAttr' n al            = n        `seq`
                          rwnf al `seq`
                          T.mkTree an al
    where
    an = XN.mkAttrNode n

mkElem' n al cl         = n        `seq`
                          en       `seq`
                          rwnf al `seq`
                          rwnf cl `seq`
                          T.mkTree en cl
    where
    en = XN.mkElementNode n al

-- ----------------------------------------
-- strict arrows

mkElement'              :: String -> LA n XmlTree -> LA n XmlTree -> LA n XmlTree
mkElement' n af cf      = (listA af &&& listA cf)
                          >>>
                          arr2 (mkElem' (mkName n))

selem'                  :: String -> [LA n XmlTree] -> LA n XmlTree
selem' n cfs            = mkElement' n none (catA cfs)

aelem'                  :: String -> [LA n XmlTree] -> LA n XmlTree
aelem' n afs            = mkElement' n (catA afs) none

sattr'                  :: String -> String -> LA n XmlTree
sattr' an av            = constA (mkAttr' (mkName an) [mkText' av])

-- ----------------------------------------

genDoc          :: Int -> String -> IOSArrow b XmlTree
genDoc d out    = constA (let t = mkBTree d in rnf t `seq` t)
                  >>>
                  xpickleVal xpickle
                  >>>
{-
                  strictA
                  >>>
                  perform (writeBinaryValue (out ++ ".bin"))
                  >>>
                  readBinaryValue (out ++ ".bin")
                  >>>
                  strictA
                  >>>
-}
                  putDoc out

-- ----------------------------------------

readDoc :: String -> IOSArrow b XmlTree
readDoc src
    = readDocument [ withParseHTML no
                   , withTrace 2
                   , withValidate no
                   , withInputEncoding isoLatin1
                   , withWarnings yes
                   , withStrictInput no
                   , withCanonicalize yes
                   , withRemoveWS no
                   -- , withExpat yes
                   -- , withTagSoup
                   ] src
{-
      >>>
      perform ( writeDocument [ withShowTree yes
                              , withOutputHTML
                              ] ""
              )
      >>>
      perform ( writeDocument [ withShowTree no
                              , withOutputHTML
                              ] ""
              )
-}
-- ----------------------------------------

unpickleTree    :: ArrowXml a => a XmlTree BTree
unpickleTree    = xunpickleVal xpickle

-- ----------------------------------------

pruneForkRight  :: LA XmlTree XmlTree
pruneForkRight
    = ( replaceChildren
        ( ( getChildren >>. take 1 )
          >>>
          pruneForkRight
        )
      ) `when` (hasName "fork")


pruneForkLeft   :: LA XmlTree XmlTree
pruneForkLeft
    = ( replaceChildren
        ( ( getChildren >>. drop 1 )
          >>>
          pruneForkLeft
        )
      ) `when` (hasName "fork")

-- ----------------------------------------

type Counter a  = State Int a

incr    :: Counter Int
incr    = do
          modify (+1)
          get

-- ----------------------------------------

data BTree      = Leaf Int
                | Fork BTree BTree
                  deriving (Show)

instance NFData BTree where
    rnf (Leaf i)	= rnf i
    rnf (Fork t1 t2)	= rnf t1 `seq` rnf t2

instance XmlPickler BTree where
    xpickle = xpAlt tag ps
        where
        tag (Leaf _     ) = 0
        tag (Fork _ _   ) = 1

        ps = [ xpWrap ( Leaf, \ (Leaf i) -> i)
               ( xpElem "leaf" $ xpAttr "value" $ xpInt ) -- xpWrap (const 0, const ">&\"äöü") $ xpText )
             , xpWrap ( uncurry Fork, \ (Fork l r) -> (l, r))
               ( xpElem "fork" $ xpPair xpickle xpickle )
             ]

-- ----------------------------------------

mkBTree         :: Int -> BTree
mkBTree depth   = evalState (mkT depth) 0
    where
    mkT :: Int -> Counter BTree
    mkT 0 = do
            i <- incr
            return (Leaf i)
    mkT n = do
            l <- mkT (n-1)
            r <- mkT (n-1)
            return (Fork l r)

bTreeToNTree	:: BTree -> NTree Int
bTreeToNTree (Leaf i)	= NTree i []
bTreeToNTree (Fork l r) = NTree j [l',r']
    where
    l' = bTreeToNTree l
    r' = bTreeToNTree r
    j  = T.getNode l' + T.getNode r'

mkNTree = bTreeToNTree . mkBTree

-- ----------------------------------------

foldT1  :: (Int -> Int -> Int) -> BTree -> Int
foldT1 _  (Leaf v)      = v
foldT1 op (Fork l r)    = foldT1 op l `op` foldT1 op r

-- ----------------------------------------

-- output is done with low level ops to write the
-- document i a lazy manner
-- adding an xml pi and encoding is done "by hand"
-- latin1 decoding is the identity, so please generate the
-- docs with latin1 encoding. Here ist done even with ASCCI
-- every none ASCII char is represented by a char ref (&nnn;)

putDoc  :: String -> IOStateArrow s XmlTree XmlTree
putDoc dst
    = writeDocument [ withOutputEncoding isoLatin1
                    , withOutputXML
                    ] dst

-- ----------------------------------------
{-

putDoc  :: String -> IOStateArrow s XmlTree XmlTree
putDoc dst
    = addXmlPi
      >>>
      addXmlPiEncoding isoLatin1
      >>>
      xshow getChildren
      >>>
      arr unicodeToXmlEntity
      >>>
      arrIO (\ s -> hPutDocument (\h -> hPutStrLn h s))
      >>>
      none
      where
      isStdout  = null dst || dst == "-"

      hPutDocument      :: (Handle -> IO()) -> IO()
      hPutDocument action
          | isStdout
              = action stdout
          | otherwise
              = do
                handle <- openBinaryFile dst WriteMode
                action handle
                hClose handle
-}
-- ----------------------------------------
