import ProjectM36.Base
import Criterion.Main
import qualified ProjectM36.Attribute as A
import ProjectM36.Relation
import ProjectM36.Persist
import ProjectM36.RelationalExpression
import ProjectM36.Error
import ProjectM36.Transaction.Persist
import qualified Data.Text as T
import qualified Data.Vector as V
import Control.Monad.Trans.Reader
import qualified ProjectM36.DatabaseContext as DBC
import qualified Data.Set as S
import Data.Monoid
import System.IO.Temp
import System.FilePath
import System.Directory

{-
* create a relation
* restrict a relation
* project a relation
* join two relations 
-}

validate :: Either RelationalError a -> a
validate (Left err) = error (show err)
validate (Right x) = x

createRelation :: Int -> Int -> Either RelationalError Relation
createRelation attributeCount tupleCount = do
  let attrs = A.attributesFromList $ map (\c-> Attribute (T.pack $ "a" ++ show c) IntAtomType) [0 .. attributeCount-1]
      tuple tupleX = RelationTuple attrs (V.generate attributeCount (\_ -> IntAtom (fromIntegral tupleX)))
      tuples = map tuple [0 .. tupleCount]
  mkRelationDeferVerify attrs (RelationTupleSet tuples)

createRelation' :: Int -> Int -> Relation
createRelation' x y = validate (createRelation x y)

restrictRelationToOneTuple :: Int -> Relation ->  Relation
restrictRelationToOneTuple match rel = validate (runReader (evalRelationalExpr restriction) exprState)
 where
  exprState = mkRelationalExprState DBC.empty
  restriction = Restrict predicateMatch (ExistingRelation rel)
  predicateMatch = AttributeEqualityPredicate "a0" (NakedAtomExpr (IntAtom match))

restrictRelationToHalfRelation :: Int -> Relation -> Relation
restrictRelationToHalfRelation cutoff rel = validate (runReader (evalRelationalExpr restriction) exprState)
 where
  exprState = mkRelationalExprState DBC.basicDatabaseContext
  restriction = Restrict predicateMatch (ExistingRelation rel)
  predicateMatch = AtomExprPredicate (FunctionAtomExpr "lte" [AttributeAtomExpr "a0", NakedAtomExpr (IntAtom cutoff)] ())

projectRelationToAttributes :: AttributeNames -> Relation -> Relation
projectRelationToAttributes attrNames rel = validate (runReader (evalRelationalExpr projection) exprState)
 where
  exprState = mkRelationalExprState DBC.empty
  projection = Project attrNames (ExistingRelation rel)

unionRelations :: Relation -> Relation -> Relation
unionRelations relA relB = validate (relA `union` relB)

joinRelations :: Relation -> Relation -> Relation
joinRelations relA relB = validate (join relA relB)

groupRelation :: AttributeNames -> Relation -> Relation
groupRelation attrNames rel = validate (runReader (evalRelationalExpr (Group attrNames "x" (ExistingRelation rel))) exprState)
 where
  exprState = mkRelationalExprState DBC.empty

bigRelAttrNames :: Int -> Int -> AttributeNames
bigRelAttrNames start end = AttributeNames (S.fromList (map (\i -> "a" <> T.pack (show i)) [start .. end]))

main :: IO ()
main = do
 tmpDir <- getCanonicalTemporaryDirectory
 createDirectoryIfMissing False (tmpDir </> "relvars")
 defaultMain [createRel, restrictRel, projectRel, unionRel, joinRel, groupRel, writeRel tmpDir]
  where
  createRel = bgroup "create" $ map (\tupCount -> bench ("relation 10x" ++ show tupCount) (nf (createRelation' 10) tupCount)) [100, 1000, 10000]
  bigrel10000 = createRelation' 10 10000
  bigrel1000 = createRelation' 10 1000
  bigrel100 = createRelation' 10 100
  
  restrictRel = bgroup "restrict" [restrictOneTupleRel, restrictHalfTuplesRel]
  restrictOneTupleRel = bench "restrict relation 10x10000 to 10x1" (nf (restrictRelationToOneTuple 5000) bigrel10000)
  restrictHalfTuplesRel = bench "restrict relation 10x10000 to 10x5000" (nf (restrictRelationToHalfRelation 5000) bigrel10000)

  projectRel = bgroup "project" [projectOneAttr, projectHalfAttrs]
  projectOneAttr = bench "project 10x1000 to 1x1000" (nf (projectRelationToAttributes (bigRelAttrNames 0 0)) bigrel1000)
  projectHalfAttrs = bench "project 10x1000 to 5x1000" (nf (projectRelationToAttributes (bigRelAttrNames 0 4)) bigrel1000)

  unionRel = bgroup "union" [unionIdenticalRelations1000, unionIdenticalRelations10000]
  unionIdenticalRelations1000 = bench "union identical 10x1000" (nf (unionRelations bigrel1000) bigrel1000)
  unionIdenticalRelations10000 = bench "union identical 10x10000" (nf (unionRelations bigrel10000) bigrel10000)

  joinRel = bgroup "join" [joinIdenticalRelations100]
  joinIdenticalRelations100 = bench "join identical 10x100" (nf (joinRelations bigrel100) bigrel100)

  groupRel = bgroup "group" [group100]
  group100 = bench "group 10x100" (nf (groupRelation (bigRelAttrNames 1 9)) bigrel100)

  writeRel tmpDir = bgroup "write" [writeRel10000 tmpDir]
  writeRel10000 tmpDir = bench "write 10x1000" $ nfIO (writeRelVar FsyncDiskSync tmpDir ("x", bigrel10000))