{-# LANGUAGE BangPatterns #-}
module Data.Poker.Enumerate
    ( enumerateFiveCards
    ) where

import Data.Poker.Deck

import Data.Bits

-- | Given a set of dead cards, enumerate over all possible selections of five cards.
--   The generated card sets do not contain the dead cards.
enumerateFiveCards :: Monad m => CardSet -> (CardSet -> m ()) -> m ()
enumerateFiveCards !deadCards action = simpleFive deadCards done step
  where
    done = return ()
    step !i1 !i2 !i3 !i4 !i5 continue = do
      let !newMask = i1 `union` i2 `union` i3 `union` i4 `union` i5
      action newMask
      continue
{-# INLINE enumerateFiveCards #-}

simpleFive :: CardSet -> t -> (CardSet -> CardSet -> CardSet -> CardSet -> CardSet -> t -> t) -> t
simpleFive !deadCards done step = simpleIteratee deadCards maxSingleCardSet done next_1 step_1
  where
    maxSingleCardSet = CardSet 0x1000000000000000

    next_1 !i1 = simpleIteratee deadCards (p i1) done next_1 step_1
    step_1 !i1 = simpleIteratee deadCards (p i1) done (next_2 i1) (step_2 i1)

    next_2 !i1 !i2 = simpleIteratee deadCards (p i2) (next_1 i1) (next_2 i1) (step_2 i1)
    step_2 !i1 !i2 = simpleIteratee deadCards (p i2) (next_1 i1) (next_3 i1 i2) (step_3 i1 i2)

    next_3 !i1 !i2 !i3 = simpleIteratee deadCards (p i3) (next_2 i1 i2) (next_3 i1 i2) (step_3 i1 i2)
    step_3 !i1 !i2 !i3 = simpleIteratee deadCards (p i3) (next_2 i1 i2) (next_4 i1 i2 i3) (step_4 i1 i2 i3)

    next_4 !i1 !i2 !i3 !i4 = simpleIteratee deadCards (p i4) (next_3 i1 i2 i3) (next_4 i1 i2 i3) (step_4 i1 i2 i3)
    step_4 !i1 !i2 !i3 !i4 = simpleIteratee deadCards (p i4) (next_3 i1 i2 i3) (next_5 i1 i2 i3 i4) (step_5 i1 i2 i3 i4)

    next_5 !i1 !i2 !i3 !i4 !i5 =
      simpleIteratee deadCards (p i5) (next_4 i1 i2 i3 i4) (next_5 i1 i2 i3 i4) (step_5 i1 i2 i3 i4)
    step_5 !i1 !i2 !i3 !i4 !i5 = step i1 i2 i3 i4 i5 (next_5 i1 i2 i3 i4 i5)

    p = predCardSet
    predCardSet (CardSet 0x10000) = CardSet 0x1000
    predCardSet (CardSet 0x100000000) = CardSet 0x10000000
    predCardSet (CardSet 0x1000000000000) = CardSet 0x100000000000
    predCardSet (CardSet mask) = CardSet (mask `unsafeShiftR` 1)

{-# INLINE simpleFive #-}

simpleIteratee :: CardSet -> CardSet -> t -> (CardSet -> t) -> (CardSet -> t) -> t
simpleIteratee !deadCards !i1 done next step
    | isEmpty i1           = done
    | overlap i1 deadCards = next i1
    | otherwise            = step i1

overlap :: CardSet -> CardSet -> Bool
overlap a b = not (isEmpty (a `intersection` b))