module Data.Apart.Structures.Tree.Prefix
        (Prefix, Labeled (..), singleton, seek, insert, crumbs) where

import "base" Control.Applicative (Alternative (..))
import "base" Control.Arrow ((&&&))
import "base" Data.Foldable (find)
import "base" Data.Function ((&))
import "base" Data.Maybe (isJust)
import "base" Data.Monoid (Monoid (..), (<>))
import "comonad" Control.Comonad (Comonad (..))
import "contravariant" Data.Functor.Contravariant (Predicate (..))
import "contravariant" Data.Functor.Contravariant.Divisible (Divisible (..))
import "free" Control.Comonad.Cofree (Cofree (..), unwrap)
import "lens" Control.Lens (Lens', (^.), (%~))

import Data.Apart.Structures.Stack (Stack)

type Prefix s t = Cofree (Labeled s t)

data Labeled s t a = Hop s (t a) deriving Show

nodes :: Lens' (Prefix s t a) (t (Prefix s t a))
nodes f (x :< Hop s ns) = (\new -> x :< Hop s new) <$> f ns

instance Functor t => Functor (Labeled s t) where
        fmap f (Hop s as) = Hop s $ f <$> as

instance Foldable t => Foldable (Labeled s t) where
        foldr f acc (Hop s as) = foldr f acc as

instance Traversable t => Traversable (Labeled s t) where
        traverse f (Hop s as) = Hop s <$> traverse f as

singleton :: Alternative t => s -> a -> Prefix s t a
singleton s v = v :< Hop s empty

-- | Prefix tree haven't nodes
deadend :: Foldable t => Predicate (Prefix s t a)
deadend = Predicate $ \(_ :< Hop _ ns) -> length ns == 0

-- | Key and current key of root matched
progress :: (Eq s, Foldable t) => Predicate (s, Prefix s t a)
progress = Predicate $ \(s, _ :< Hop s' ns) -> s == s'

-- | Keys matched and this is the end
exactly :: (Eq s, Foldable t) => Predicate (s, Prefix s t a)
exactly = divide (snd &&& id) deadend progress

seek :: (Functor t, Foldable t, Eq s)
        => Stack s -> Prefix s t v -> Maybe v
seek (s :< Just ss) prefix@(getPredicate progress . (s,) -> True) =
        (<$>) extract $ find (isJust . seek ss) $ unwrap prefix
seek (s :< Nothing) prefix@(getPredicate progress . (s,) -> True) = Just $ extract prefix
seek (s :< _) prefix@(getPredicate progress . (s,) -> False) = Nothing

-- | You can insert value with @path + 1 symbol@ of existing @path@ in tree.
insert :: (Foldable t, Alternative t, Eq s)
        => Stack s -> v -> Prefix s t v -> Prefix s t v
insert (s :< _) x prefix@(getPredicate progress . (s,) -> False) = prefix
insert (s :< Nothing) x prefix@(getPredicate progress . (s,) -> True) = x :< unwrap prefix
insert (s :< Just ss@(s' :< Just _)) x prefix@(getPredicate progress . (,) s -> True) =
        prefix & nodes %~ (<$>) (insert ss x)
insert (s :< Just ss@(s' :< Nothing)) x prefix@(getPredicate progress . (,) s -> True) =
        prefix & nodes %~ (<|>) (pure $ x :< Hop s' empty)
insert _ _ prefix = prefix

-- | Unlike @insert@, you can specify longest path, but a gap will be filled Monoid's empty values
crumbs :: (Foldable t, Alternative t, Eq s, Monoid v)
        => Stack s -> v -> Prefix s t v -> Prefix s t v
crumbs (s :< _) x prefix@(getPredicate progress . (s,) -> False) = prefix
crumbs (s :< Just ss) x prefix@(getPredicate exactly . (,) s -> True) =
        (extract prefix) :< Hop s (pure $ crumbs ss x $ mempty :< Hop (extract ss) empty)
crumbs (s :< Nothing) x prefix@(getPredicate exactly . (,) s -> True) = x :< Hop s empty
crumbs (s :< Just ss@(s' :< Just _)) x prefix@(getPredicate progress . (,) s -> True) =
        prefix & nodes %~ (<$>) (crumbs ss x)
crumbs (s :< Just (s' :< Nothing)) x prefix@(getPredicate progress . (,) s -> True) =
        prefix & nodes %~ (<|>) (pure $ x :< Hop s' empty)
crumbs _ _ prefix = prefix