{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE RecordWildCards #-}

module BTree.Types where

import Control.Applicative
import Data.Maybe (fromMaybe)
import GHC.Generics
import Control.Monad (when)
import Data.Int
import Prelude

import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import Control.Lens
import qualified Data.Vector as V
import Data.Vector.Binary
import qualified Data.ByteString as BS

-- | An offset within the stream
type Offset = Int64

-- | The number of entries in a B-tree
type Size = Word64

-- | The maximum number of children of a B-tree inner node
type Order = Word64

-- | @'OnDisk' a@ is a reference to an object of type @a@ on disk.
-- The offset does not include the header; e.g. the first object after
-- the header is located at offset 0.
newtype OnDisk a = OnDisk Offset
                 deriving (Show, Eq, Ord)

instance Binary (OnDisk a) where
    get = OnDisk <$> get
    {-# INLINE get #-}
    put (OnDisk off) = put off
    {-# INLINE put #-}

-- | A tree leaf (e.g. key/value pair)
data BLeaf k e = BLeaf !k !e
               deriving (Generic, Functor)

deriving instance (Show k, Show e) => Show (BLeaf k e)

-- | This only compares on the keys
instance (Eq k) => Eq (BLeaf k e) where
    BLeaf a _ == BLeaf b _ = a == b

-- | This only compares on the keys
instance Ord k => Ord (BLeaf k e) where
    compare (BLeaf a _) (BLeaf b _) = compare a b
    {-# INLINE compare #-}

instance (Binary k, Binary e) => Binary (BLeaf k e) where
    get = BLeaf <$> get <*> get
    {-# INLINE get #-}
    put (BLeaf k e) = put k >> put e
    {-# INLINE put #-}

-- | @'BTree' k f e@ is a B* tree of key type @k@ with elements of type @e@.
-- Subtree references are contained within a type @f@.
--
-- The 'Node' constructor contains a left child, and a list of key/child pairs
-- where each child's keys are greater than or equal to the given key.
data BTree k f e = Node (f (BTree k f e)) (V.Vector (k, f (BTree k f e)))
                 | Leaf !(BLeaf k e)
                 deriving (Generic)

deriving instance (Show e, Show k, Show (f (BTree k f e))) => Show (BTree k f e)
deriving instance (Eq e, Eq k, Eq (f (BTree k f e))) => Eq (BTree k f e)

instance (Binary k, Binary (f (BTree k f e)), Binary e)
  => Binary (BTree k f e) where
    get = do typ <- getWord8
             case typ of
               0 -> Node <$> get <*> getChildren
               1 -> bleaf <$> get <*> get
               _ -> fail "BTree.Types/get: Unknown node type"
      where bleaf k v = Leaf (BLeaf k v)
            getChildren =
                genericGetVectorWith (fromIntegral <$> getWord32be) ((,) <$> get <*> get)
    {-# INLINE get #-}

    -- some versions of binary don't inline the Binary (,) instance, pitiful
    -- performance ensues
    put (Node e0 es) = do
        putWord8 0
        put e0
        genericPutVectorWith (putWord32be . fromIntegral) (\(a,b) -> put a >> put b) es
    put (Leaf (BLeaf k0 e)) = putWord8 1 >> put k0 >> put e
    {-# INLINE put #-}

magic :: Word64
magic = 0xdeadbeefbbbbcccc

-- | B-tree file header
data BTreeHeader k e = BTreeHeader { _btMagic   :: !Word64
                                   , _btVersion :: !Word64
                                   , _btOrder   :: !Order
                                   , _btSize    :: !Size
                                   , _btRoot    :: !(Maybe (OnDisk (BTree k OnDisk e)))
                                     -- ^ 'Nothing' represents an empty tree
                                   }
                 deriving (Show, Eq, Generic)
makeLenses ''BTreeHeader

-- | It is critical that this encoding is of fixed size
instance Binary (BTreeHeader k e) where
    get = do
        _btMagic <- get
        _btVersion <- get
        _btOrder <- get
        _btSize <- get
        root <- get
        let _btRoot = if root == OnDisk 0 then Nothing else Just root
        return BTreeHeader {..}
    put (BTreeHeader {..}) = do
        put _btMagic
        put _btVersion
        put _btOrder
        put _btSize
        put $ fromMaybe (OnDisk 0) _btRoot

validateHeader :: BTreeHeader k e -> Either String ()
validateHeader hdr = do
    when (hdr^.btMagic /= magic) $ Left "Invalid magic number"
    when (hdr^.btVersion > 1) $ Left "Invalid version"

-- | A read-only B-tree for lookups
data LookupTree k e = LookupTree { _ltData    :: !BS.ByteString
                                 , _ltHeader  :: !(BTreeHeader k e)
                                 }
makeLenses ''LookupTree