{-# OPTIONS_GHC-funbox-strict-fields #-}

{-# LANGUAGE BangPatterns   #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric  #-}

module HaskellWorks.Data.RankSelect.Poppy512S
    ( Poppy512S(..)
    , Rank1(..)
    , makePoppy512S
    , sampleRange
    ) where

import Control.DeepSeq
import Data.Word
import GHC.Generics
import HaskellWorks.Data.AtIndex
import HaskellWorks.Data.BalancedParens.BalancedParens
import HaskellWorks.Data.BalancedParens.CloseAt
import HaskellWorks.Data.BalancedParens.Enclose
import HaskellWorks.Data.BalancedParens.FindClose
import HaskellWorks.Data.BalancedParens.FindCloseN
import HaskellWorks.Data.BalancedParens.FindOpen
import HaskellWorks.Data.BalancedParens.FindOpenN
import HaskellWorks.Data.BalancedParens.NewCloseAt
import HaskellWorks.Data.BalancedParens.OpenAt
import HaskellWorks.Data.Bits.BitLength
import HaskellWorks.Data.Bits.BitRead
import HaskellWorks.Data.Bits.BitWise
import HaskellWorks.Data.Bits.PopCount.PopCount1
import HaskellWorks.Data.Positioning
import HaskellWorks.Data.RankSelect.Base.Rank0
import HaskellWorks.Data.RankSelect.Base.Rank1
import HaskellWorks.Data.RankSelect.Base.Select1
import HaskellWorks.Data.Search
import HaskellWorks.Data.Vector.AsVector64
import Prelude                                         hiding (length)

import qualified Data.Vector.Storable as DVS

data Poppy512S = Poppy512S
  { poppy512SBits   :: !(DVS.Vector Word64)
  , poppy512Index   :: !(DVS.Vector Word64)
  , poppy512Samples :: !(DVS.Vector Word64) -- Sampling position of each 8192 1-bit
  } deriving (Eq, Show, NFData, Generic)

instance AsVector64 Poppy512S where
  asVector64 = asVector64 . poppy512SBits
  {-# INLINE asVector64 #-}

instance PopCount1 Poppy512S where
  popCount1 = popCount1 . poppy512SBits
  {-# INLINE popCount1 #-}

popCount1Range :: Int -> Int -> DVS.Vector Count -> Count
popCount1Range start len = popCount1 . DVS.take len . DVS.drop start

makePoppy512S :: DVS.Vector Word64 -> Poppy512S
makePoppy512S v = Poppy512S
  { poppy512SBits     = v
  , poppy512Index     = DVS.constructN (((DVS.length v + 8 - 1) `div` 8) + 1) gen512Index
  , poppy512Samples   = DVS.unfoldrN (fromIntegral (popCount1 v `div` 8192) + 1) genS (0, 0)
  }
  where gen512Index !u = let !indexN = DVS.length u - 1 in
          if indexN == -1
            then 0
            else let !result = popCount1Range (indexN * 8) 8 v + DVS.last u in result
        genS :: (Count, Position) -> Maybe (Word64, (Count, Position))
        genS (!pca, !n) = if n < end v
          then  let !w = v !!! n in
                let !pcz = pca + popCount1 w in
                if (8192 - 1 + pca) `div` 8192 /= (8192 - 1 + pcz) `div` 8192
                  then  let !newWord = fromIntegral n * 64 + fromIntegral (select1 w (fromIntegral (8192 - (pca `mod` 8192))))
                            !newCount = pcz
                            !newPosition = n + 1
                            !newState = (newCount, newPosition)
                            !newResult = (newWord, newState) in
                        Just newResult
                  else  let !newCount = n + 1
                            !newState = (pcz, newCount) in
                        genS newState
          else Nothing

instance BitLength Poppy512S where
  bitLength v = length (poppy512SBits v) * bitLength (poppy512SBits v !!! 0)
  {-# INLINE bitLength #-}

instance TestBit Poppy512S where
  (.?.) = (.?.) . poppy512SBits
  {-# INLINE (.?.) #-}

instance BitRead Poppy512S where
  bitRead = fmap makePoppy512S . bitRead

instance Rank1 Poppy512S where
  rank1 (Poppy512S v i _) p =
    (i !!! toPosition (p `div` 512)) + rank1 (DVS.drop ((fromIntegral p `div` 512) * 8) v) (p `mod` 512)

instance Rank0 Poppy512S where
  rank0 (Poppy512S v i _) p =
    p `div` 512 * 512 - (i !!! toPosition (p `div` 512)) + rank0 (DVS.drop ((fromIntegral p `div` 512) * 8) v) (p `mod` 512)

sampleRange :: Poppy512S -> Count -> (Word64, Word64)
sampleRange (Poppy512S _ index samples) p =
  let j = (fromIntegral p - 1) `div` 8192 in
  if 0 <= j && j < DVS.length samples
    then  let pa = samples DVS.! j                in
          if j + 1 < DVS.length samples
            then  let pz = samples DVS.! (j + 1)          in
                  (pa, pz)
            else (pa, fromIntegral (DVS.length index - 1))
    else (1, fromIntegral (DVS.length index - 1))

instance Select1 Poppy512S where
  select1 iv@(Poppy512S v i _) p = if DVS.length v /= 0
      then toCount q * 512 + select1 (DVS.drop (fromIntegral q * 8) v) (p - s)
      else 0
    where q = binarySearch (fromIntegral p) wordAt iMin iMax
          s = (i !!! q) :: Count
          wordAt = (i !!!)
          (sampleMin, sampleMax) = sampleRange iv p
          iMin = fromIntegral $  (sampleMin - 1) `div` 512      :: Position
          iMax = fromIntegral $ ((sampleMax - 1) `div` 512) + 1 :: Position

instance OpenAt Poppy512S where
  openAt = openAt . poppy512SBits
  {-# INLINE openAt #-}

instance CloseAt Poppy512S where
  closeAt = closeAt . poppy512SBits
  {-# INLINE closeAt #-}

instance NewCloseAt Poppy512S where
  newCloseAt = newCloseAt . poppy512SBits
  {-# INLINE newCloseAt #-}

instance FindOpenN Poppy512S where
  findOpenN = findOpenN . poppy512SBits
  {-# INLINE findOpenN #-}

instance FindOpen Poppy512S where
  findOpen = findOpen . poppy512SBits
  {-# INLINE findOpen #-}

instance FindClose Poppy512S where
  findClose = findClose . poppy512SBits
  {-# INLINE findClose #-}

instance FindCloseN Poppy512S where
  findCloseN = findCloseN . poppy512SBits
  {-# INLINE findCloseN #-}

instance Enclose Poppy512S where
  enclose = enclose . poppy512SBits
  {-# INLINE enclose #-}

instance BalancedParens Poppy512S where
  firstChild  = firstChild  . poppy512SBits
  nextSibling = nextSibling . poppy512SBits
  parent      = parent      . poppy512SBits
  {-# INLINE firstChild  #-}
  {-# INLINE nextSibling #-}
  {-# INLINE parent      #-}