{-# Language MagicHash #-}

module ADP.Fusion.Core.Classes where

import           Data.Proxy
import           Data.Strict.Tuple
import           GHC.Exts hiding (build)
import qualified Data.Vector.Fusion.Stream.Monadic as S

import           Data.PrimitiveArray



data OutsideContext s
  = OStatic     s
  | ORightOf    s
  | OFirstLeft  s
  | OLeftOf     s
  deriving (Show)

data InsideContext s
  = IStatic   {iGetContext :: s}
  | IVariable {iGetContext :: s}
  deriving (Show)

data ComplementContext
  = Complemented
  deriving (Show)

-- | Needed for structures that have long-range interactions and "expand",
-- like sets around edge boundaries: @set <edge> set@. requires the sets to
-- be connected.

data ExtComplementContext s
  = CStatic s
  | CVariable s

class RuleContext i where
  type Context i :: *
  initialContext :: i -> Context i

-- | While we ostensibly use an index of type @i@ we typically do not need
-- every element of an @i@. For example, when looking at 'Subword's, we do
-- not need both element of @j:.k@ but only @k@.
-- Also, inside grammars do need fewer moving indices than outside
-- grammars.
--
-- TODO Sometimes, the actual RunningIndex ctors are not erased. This could
-- be due to <https://ghc.haskell.org/trac/ghc/ticket/2289>. To test, we
-- should transform RunningIndex into a type class to give us access to the
-- left and right member, also we should create instances a la
-- @RunningIndex (is :. Subword I) = RiSwI !(RunningIndex is) !Int@.
-- Hopefully, these are completely erased.

{-
class RunningIndexCl i where
  type RecursiveRl i :: *
  type ThisRI i :: *
-}

data family RunningIndex i :: *

data instance RunningIndex (is:.i) = !(RunningIndex is) :.: !(RunningIndex i)

data instance RunningIndex Z = RiZ

deriving instance Show (RunningIndex Z)


-- | During construction of the stream, we need to extract individual elements
-- from symbols in production rules. An element in a stream is fixed by both,
-- the type @x@ of the actual argument we want to grab (say individual
-- characters we parse from an input) and the type of indices @i@ we use.
--
-- @Elm@ data constructors are all eradicated during fusion and should never
-- show up in CORE.

class Element x i where
  data Elm    x i :: *
  type RecElm x i :: *
  type Arg    x   :: *
  getArg :: Elm x i -> Arg x
  getIdx :: Elm x i -> RunningIndex i
  getElm :: Elm x i -> RecElm x i

-- | @mkStream@ creates the actual stream of elements (@Elm@) that will be fed
-- to functions on the left of the @(<<<)@ operator. Streams work over all
-- monads and are specialized for each combination of arguments @x@ and indices
-- @i@.

class (Monad m) => MkStream m x i where
  mkStream :: x -> Context i -> i -> i -> S.Stream m (Elm x i)

-- | Finally, we need to be able to correctly build together symbols on the
-- right-hand side of the @(<<<)@ operator.
--
-- The default makes sure that the last (or only) argument left over is
-- correctly assigned a @Z@ to terminate the symbol stack.

class Build x where
  type Stack x :: *
  type Stack x = S :!: x
  build :: x -> Stack x
  default build :: (Stack x ~ (S :!: x)) => x -> Stack x
  build x = S :!: x
  {-# Inline build #-}

instance Build x => Build (x:!:y) where
  type Stack (x:!:y) = Stack x :!: y
  build (x:!:y) = build x :!: y
  {-# Inline build #-}

-- | Similar to 'Z', but terminates an argument stack.

data S = S
  deriving (Eq,Show)

instance
  (
  ) => Element S i where
  data Elm S i = ElmS !(RunningIndex i)
  type Arg S   = Z
  getArg (ElmS _) = Z
  getIdx (ElmS i) = i
  {-# Inline getArg #-}
  {-# Inline getIdx #-}

deriving instance (Show (RunningIndex ix)) => Show (Elm S ix)

-- | 'staticCheck' acts as a static filter. If 'b' is true, we keep all stream
-- elements. If 'b' is false, we discard all stream elements.

staticCheck :: Monad m => Bool -> S.Stream m a -> S.Stream m a
staticCheck b (S.Stream step t) = b `seq` S.Stream snew (CheckLeft b t) where
  {-# Inline [0] snew #-}
  snew (CheckLeft  False _) = return $ S.Done
  snew (CheckLeft  True  s) = return $ S.Skip (CheckRight s)
  snew (CheckRight s      ) = do r <- step s
                                 case r of
                                   S.Yield x s' -> return $ S.Yield x (CheckRight s')
                                   S.Skip    s' -> return $ S.Skip    (CheckRight s')
                                   S.Done       -> return $ S.Done
{-# INLINE staticCheck #-}

data StaticCheck a b = CheckLeft Bool a | CheckRight b

staticCheck# :: Monad m => Int# -> S.Stream m a -> S.Stream m a
staticCheck# !b (S.Stream step t) = S.Stream snew (SL t b) where
  {-# Inline [0] snew #-}
  snew (SL s k)
    | 1# <- k   = return $ S.Skip (SR s)
    | otherwise = return $ S.Done
  snew (SR s  ) = do r <- step s
                     case r of
                       S.Yield x s' -> return $ S.Yield x (SR s')
                       S.Skip    s' -> return $ S.Skip    (SR s')
                       S.Done       -> return $ S.Done
{-# Inline staticCheck# #-}


data SLR z = SL !z !Int# | SR !z

-- | Constrains the behaviour of the memoizing tables. They may be 'EmptyOk' if
-- @i==j@ is allowed (empty subwords or similar); or they may need 'NonEmpty'
-- indices, or finally they can be 'OnlyZero' (only @i==j@ allowed) which is
-- useful in multi-dimensional casese.

--data TableConstraint
--  = EmptyOk
--  | NonEmpty
--  | OnlyZero
--  deriving (Eq,Show)

data EmptyOk = EmptyOk

data NonEmpty = NonEmpty

class MinSize c where
  minSize :: c -> Int

instance MinSize EmptyOk where
  minSize EmptyOk = 0
  {-# Inline minSize #-}

instance MinSize NonEmpty where
  minSize NonEmpty = 1
  {-# Inline minSize #-}

{-
minSize :: TableConstraint -> Int
minSize NonEmpty = 1
minSize _        = 0
{-# Inline [0] minSize #-}
-}

-- |
--
-- TODO Rewrite to generalize easily over multi-dim cases.

class ModifyConstraint t where
  type TNE t :: *
  type TE  t :: *
  toNonEmpty :: t -> TNE t
  toEmpty    :: t -> TE  t

--
--instance ModifyConstraint EmptyOk
--  type TNE EmptyOk = NonEmpty
--  type TE  EmptyOk = 

-- |

--type family   TblConstraint x       :: *
--
--type instance TblConstraint (is:.i) =  TblConstraint is :. TblConstraint i
--type instance TblConstraint Z       = Z
--
---- TODO move into the sub-modules
--
--type instance TblConstraint (PointL  t) = TableConstraint
--type instance TblConstraint (PointR  t) = TableConstraint
--type instance TblConstraint (Subword t) = TableConstraint