{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}

-- | This module contains definitions to work with Template Haskell. All
-- the definitions in this module are used by Template Haskell in
-- "Quipper.Algorithms.QLS.TemplateOracle" and "Quipper.Algorithms.QLS.RealFunc".
module Quipper.Algorithms.QLS.CircLiftingImport where

import Data.Typeable

import Quipper
import Quipper.Internal

import Quipper.Libraries.Arith
import Quipper.Libraries.Decompose

import Quipper.Algorithms.QLS.QDouble
import Quipper.Algorithms.QLS.QSignedInt
import Quipper.Algorithms.QLS.Utils

-- * Utility function

-- | @'grepn' /regexp/ /list/@: Counts how many times /regexp/ is a
-- sublist of /list/.
grepn :: (Eq a) => [a] -> [a] -> Int
grepn regexp l = 
      if (length regexp > length l) then 0
      else if ((take (length regexp) l) == regexp) then 1 + (grepn regexp $ tail l)
      else grepn regexp $ tail l




-- * Lifting of ordering operators.


-- | Template version of '/='.
template_symb_slash_symb_equal_ :: (Typeable qa, QOrd qa) => Circ (qa -> Circ (qa -> Circ Qubit))
template_symb_slash_symb_equal_ = return $ \x -> return $ \y -> do
            (_,_,r) <- box "/=" (decompose_generic Toffoli $ uncurry q_is_not_equal) (x,y)
            return r

-- | Template version of '<'.
template_symb_oangle_ :: (Typeable qa, QOrd qa) => Circ (qa -> Circ (qa -> Circ Qubit))
template_symb_oangle_ = return $ \x -> return $ \y -> box "<" (uncurry q_less) (x,y)

-- | Template version of '<='.
template_symb_oangle_symb_equal_ :: (Typeable qa, QOrd qa) => Circ (qa -> Circ (qa -> Circ Qubit))
template_symb_oangle_symb_equal_ = return $ \x -> return $ \y -> box "<=" (uncurry q_leq) (x,y)

-- | Template version of '>'.
template_symb_cangle_ :: (Typeable qa, QOrd qa) => Circ (qa -> Circ (qa -> Circ Qubit))
template_symb_cangle_ = return $ \x -> return $ \y -> box ">" (uncurry q_greater) (x,y)

-- | Template version of '>='.
template_symb_cangle_symb_equal_ :: (Typeable qa, QOrd qa) => Circ (qa -> Circ (qa -> Circ Qubit))
template_symb_cangle_symb_equal_ = return $ \x -> return $ \y -> box ">=" (uncurry q_geq) (x,y)




-- * Lifting of arithmetic operators

-- | Template version of '-'.
template_symb_minus_ :: (Typeable qa, QNum qa) => Circ (qa -> Circ (qa -> Circ qa))
template_symb_minus_ = return $ \qx -> return $ \qy -> do (qx,qy,qz) <- box "-" (uncurry q_sub) (qx,qy); return qz

-- | Template version of '+'.
template_symb_plus_ :: (Typeable qa, QNum qa) => Circ (qa -> Circ (qa -> Circ qa))
template_symb_plus_ = return $ \qx -> return $ \qy -> do (qx,qy,qz) <- box "+" (uncurry q_add) (qx,qy); return qz

-- | Template version of '*'.
template_symb_star_ :: (Typeable qa, QNum qa) => Circ (qa -> Circ (qa -> Circ qa))
template_symb_star_ = return $ \qx -> return $ \qy -> do (qx,qy,qz) <- box "*" (uncurry q_mult) (qx,qy); return qz

-- | Template version of 'negate'.
template_negate :: (Typeable qa, QNum qa) => Circ (qa -> Circ qa)
template_negate = return $ \qx -> do (_,qz) <- box "neg" q_negate qx; return qz

-- | Template version of 'abs'.
template_abs :: (Typeable qa, QNum qa) => Circ (qa -> Circ qa)
template_abs = return $ \x -> do
                  (_,r) <- box "abs" q_abs x
                  return r

-- | Template version of 'mod'
template_mod :: Circ (QSignedInt -> Circ (QSignedInt -> Circ QSignedInt))
template_mod = return $ \x -> return $ \y -> box "mod" (decompose_generic Toffoli $ uncurry q_mod) (x,y)


