{-# LANGUAGE BangPatterns, RankNTypes #-}

{-| Space efficient bit arrays

The module is meant to be imported qualified
(as it is common with collection libraries).

-}

{-

Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-}

module Ganeti.Objects.BitArray
  ( BitArray
  , size
  , empty
  , zeroes
  , count0
  , count1
  , foldr
  , (!)
  , setAt
  , (-&-)
  , (-|-)
  , subset
  , asString
  , fromList
  , toList
  ) where

import Prelude hiding (foldr)

import Control.Monad
import Control.Monad.Error
import qualified Data.IntSet as IS
import qualified Text.JSON as J

import Ganeti.BasicTypes
import Ganeti.JSON

-- | A fixed-size, space-efficient array of bits.
data BitArray = BitArray
  { size :: !Int
  , _bitArrayBits :: !IS.IntSet
    -- ^ Must not contain elements outside [0..size-1].
  }
  deriving (Eq, Ord)

instance Show BitArray where
  show = asString '0' '1'

empty :: BitArray
empty = BitArray 0 IS.empty

zeroes :: Int -> BitArray
zeroes s = BitArray s IS.empty

-- | Right fold over the set, including indexes of each value.
foldr :: (Bool -> Int -> a -> a) -> a -> BitArray -> a
foldr f z (BitArray s bits) = let (j, x) = IS.foldr loop (s, z) bits
                               in feed0 (-1) j x
  where
    loop i (!l, x) = (i, f True i (feed0 i l x))
    feed0 !i !j x | i >= j'   = x
                  | otherwise = feed0 i j' (f False j' x)
      where j' = j - 1

-- | Converts a bit array into a string, given characters
-- for @0@ and @1@/
asString :: Char -> Char -> BitArray -> String
asString c0 c1 = foldr f []
  where f b _ = ((if b then c1 else c0) :)

-- | Computes the number of zeroes in the array.
count0 :: BitArray -> Int
count0 ba@(BitArray s _) = s - count1 ba

-- | Computes the number of ones in the array.
count1 :: BitArray -> Int
count1 (BitArray _ bits) = IS.size bits

infixl 9 !
-- | Test a given bit in an array.
-- If it's outside its scope, it's always @False@.
(!) :: BitArray -> Int -> Bool
(!) (BitArray s bits) i | (i >= 0) && (i < s) = IS.member i bits
                        | otherwise           = False

-- | Sets or removes an element from a bit array.

-- | Sets a given bit in an array. Fails if the index is out of bounds.
setAt :: (MonadError e m, Error e) => Int -> Bool -> BitArray -> m BitArray
setAt i False (BitArray s bits) =
  return $ BitArray s (IS.delete i bits)
setAt i True (BitArray s bits) | (i >= 0) && (i < s) =
  return $ BitArray s (IS.insert i bits)
setAt i True _ = failError $ "Index out of bounds: " ++ show i

infixl 7 -&-
-- | An intersection of two bit arrays.
-- The length of the result is the minimum length of the two.
(-&-) :: BitArray -> BitArray -> BitArray
BitArray xs xb -&- BitArray ys yb = BitArray (min xs ys)
                                             (xb `IS.intersection` yb)

infixl 5 -|-
-- | A union of two bit arrays.
-- The length of the result is the maximum length of the two.
(-|-) :: BitArray -> BitArray -> BitArray
BitArray xs xb -|- BitArray ys yb = BitArray (max xs ys) (xb `IS.union` yb)

-- | Checks if the first array is a subset of the other.
subset :: BitArray -> BitArray -> Bool
subset (BitArray _ xs) (BitArray _ ys) = IS.isSubsetOf xs ys

-- | Converts a bit array into a list of booleans.
toList :: BitArray -> [Bool]
toList = foldr (\b _ -> (b :)) []

-- | Converts a list of booleans to a 'BitArray'.
fromList :: [Bool] -> BitArray
fromList xs =
  -- Note: This traverses the list twice. It'd be better to compute everything
  -- in one pass.
  BitArray (length xs) (IS.fromList . map fst . filter snd . zip [0..] $ xs)

instance J.JSON BitArray where
  showJSON = J.JSString . J.toJSString . show
  readJSON j = do
    let parseBit '0' = return False
        parseBit '1' = return True
        parseBit c   = fail $ "Neither '0' nor '1': '" ++ [c] ++ "'"
    str <- readEitherString j
    fromList `liftM` mapM parseBit str