module ADP.Fusion.SynVar.Indices.Set1 where

import Control.Exception (assert)
import Data.Proxy
import Data.Vector.Fusion.Stream.Monadic (map,Stream,head,mapM,Step(..))
import Data.Vector.Fusion.Util (delay_inline)
import Debug.Trace
import Prelude hiding (map,head,mapM)
import Data.Bits.Extras
import Data.Bits

import ADP.Fusion.Core
import ADP.Fusion.Core.Unit
import Data.Bits.Ordered
import Data.PrimitiveArray hiding (map)

import ADP.Fusion.Core.Boundary
import ADP.Fusion.Core.EdgeBoundary
import ADP.Fusion.Core.Set1



-- | Since there is only one boundary, it doesn't matter if @k==First@ or
-- @k==Last@. As a result, the "name" of the boundary is kept variable.
--
-- Given the outer (set,bnd) system, we try all boundaries α for
-- (set-bnd,α) for the smaller set @Y@ in @X -> Y e@.
--
-- TODO After this case we should only allow @S@, since we write, in
-- essence, left-linear grammars here.
--
-- TODO we should try to statically assure that @rb==0@ holds always in
-- this case. It should because every other symbol moves to @IVariable@
-- once the number of of reserved bits is @>0@.
--
-- TODO kind-of hacked and should be written in a better way