-- * Operations on 'QDouble'

-- | Template version of '/' on 'Fractional'.
template_symb_slash_:: Circ (QDouble -> Circ (QDouble -> Circ QDouble))
template_symb_slash_ = return $ \x -> return $ \y -> box "/" (decompose_generic Toffoli $ uncurry q_div_real) (x,y)

-- | The constant 'pi' as an 'FDouble'.
local_pi :: FDouble
local_pi =  fdouble pi

-- | Template version of 'local_pi'.
template_local_pi :: Circ QDouble
template_local_pi = qinit (fdouble pi)

-- | The identity function of type 'FDouble'. This is used to help the
-- type checker work around a problem in GHC 8.0, where types of
-- overloaded operations sometimes require disambiguation.
id_fdouble :: FDouble -> FDouble
id_fdouble x = x

-- | Template version of 'id_fdouble'.
template_id_fdouble :: Circ (QDouble -> Circ QDouble)
template_id_fdouble = return $ \x -> return x

-- * Relation between 'QDouble' and 'QSignedInt'.

-- | Template version of 'floor'.
template_floor :: Circ (QDouble -> Circ QSignedInt)
template_floor = return $ \(XDouble k (SInt x b)) -> 
      return $ SInt (reverse . drop k . reverse $ x) b

-- | Template version of 'ceiling'.
template_ceiling :: Circ (QDouble -> Circ QSignedInt)
template_ceiling = return $ \x -> q_ceiling x

-- | Template version of 'fromIntegral'.
template_fromIntegral :: Circ (QSignedInt -> Circ QDouble)
template_fromIntegral = return $ \x -> q_fromIntegral x



-- * Dealing with parameters.


-- | Lift a real number to 'QDouble'.
template_rational :: Double -> Circ QDouble
template_rational x = qinit $ fdouble x

-- | Lift an integer to 'QSignedInt'.
template_integer :: Int -> Circ QSignedInt
template_integer x = qinit $ fromIntegral x


-- | Make a parameter 'Int' as a regular 'Int' that can be lifted.
getIntFromParam :: Int -> Int
getIntFromParam x = fromIntegral x

-- | Template version of 'getIntFromParam'.
template_getIntFromParam :: Circ (Int -> Circ QSignedInt)
template_getIntFromParam = return $ \x -> qinit $ fromIntegral x

-- | Parameter integer of value '0'.
paramZero :: Int
paramZero = 0

-- | Template version of 'paramZero'.
template_paramZero :: Circ Int
template_paramZero = return 0

-- | Parameter integer of value '10'.
paramTen :: Int
paramTen = 10

-- | Template version of 'paramTen'.
template_paramTen :: Circ Int
template_paramTen = return paramTen

-- | Successor function acting on parameter 'Int'.
paramSucc :: Int -> Int
paramSucc x = x+1

-- | Template version of 'paramSucc'.
template_paramSucc :: Circ (Int -> Circ Int)
template_paramSucc = return $ \x -> return (x+1)

-- | Predecessor function acting on parameter 'Int'.
paramPred :: Int -> Int
paramPred x = x - 1

-- | Template version of 'paramPred'.
template_paramPred :: Circ (Int -> Circ Int)
template_paramPred = return $ \x -> return (x-1)



-- | Subtraction of parameter integers.
paramMinus :: Int -> Int -> Int
paramMinus x y = x - y

-- | Template version of 'paramMinus'.
template_paramMinus :: Circ (Int -> Circ (Int -> Circ Int))
template_paramMinus = return $ \x -> return $ \y -> return (x-y)



-- * Miscellaneous operations.

-- | Lifted version of @'length'@.
template_length :: Circ ([a] -> Circ QSignedInt)
template_length = return $ \l -> qinit $ fromIntegral $ length l

-- | Return the first half of the input list.
take_half :: [a] -> [a]
take_half l = take (1 + (length l) `div` 2) l

-- | Lifted version of @'take_half'@.
template_take_half :: Circ ([a] -> Circ [a])
template_take_half = return $ \l -> return $ take_half l