{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}

module Text.HTML.TagSoup.Navigate.Types.TagTreePosState(
  TagTreePosStateT(..)
, TagTreePosState
, tagTreePosState
, runTagTreePosStateT
, evalTagTreePosStateT
, execTagTreePosStateT
, runTagTreePosState
, evalTagTreePosState
, execTagTreePosState
, modifyTagTreePosStateT
, modifyTagTreePosState
, getsTagTreePosStateT
, getsTagTreePosState
, maybeTagTreePosStateT
, maybeTagTreePosState
, putTagTreePosStateT
, putTagTreePosState
, getTagTreePos
, root
, parent
, firstChild
, lastChild
, prevSibling
, nextSibling
, content
, before
, after
, parents
, liftTagTreePosState
, putTagTree
, opticContent
, findContentUntil
, findUntil
, tagBranchLeafText
) where

import Control.Applicative(Applicative((<*>), pure), Alternative((<|>), empty), liftA2)
import Control.Category((.))
import Control.Lens(Rewrapped, Wrapped, Unwrapped, _Wrapped', _Wrapped, iso, view, preview, over, from, _head, _1, _2, _3)
import Control.Monad(Monad(return, (>>=)))
import Control.Monad.Morph(MFunctor(hoist))
import Control.Monad.Reader.Class(MonadReader(ask, local, reader))
import Control.Monad.State.Class(MonadState(state, get, put), gets, modify)
import Control.Monad.Trans.Class(MonadTrans(lift))
import Control.Monad.IO.Class(MonadIO(liftIO))
import Data.Bool(Bool, bool)
import Data.Functor(Functor(fmap))
import Data.Functor.Apply(Apply((<.>)))
import Data.Functor.Alt(Alt((<!>)))
import Data.Functor.Bind(Bind((>>-)))
import Data.Functor.Const(Const)
import Data.Functor.Identity(Identity(Identity, runIdentity))
import Data.Maybe(Maybe(Nothing, Just))
import Data.Semigroup(Semigroup((<>)))
import Data.Monoid(Monoid(mempty, mappend), First)
import Text.HTML.TagSoup.Navigate.Types.Tag(_TagText)
import Text.HTML.TagSoup.Navigate.Types.TagTree(TagTree, _TagLeaf, _TagBranch, _TagBranch_)
import Text.HTML.TagSoup.Navigate.Types.TagTreePos(TagTreePos, tagsoupTagTreePos, tagTreePosContent, tagTreePosBefore, tagTreePosAfter, tagTreePosParents, fromTagTree)
import Text.HTML.TagSoup.Navigate.Types.TagTreePosParent(TagTreePosParent)
import qualified Text.HTML.TagSoup.Tree.Zipper as TagSoup(prevSibling, nextSibling, parent, firstChild, lastChild, root)

newtype TagTreePosStateT str f a =
  TagTreePosStateT
    (TagTreePos str -> f (Maybe (TagTreePos str, a)))

type TagTreePosState str a =
  TagTreePosStateT str Identity a

tagTreePosState ::
  (TagTreePos str -> Maybe (TagTreePos str, a))
  -> TagTreePosState str a
tagTreePosState k =
  TagTreePosStateT (Identity . k)

instance TagTreePosStateT str f a ~ t =>
  Rewrapped (TagTreePosStateT str f' a') t

instance Wrapped (TagTreePosStateT str f a) where
  type Unwrapped (TagTreePosStateT str f a) =
    TagTreePos str -> f (Maybe (TagTreePos str, a))
  _Wrapped' =
    iso (\(TagTreePosStateT x) -> x) TagTreePosStateT

instance Functor f => Functor (TagTreePosStateT str f) where
  fmap f (TagTreePosStateT x) =
    TagTreePosStateT (fmap (fmap (fmap (fmap f))) x)

instance Monad f => Apply (TagTreePosStateT str f) where
  TagTreePosStateT f <.> TagTreePosStateT a =
    TagTreePosStateT (\p ->
     f p >>= \case
      Nothing ->
        pure Nothing
      Just (s, f') ->
        fmap (fmap (fmap f')) (a s)
    )

instance Monad f => Applicative (TagTreePosStateT str f) where
  pure a =
    TagTreePosStateT (\p -> pure (pure (p, a)))
  (<*>) =
    (<.>)

instance Monad f => Alt (TagTreePosStateT str f) where
  TagTreePosStateT x <!> TagTreePosStateT y =
    TagTreePosStateT (\p ->
      x p >>= \case
        Nothing ->
          y p
        Just (s, a) ->
          pure (Just (s, a))
    )

instance Monad f => Alternative (TagTreePosStateT str f) where
  (<|>) =
    (<!>)
  empty =
    TagTreePosStateT (pure (pure Nothing))

instance Monad f => Bind (TagTreePosStateT str f) where
  TagTreePosStateT x >>- f =
    TagTreePosStateT (\p ->
      x p >>= \case
        Nothing ->
          pure Nothing
        Just (s, a) ->
          view _Wrapped (f a) s
    )

instance Monad f => Monad (TagTreePosStateT str f) where
  return =
    pure
  (>>=) =
    (>>-)

instance MonadTrans (TagTreePosStateT str) where
  lift x =
    TagTreePosStateT (\p -> fmap (\a -> pure (p, a)) x)

instance MonadIO f => MonadIO (TagTreePosStateT str f) where
  liftIO x =
    TagTreePosStateT (\p -> liftIO (fmap (\a -> pure (p, a)) x))

instance Monad f => MonadState (TagTreePos str) (TagTreePosStateT str f) where
  state k =
    TagTreePosStateT (\p -> let (a, q) = k p in pure (pure (q, a)))
  get =
    TagTreePosStateT (\p -> pure (pure (p, p)))
  put p =
    TagTreePosStateT (pure (pure (pure (p, ()))))

instance Monad f => MonadReader (TagTreePos str) (TagTreePosStateT str f) where
  ask =
    get
  local k (TagTreePosStateT x) =
    TagTreePosStateT (fmap (fmap (over _1 k)) . x)
  reader k =
    TagTreePosStateT (\p -> pure (pure (p, k p)))

instance MFunctor (TagTreePosStateT str) where
  hoist k (TagTreePosStateT x) =
    TagTreePosStateT (k . x)

instance (Monad f, Semigroup a) => Semigroup (TagTreePosStateT str f a) where
  (<>) =
    liftA2 (<>)

instance (Monad f, Monoid a) => Monoid (TagTreePosStateT str f a) where
  mappend =
    liftA2 mappend
  mempty =
    pure mempty

runTagTreePosStateT ::
  TagTreePosStateT str f a
  -> TagTreePos str
  -> f (Maybe (TagTreePos str, a))
runTagTreePosStateT =
  view _Wrapped

evalTagTreePosStateT ::
  Functor f =>
  TagTreePosStateT str f a
  -> TagTreePos str
  -> f (Maybe a)
evalTagTreePosStateT s p =
  fmap (fmap (view _2)) (runTagTreePosStateT s p)

execTagTreePosStateT ::
  Functor f =>
  TagTreePosStateT str f a
  -> TagTreePos str
  -> f (Maybe (TagTreePos str))
execTagTreePosStateT s p =
  fmap (fmap (view _1)) (runTagTreePosStateT s p)

runTagTreePosState ::
  TagTreePosState str a
  -> TagTreePos str
  -> Maybe (TagTreePos str, a)
runTagTreePosState s p =
  runIdentity (runTagTreePosStateT s p)

evalTagTreePosState ::
  TagTreePosState str a
  -> TagTreePos str
  -> Maybe a
evalTagTreePosState s p =
  fmap (view _2) (runTagTreePosState s p)

execTagTreePosState ::
  TagTreePosState str a
  -> TagTreePos str
  -> Maybe (TagTreePos str)
execTagTreePosState s p =
  fmap (view _1) (runTagTreePosState s p)

modifyTagTreePosStateT ::
  Functor f =>
  (TagTreePos x -> f (Maybe (TagTreePos x)))
  -> TagTreePosStateT x f ()
modifyTagTreePosStateT k =
  TagTreePosStateT (fmap (fmap (\q -> (q, ()))) . k)

modifyTagTreePosState ::
  Applicative f =>
  (TagTreePos x -> Maybe (TagTreePos x))
  -> TagTreePosStateT x f ()
modifyTagTreePosState k =
  modifyTagTreePosStateT (pure . k)

getsTagTreePosStateT ::
  Functor f =>
  (TagTreePos x -> f (Maybe a))
  -> TagTreePosStateT x f a
getsTagTreePosStateT k =
  TagTreePosStateT (\p -> fmap (fmap (\a -> (p, a))) (k p))

getsTagTreePosState ::
  Applicative f =>
  (TagTreePos x -> Maybe a)
  -> TagTreePosStateT x f a
getsTagTreePosState k =
  getsTagTreePosStateT (pure . k)

maybeTagTreePosStateT ::
  Functor f =>
  f (Maybe a)
  -> TagTreePosStateT x f a
maybeTagTreePosStateT =
  getsTagTreePosStateT . pure

maybeTagTreePosState ::
  Applicative f =>
  Maybe a
  -> TagTreePosStateT x f a
maybeTagTreePosState =
  getsTagTreePosState . pure

putTagTreePosStateT ::
  Functor f =>
  f (Maybe (TagTreePos x))
  -> TagTreePosStateT x f ()
putTagTreePosStateT x =
  TagTreePosStateT (pure (fmap (fmap (\p -> (p, ()))) x))

putTagTreePosState ::
  Applicative f =>
  Maybe (TagTreePos x)
  -> TagTreePosStateT x f ()
putTagTreePosState =
  putTagTreePosStateT . pure

getTagTreePos ::
  Applicative f =>
  TagTreePosStateT x f (TagTree x)
getTagTreePos =
  liftTagTreePosState getTagTreePos

root ::
  Monad f =>
  TagTreePosStateT str f ()
root =
  modify (view (from tagsoupTagTreePos) . TagSoup.root . view tagsoupTagTreePos)

parent ::
  Monad f =>
  TagTreePosStateT str f ()
parent =
  modifyTagTreePosState (fmap (view (from tagsoupTagTreePos)) . TagSoup.parent . view tagsoupTagTreePos)

firstChild ::
  Monad f =>
  TagTreePosStateT str f ()
firstChild =
  modifyTagTreePosState (fmap (view (from tagsoupTagTreePos)) . TagSoup.firstChild . view tagsoupTagTreePos)

lastChild ::
  Monad f =>
  TagTreePosStateT str f ()
lastChild =
  modifyTagTreePosState (fmap (view (from tagsoupTagTreePos)) . TagSoup.lastChild . view tagsoupTagTreePos)

prevSibling ::
  Monad f =>
  TagTreePosStateT str f ()
prevSibling =
  modifyTagTreePosState (fmap (view (from tagsoupTagTreePos)) . TagSoup.prevSibling . view tagsoupTagTreePos)

nextSibling ::
  Monad f =>
  TagTreePosStateT str f ()
nextSibling =
  modifyTagTreePosState (fmap (view (from tagsoupTagTreePos)) . TagSoup.nextSibling . view tagsoupTagTreePos)

content ::
  Monad f =>
  TagTreePosStateT x f (TagTree x)
content =
  gets (view tagTreePosContent)

before ::
  Monad f =>
  TagTreePosStateT x f [TagTree x]
before =
  gets (view tagTreePosBefore)

after ::
  Monad f =>
  TagTreePosStateT x f [TagTree x]
after =
  gets (view tagTreePosAfter)

parents ::
  Monad f =>
  TagTreePosStateT x f [TagTreePosParent x]
parents =
  gets (view tagTreePosParents)

liftTagTreePosState ::
  Applicative f =>
  TagTreePosState str a
  -> TagTreePosStateT str f a
liftTagTreePosState (TagTreePosStateT x) =
  TagTreePosStateT (pure . runIdentity . x)

putTagTree ::
  Monad f =>
  TagTree str
  -> TagTreePosStateT str f ()
putTagTree =
  put . fromTagTree

opticContent ::
  ((a -> Const (First a) a) -> TagTree str -> Const (First a) (TagTree str))
  -> TagTreePosState str a
opticContent k =
  getsTagTreePosState
    (preview (tagTreePosContent . k))

findContentUntil ::
  Monad f =>
  TagTreePosStateT str f a
  -> (TagTree str -> Bool)
  -> TagTreePosStateT str f ()
findContentUntil oper pr =
  findUntil oper (reader (pr . view tagTreePosContent) >>= bool empty (pure ()))

findUntil ::
  Monad f =>
  TagTreePosStateT str f a
  -> TagTreePosStateT str f b
  -> TagTreePosStateT str f b
findUntil oper pr =
  do  _ <- oper
      pr <|> findUntil oper pr

-- |
--
-- produces `(x, t)` in `TagBranch x _ [TagLeaf (TagText t)]`
tagBranchLeafText ::
  TagTreePosState a (a, a)
tagBranchLeafText =
  do  x <- opticContent _TagBranch_
      t <- opticContent (_TagBranch . _3 . _head . _TagLeaf . _TagText)
      pure (x, t)