instance
  ( IndexHdr s x0 i0 us (BS1 k I) cs c is (BS1 k I)
  ) => AddIndexDense s (us:.BS1 k I) (cs:.c) (is:.BS1 k I) where
  -- This rule should only be active if we have @X -> Y@ rules. Neither @X
  -- -> Y Z@ nor @X -> e Y@ are possible in a left-linear grammar.
  addIndexDenseGo (cs:.c) (vs:.IStatic rb) (lbs:._) (ubs:._) (us:.BS1 uSet uBnd) (is:.BS1 set bnd)
    = map (\(SvS s t y') ->
        let RiBs1I (BS1 cset (Boundary to)) = getIndex (getIdx s) (Proxy :: PRI is (BS1 k I))
        in  assert (cset == 0 && to == (-1)) $ SvS s (t:.BS1 set bnd) (y' :.: RiBs1I (BS1 set bnd)))
    . addIndexDenseGo cs vs lbs ubs us is
    . assert (rb==0)
  -- Deal with @X -> Y e@ type rules.
  addIndexDenseGo (cs:.c) (vs:.IVariable rb) (lbs:._) (ubs:._) (us:.BS1 uSet uBnd) (is:.BS1 set bnd)
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is . assert (rb==1) -- only works with one element
          -- Extract how many bits are already set -- should be zero (!) --
          -- and prepare the list of all possibilities.
          -- In addition, at least one bit should be reserved, hence
          -- @rb>0@. The boundary bit is one of those reserved and
          -- constitutes a mask to be used further down.
    where mk (SvS s t y') =
            let
            in  
#if ADPFUSION_DEBUGOUTPUT
                traceShow (set,bnd,rb) $
#endif
                return (SvS s t y', Just $ set `clearBit` getBoundary bnd)
          step (_, Nothing) = return Done
          step (SvS s t y', Just 0 ) = return $ Yield (SvS s (t:.BS1 0 0) (y':.:RiBs1I (BS1 0 0)))
                                                      (SvS s t y', Nothing)
          step (SvS s t y', Just bits) =
            let nbnd = lsbZ bits
                nset = set `clearBit` getBoundary bnd
                bs1  = BS1 nset (Boundary nbnd)
            in  -- traceShow (Boundary nbnd == bnd,bs1) $
                return $ Yield (SvS s (t:.bs1) (y':.:RiBs1I bs1))
                               (SvS s t y', Just $ bits `clearBit` nbnd)
          {-# Inline [0] mk       #-}
          {-# Inline [0] step     #-}
  {-# Inline addIndexDenseGo #-}

-- | For the inside case, we try all possible boundaries for @Y@ in @X ->
-- Y e@. For the outside case we now have: @Y -> X e@ where @Y@ is now
-- extended. @(yset,ybnd) -> (yset + α,α)@ for all @α@ that are not in
-- @yset@.
--
-- TODO 17.2.2017 added

instance
  ( IndexHdr s x0 i0 us (BS1 k O) cs c is (BS1 k O)
  ) => AddIndexDense s (us:.BS1 k O) (cs:.c) (is:.BS1 k O) where
  addIndexDenseGo (cs:.c) (vs:.ORightOf rb) (lbs:._) (ubs:._) (us:.BS1 uSet uBnd) (is:.BS1 cSet cBnd)
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is . assert (rb==1) -- only works with one element to the right
          -- Extract how many bits are already set -- should be zero (!) --
          -- and prepare the list of all possibilities.
          -- In addition, at least one bit should be reserved, hence
          -- @rb>0@. The boundary bit is one of those reserved and
          -- constitutes a mask to be used further down.
    where mk (SvS s t y') =
            let possible = uSet .&. complement cSet
            in
#if ADPFUSION_DEBUGOUTPUT
                traceShow ("aIDG/BS1/O/mk",(BS1 uSet uBnd), (BS1 cSet cBnd), possible) $
#endif
                return (SvS s t y', possible)
          -- in this case, the current set does not yield something to
          -- "make smaller".
          step (_, k) | popCount uSet == popCount cSet = return Done
          -- exhausted all options
          step (_, 0) = return Done
          step (SvS s t y', bits) =
            let nbnd = lsbZ bits
                nset = cSet `setBit` nbnd
                bs1  = BS1 nset (Boundary nbnd)
            in
#if ADPFUSION_DEBUGOUTPUT
                traceShow ("aIDG/BS1/O/step",(BS1 nset (Boundary nbnd))) $
#endif
                return $ Yield (SvS s (t:.bs1) (y':.:RiBs1O bs1))
                               (SvS s t y', bits `clearBit` nbnd)
          {-# Inline [0] mk       #-}
          {-# Inline [0] step     #-}
  {-# Inline addIndexDenseGo #-}


-- | 

instance
  ( IndexHdr s x0 i0 us (BS1 k I) cs c is (BS1 k O)
  ) => AddIndexDense s (us:.BS1 k I) (cs:.c) (is:.BS1 k O) where

-- | A @Unit@ index expands to the full set with all possible boundaries
-- tried in order.

instance
  ( IndexHdr s x0 i0 us (BS1 k I) cs c is (Unit I)
  ) => AddIndexDense s (us:.BS1 k I) (cs:.c) (is:.Unit I) where
  addIndexDenseGo (cs:.c) (vs:.IStatic ()) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:._) -- unit has only one index value
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is
    where mk (SvS s t y') = return $ (SvS s t y', fullSet)
          -- no more active bits
          step (_, 0) = return Done
          step (SvS s t y', bits)
            | b <- lsb bits = return $ Yield (SvS s (t:.BS1 fullSet (Boundary b)) (y':.:RiU))
                                             (SvS s t y', bits `clearBit` b)
          {-# Inline [0] mk   #-}
          {-# Inline [0] step #-}
  {-# Inline addIndexDenseGo #-}

-- | A single @Boundary@ index allows us to get the optimal results ending
-- on each individual boundary.

instance
  ( IndexHdr s x0 i0 us (BS1 k I) cs c is (Boundary k I)
  ) => AddIndexDense s (us:.BS1 k I) (cs:.c) (is:.Boundary k I) where
  addIndexDenseGo (cs:.c) (vs:.IStatic ()) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.i)
    = map (\(SvS s t y') -> SvS s (t:.BS1 fullSet i) (y':.:RiBI i))
    . addIndexDenseGo cs vs lbs ubs us is
  {-# Inline addIndexDenseGo #-}

-- | Given indices that index _only_ the current edge @First -> Last@, we
-- want to go over all possible set combinations.
--
-- The @to@ element from an edge boundary will serve as the @First@ element
-- in a rule
-- @X -> Last (from :-> to) First

instance
  ( IndexHdr s x0 i0 us (BS1 First I) cs c is (EdgeBoundary I)
  ) => AddIndexDense s (us:.BS1 First I) (cs:.c) (is:.EdgeBoundary I) where
  addIndexDenseGo (cs:.c) (vs:.IStatic k) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.(from :-> to))
    = map (\(SvS s t y') ->
        let RiEBI usedSet (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary I))
            hereBits = fullSet .&. complement usedSet
            hereSet  = hereBits `setBit` to
        in  SvS s (t:.BS1 hereSet (Boundary to)) (y':.:RiEBI fullSet (from :-> to)))
    . addIndexDenseGo cs vs lbs ubs us is
  {-# Inline addIndexDenseGo #-}

-- | Generate all possible bitsets until 'fullSet' is reached. @from@ is
-- our @Last@, and @to@ may not be set.

instance
  ( IndexHdr s x0 i0 us (BS1 Last I) cs c is (EdgeBoundary I)
  ) => AddIndexDense s (us:.BS1 Last I) (cs:.c) (is:.EdgeBoundary I) where
  addIndexDenseGo (cs:.c) (vs:.IVariable rb) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.(from :-> to))
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is
    where mk (SvS s t y') =
            let RiEBI usedBits (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary I))
            in  assert (usedBits == 0) . return $ (SvS s t y', Just (zeroBits :: BitSet I))
          step (_, (Nothing :: Maybe (BitSet I))) = return $ Done
          step (SvS s t y', Just cbits)
            | popCount cbits > maxCount = return $ Done
            | otherwise =
                let sbits = popShiftL shiftMask cbits
                    cset  = sbits `setBit` from
                in  return $ Yield (SvS s (t:.BS1 cset (Boundary from)) (y':.:RiEBI cset (from :-> to)))
                                   (SvS s t y', setSucc zeroBits (BitSet $ 2^maxCount-1) cbits)
          !maxCount = popCount fullSet - rb - 1 -- remove one for @from@, the @to@ bit should be in @rb@
          !shiftMask = fullSet `clearBit` from `clearBit` to
          {-# Inline [0] mk   #-}
          {-# Inline [0] step #-}
  {-# Inline addIndexDenseGo #-}

-- | TODO 17.2.2017 added

instance
  ( IndexHdr s x0 i0 us (BS1 First I) cs c is (EdgeBoundary C)
  ) => AddIndexDense s (us:.BS1 First I) (cs:.c) (is:.EdgeBoundary C) where
  addIndexDenseGo (cs:.c) (vs:.CStatic()) (lbs:._) (ubs:.BS1 (BitSet fullSet) _) (us:._) (is:.(from :-> to))
    = map (\(SvS s t y') ->
        let RiEBC (BitSet usedSet) (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            hereBits = fullSet .&. complement usedSet
            hereSet  = hereBits `setBit` to
        in  SvS s (t:.BS1 (BitSet hereSet) (Boundary to)) (y':.:RiEBC (BitSet fullSet) (from :-> to)))
    . addIndexDenseGo cs vs lbs ubs us is
  addIndexDenseGo (cs:.c) (vs:.CVariable ()) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.(from :-> to))
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is
    where mk (SvS s t y') =
            let RiEBC usedBits (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            in  assert (usedBits == 0) . return $ (SvS s t y', Just (zeroBits :: BitSet I))
          step (_, Nothing) = return $ Done
          step (SvS s t y', Just cbits)
            | popCount cbits > maxCount = return $ Done
            | otherwise =
                let sbits = popShiftL shiftMask cbits
                    cset  = getBitSet $ sbits `setBit` from
                in  return $ Yield (SvS s (t:.BS1 (BitSet cset) (Boundary from)) (y':.:RiEBC (BitSet cset) (from :-> to)))
                                   (SvS s t y', setSucc zeroBits (BitSet $ 2^maxCount-1) cbits)
          !maxCount = popCount fullSet - 1 -- remove one for @from@, the @to@ bit should be in @rb@
          !shiftMask = fullSet `clearBit` from `clearBit` to
          {-# Inline [0] mk   #-}
          {-# Inline [0] step #-}
  {-# Inline addIndexDenseGo #-}

-- | TODO 17.2.2017 added

instance
  ( IndexHdr s x0 i0 us (BS1 First O) cs c is (EdgeBoundary C)
  ) => AddIndexDense s (us:.BS1 First O) (cs:.c) (is:.EdgeBoundary C) where
  addIndexDenseGo (cs:.c) (vs:.CStatic()) (lbs:._) (ubs:.BS1 (BitSet fullSet) _) (us:._) (is:.(from :-> to))
    = map (\(SvS s t y') ->
        let RiEBC (BitSet usedSet) (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            hereBits = fullSet .&. complement usedSet
            hereSet  = hereBits `setBit` to
        in  SvS s (t:.BS1 (BitSet hereSet) (Boundary to)) (y':.:RiEBC (BitSet fullSet) (from :-> to)))
    . addIndexDenseGo cs vs lbs ubs us is
  addIndexDenseGo (cs:.c) (vs:.CVariable ()) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.(from :-> to))
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is
    where mk (SvS s t y') =
            let RiEBC usedBits (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            in  assert (usedBits == 0) . return $ (SvS s t y', Just (zeroBits :: BitSet O))
          step (_, Nothing) = return $ Done
          step (SvS s t y', Just cbits)
            | popCount cbits > maxCount = return $ Done
            | otherwise =
                let sbits = popShiftL shiftMask cbits
                    cset  = getBitSet $ sbits `setBit` from
                in  return $ Yield (SvS s (t:.BS1 (BitSet cset) (Boundary from)) (y':.:RiEBC (BitSet cset) (from :-> to)))
                                   (SvS s t y', setSucc zeroBits (BitSet $ 2^maxCount-1) cbits)
          !maxCount = popCount fullSet - 1 -- remove one for @from@, the @to@ bit should be in @rb@
          !shiftMask = fullSet `clearBit` from `clearBit` to
          {-# Inline [0] mk   #-}
          {-# Inline [0] step #-}
  {-# Inline addIndexDenseGo #-}

-- | TODO 18.2.2017 added

instance
  ( IndexHdr s x0 i0 us (BS1 Last I) cs c is (EdgeBoundary C)
  ) => AddIndexDense s (us:.BS1 Last I) (cs:.c) (is:.EdgeBoundary C) where
{-
  addIndexDenseGo (cs:.c) (vs:.CStatic()) (lbs:._) (ubs:.BS1 (BitSet fullSet) _) (us:._) (is:.(from :-> to))
    = map (\(SvS s t y') ->
        let RiEBC (BitSet usedSet) (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            hereBits = fullSet .&. complement usedSet
            hereSet  = hereBits `setBit` to
        in  SvS s (t:.BS1 (BitSet hereSet) (Boundary to)) (y':.:RiEBC (BitSet fullSet) (from :-> to)))
    . addIndexDenseGo cs vs lbs ubs us is
    -}
  addIndexDenseGo (cs:.c) (vs:.CVariable ()) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.(from :-> to))
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is
    where mk (SvS s t y') =
            let RiEBC usedBits (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            in  assert (usedBits == 0) . return $ (SvS s t y', Just (zeroBits :: BitSet I))
          step (_, Nothing) = return $ Done
          step (SvS s t y', Just cbits)
            | popCount cbits > maxCount = return $ Done
            | (zeroBits `setBit` from `setBit` to) .&. cbits > 0 =
                return $ Skip (SvS s t y', setSucc zeroBits (BitSet $ 2^maxCount-1) cbits)
            | otherwise =
                let sbits = cbits -- popShiftL shiftMask cbits
                    cset  = getBitSet $ sbits `setBit` from -- `setBit` to
                in
#if ADPFUSION_DEBUGOUTPUT
                    traceShow ("EB/BS1-Last-I/C/step",(BS1 (BitSet cset) (Boundary from))) $
#endif
                    return $ Yield (SvS s (t:.BS1 (BitSet cset) (Boundary from)) (y':.:RiEBC (BitSet cset) (from :-> to)))
                                   (SvS s t y', setSucc zeroBits (BitSet $ 2^maxCount-1) cbits)
          !maxCount = popCount fullSet - 1 -- remove one for @from@, the @to@ bit should be in @rb@
          !shiftMask = fullSet `clearBit` from `clearBit` to
          {-# Inline [0] mk   #-}
          {-# Inline [0] step #-}
  {-# Inline addIndexDenseGo #-}

-- | TODO 18.2.2017 added

instance
  ( IndexHdr s x0 i0 us (BS1 Last O) cs c is (EdgeBoundary C)
  ) => AddIndexDense s (us:.BS1 Last O) (cs:.c) (is:.EdgeBoundary C) where
  addIndexDenseGo (cs:.c) (vs:.CStatic()) (lbs:._) (ubs:.BS1 (BitSet fullSet) _) (us:._) (is:.(from :-> to))
    = map (\(SvS s t y') ->
        let RiEBC (BitSet usedSet) (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            hereBits = usedSet -- fullSet .&. complement usedSet
            hereSet  = hereBits `setBit` to `setBit` from
        in
#if ADPFUSION_DEBUGOUTPUT
            traceShow ("EB/BS1-Last-O/C/step",(BS1 (BitSet hereSet) (Boundary to))) $
#endif
            SvS s (t:.BS1 (BitSet hereSet) (Boundary to)) (y':.:RiEBC (BitSet fullSet) (from :-> to)))
    . addIndexDenseGo cs vs lbs ubs us is
{-
  addIndexDenseGo (cs:.c) (vs:.CVariable ()) (lbs:._) (ubs:.BS1 fullSet _) (us:._) (is:.(from :-> to))
    = flatten mk step . addIndexDenseGo cs vs lbs ubs us is
    where mk (SvS s t y') =
            let RiEBC usedBits (_ :-> _) = getIndex (getIdx s) (Proxy :: PRI is (EdgeBoundary C))
            in  assert (usedBits == 0) . return $ (SvS s t y', Just (zeroBits :: BitSet O))
          step (_, Nothing) = return $ Done
          step (SvS s t y', Just cbits)
            | popCount cbits > maxCount = return $ Done
            | otherwise =
                let sbits = popShiftL shiftMask cbits
                    cset  = getBitSet $ sbits `setBit` from
                in  return $ Yield (SvS s (t:.BS1 (BitSet cset) (Boundary from)) (y':.:RiEBC (BitSet cset) (from :-> to)))
                                   (SvS s t y', setSucc zeroBits (BitSet $ 2^maxCount-1) cbits)
          !maxCount = popCount fullSet - 1 -- remove one for @from@, the @to@ bit should be in @rb@
          !shiftMask = fullSet `clearBit` from `clearBit` to
          {-# Inline [0] mk   #-}
          {-# Inline [0] step #-}
-}
  {-# Inline addIndexDenseGo #-}