{-# LANGUAGE Unsafe #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}

-- | This module contains an efficient representation of algebraic
-- boolean formulas.
module QuipperLib.ClassicalOptim.AlgExp where

import qualified Test.QuickCheck as Test

import qualified Data.Map as M
import qualified Data.List as L
import qualified Data.Set.Monad as S  {- set-monad-0.1.0.0 -}
import qualified Data.IntSet as IS
import qualified Data.IntMap.Strict as IM {- containers-0.5.2.1 -}

import Libraries.Auxiliary (bool_xor)

import QuipperLib.ClassicalOptim.Circuit

-- ----------------------------------------------------------------------
-- * Auxiliary functions

-- | Build the characteristic function of a set.
mapOfSet :: Ord a => S.Set a -> M.Map a Int
mapOfSet s = S.foldl' (\m x -> M.insert x 1 m) M.empty s

-- | Get the set of elements whose images are odd.
setOfMap :: Ord a => M.Map a Int -> S.Set a
setOfMap m = 
    M.foldlWithKey' (\s x _ -> S.insert x s) S.empty $ 
          M.filter (\x -> mod x 2 == 1) m

-- | Split a list in the middle.
split_even :: [a] -> ([a],[a])
split_even a = splitAt (div (length a) 2) a

-- ----------------------------------------------------------------------
-- * Expressions

-- | The type of algebraic boolean expressions. 
-- 
-- We represent boolean expressions using \"and\" and \"xor\" as the
-- primitive connectives. Equivalently, we can regard booleans as the
-- elements of the two-element field /F/[sub 2], with operations \"*\"
-- (times) and \"+\" (plus). 
-- 
-- An algebraic expression
--      @x1*x2*x3 + y1*y2*y3 + z1*z2@
-- is encoded as
--      @{{x1,x2,x3},{y1,y2,y3},{z1,z2}}@.
-- 
-- In particular,
--     @{}   == False == 0@ and 
--     @{{}} == True  == 1@.
type Exp = S.Set IS.IntSet

instance Show Exp where
    show e = if (S.null e) then "F"
             else if (e == S.singleton (IS.empty)) then "T"
             else L.concat $ L.intersperse "+" (L.map (\e -> L.concat $ L.map (\x -> "x" ++ (show x)) $ IS.toList e) $ S.toList e)

-- | Turn an @Exp@ into a list of lists.
listOfExp :: Exp -> [[Int]]
listOfExp e = S.toList $ S.map IS.toList e

-- | Turn a list of lists into an @Exp@.
expOfList :: [[Int]] -> Exp
expOfList l = S.fromList $ L.map IS.fromList l

-- | The conjunction of two expression.
exp_and :: Exp -> Exp -> Exp
exp_and a b = 
    setOfMap $
    S.foldl (\exp monomial -> M.unionWith (+) exp $ exp_and_aux monomial $ mapOfSet a) M.empty b
  where
    exp_and_aux :: IS.IntSet -> M.Map IS.IntSet Int -> M.Map IS.IntSet Int
    exp_and_aux monomial exp =  M.mapKeysWith (+) (IS.union monomial) exp

-- | The xor of two expressions.
exp_xor :: Exp -> Exp -> Exp
exp_xor a b = setOfMap $ M.unionWith (+) (mapOfSet a) (mapOfSet b)

-- | The expression \"False\".
exp_false :: Exp
exp_false = S.empty

-- | The expression \"True\".
exp_true :: Exp
exp_true = S.singleton IS.empty

-- | The negation of an expression.
exp_not :: Exp -> Exp 
exp_not e = exp_xor e exp_true

-- | The expression /x/[sub /n/].
exp_var :: Int -> Exp
exp_var x = S.singleton $ IS.singleton x

-- ----------------------------------------------------------------------
-- * Properties of expressions

-- $ The important property of expressions is that two formulas have
-- the same truth table iff they are syntactically equal. This makes
-- the equality test of wires theoretically straightforward.
-- 
-- The following automated tests check this property, using the
-- "Test.QuickCheck" library.

-- ----------------------------------------------------------------------
-- ** Truth tables

-- $ A /valuation/ on a set of variables is a map from variables to
-- booleans. This can be thought of as a row in a truth table. A
-- /truth table/ is a map from valuations to booleans, but we just
-- represent this as a list of booleans, listed in lexicographically
-- increasing order of valuations.

-- | Get the variables used in an expression.
vars_of_exp :: Exp -> [Int]
vars_of_exp e = IS.toList $ S.foldl (\a b -> IS.union a b) IS.empty e

-- | Evaluate the expression with respect to the given valuation. A
-- /valuation/ is a map from variables to booleans, i.e., a row in a
-- truth table.
exp_eval :: Exp -> M.Map Int Bool -> Bool
exp_eval e m = L.foldl bool_xor False $ L.map (L.foldl (&&) True) $ L.map (L.map (m M.!)) $ L.map (IS.toList) $ S.toList e

-- | Construct the list of all 2[super /n/] valuations for a given
-- list of /n/ variables.
valuations_of_vars :: [Int] -> [M.Map Int Bool]
valuations_of_vars [] = [M.empty]
valuations_of_vars (h:t) = l
  where
    l = (L.map (M.insert h False) v) ++ (L.map (M.insert h True) v)
    v = valuations_of_vars t 

-- | Build the truth table for the given expression, on the given list
-- of variables. The truth table is returned as a list of booleans in
-- lexicographic order of valuations. For example, if
-- 
-- >  1 2 | exp
-- >  F F | f1
-- >  F T | f2
-- >  T F | f3
-- >  T T | f4
-- 
-- then the output of the function is @[f1,f2,f3,f4]@.
truth_table_of_exp :: [Int] -> Exp -> [Bool]
truth_table_of_exp vars e = L.map (exp_eval e) (valuations_of_vars vars)

-- | Return an expression realizing the given truth table. Uses
-- variables starting with the given number.
exp_of_truth_table :: Int -> [Bool] -> Exp
exp_of_truth_table i [] = exp_true
exp_of_truth_table i [False] = exp_false
exp_of_truth_table i [True] = exp_true
exp_of_truth_table i t = ((exp_not (exp_var i)) `exp_and` e1) `exp_xor` ((exp_var i) `exp_and` e2)
  where
    (t1,t2) = split_even t
    e1 = exp_of_truth_table (i+1) t1
    e2 = exp_of_truth_table (i+1) t2

-- ----------------------------------------------------------------------
-- ** Quick-checking

-- | Compute 2[sup /n/].
twoExp :: (Integral a) => a -> Int
twoExp 0 = 1
twoExp n | mod n 2 == 0 = let a = twoExp (div n 2) in a * a
         | otherwise = 2 * (twoExp (n-1))

-- | Generate a list of 'Bool'.
genBoolList :: Integral a => a -> Test.Gen [Bool]
genBoolList n = Test.vectorOf (twoExp n) $ Test.oneof [return True, return False]

-- | Arguments for QuickCheck.
test_args :: Test.Args
test_args = Test.stdArgs { Test.maxSize = 500, 
                           Test.maxSuccess = 500, 
                           Test.maxDiscardRatio = 20 }

-- | First test: truth table to expression to truth table is the identity.
test_truth1 :: Int -> IO ()
test_truth1 n = Test.quickCheckWith test_args  $ aux
  where
    aux = Test.forAll (genBoolList n) $ \x ->
      x == (truth_table_of_exp [1..n] $ exp_of_truth_table 1 x)

-- | Generate a random list of @Int@s.
genIntList :: [Int] -> Int -> Test.Gen [Int]
genIntList vars size = do
  s <- Test.choose (0,size)
  Test.vectorOf s $ Test.elements vars

-- | Generate a random expression out of the given variables.
genExp :: [Int] -> Test.Gen Exp
genExp vars = do
  nber <- Test.choose (0, twoExp (length vars))
  v <- Test.vectorOf nber $ Test.sized $ genIntList vars
  return $ expOfList v

-- | Second test: expression to truth table to expression is the
-- identity.
test_truth2 :: Int -> IO ()
test_truth2 n = Test.quickCheckWith test_args $ aux
  where
    aux = Test.forAll (genExp [1..n]) $ \x ->
      x == (exp_of_truth_table 1 $ truth_table_of_exp [1..n] x)