{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, UndecidableInstances #-}
module Main where
import Data.TCache
import Data.TCache.DefaultPersistence
import Data.ByteString.Lazy.Char8(pack,unpack)
import System.IO.Unsafe
import Data.Typeable

{-
-- would create orphan instances
instance (Read a, Show a) => Serializable a where
   serialize = pack . show
   deserialize = read . unpack
-}

-- An Employee
data Emp = Emp
  { ename :: String
  , salary :: Float
  } deriving (Read, Show, Typeable)

instance Serializable Emp where
   serialize = pack . show
   deserialize = read . unpack

instance Indexable Emp where
  key Emp { ename = name } = name

-- For illustration
newtype Other = Other String deriving (Read, Show)

-- A Company
data Company = Company
  { cname :: String
  , personnel :: [DBRef Emp]
  , other :: Other
  } deriving (Read, Show, Typeable)

instance Serializable Company where
   serialize = pack . show
   deserialize = read . unpack

instance Indexable Company where
  key Company{ cname = name } = name

myCompanyName :: String
myCompanyName = "mycompany"

-- Creating a Company from scratch
{-# NOINLINE myCompanyRef #-}
myCompanyRef :: DBRef Company
myCompanyRef = unsafePerformIO . atomically $  do

     refEmp1 <- newDBRef Emp{ename= "Emp1", salary= 34000}
     refEmp2 <- newDBRef Emp{ename= "Emp2", salary= 35000}
     refEmp3 <- newDBRef Emp{ename= "Emp3", salary= 54000}
     refEmp4 <- newDBRef Emp{ename= "Emp4", salary= 64000}

     newDBRef $
           Company
               {cname= myCompanyName
               ,personnel= [refEmp1, refEmp2, refEmp3, refEmp4]
               ,other= Other "blah blah blah"}

-- myCompany= Company myCompanyName [getDBRef "Emp1",getDBRef "Emp2",getDBRef "Emp3"]

increaseSalaries :: Float -> STM ()
increaseSalaries percent1 = do
  mycompany' <- readDBRef myCompanyRef
  mycompany <- case mycompany' of
    Just x -> pure x
    Nothing -> error "Boom"

  mapM_  (increase percent1 ) $ personnel mycompany
  where
  increase percent ref= do
    emp' <- readDBRef ref
    emp <- case emp' of
      Just x -> pure x
      Nothing -> error "Boom"

    writeDBRef ref $ emp{salary= salary emp * factor}
    where
    factor= 1+ percent/ 100

printSalaries :: DBRef Company -> IO ()
printSalaries ref1 = do
  Just comp <- atomically $ readDBRef ref1
  mapM_ printSalary $ personnel comp
  where
  printSalary ref= atomically (readDBRef ref) >>=  print

putMsg :: String -> IO ()
putMsg msg= putStrLn $ ">>" ++ msg

main :: IO ()
main = do
  putMsg "DBRefs are cached indexable, serializable, unique-by-key references to objects stored in the cache, mutable under STM transactions"
  putMsg "DBRef's are instances of Show"
  print myCompanyRef


  let myCompanyRef2 = read $ show myCompanyRef :: DBRef Company
  putMsg "DBRefs are identified by the key of the referenced object"
  putMsg "DBRef's are alse instances of read"

  print myCompanyRef2
  putMsg "DBReference's with the same key point to the same data object"
  putMsg "DBRefs can be part of serializable mutable structures"
  putMsg "the referenced object are reloaded  transparently on demand in the cache and discarded according with TCache definable policies"
  putMsg "the DBRef load and reload requires a cache lockup, but subsequient accesses does not. so performance is almost like TVars and way better that the *Resource* primitives"
  atomically (readDBRef myCompanyRef) >>= print
  atomically (readDBRef myCompanyRef2) >>= print

  putMsg "Before salary increase, the company personnel is accessed with the second reference"
  printSalaries myCompanyRef2
  putMsg "atomically increase the salaries of all the personel"
  atomically $ increaseSalaries 10
  putMsg "after the increase"
  printSalaries myCompanyRef2

  let emp3ref = getDBRef "Emp3"
  putMsg "tch tch, this bad boy does not deserve his salary"
  Just emp3 <- atomically $ readDBRef emp3ref
  print emp3
  atomically $ writeDBRef emp3ref $ emp3{salary= 10000}

  putMsg "so the complete list of company salaries are..."
  printSalaries myCompanyRef
  syncCache  --  use it if you want to save all the changes. (or, else, clearSyncCache)

  putStrLn "checking race condition on cache cleaning"

  let emp1 =  Emp{ename="Emp1", salary= -1}
  let key1 = keyResource emp1
  let remp1 = getDBRef key1
  Just emp1' <- atomically $ readDBRef remp1
  atomically $ flushDBRef  remp1
  let remp1' = getDBRef key1
  atomically $ writeDBRef remp1' $ emp1'{salary=0}

  putStrLn "must reflect the salary 0 for emp1"
  printSalaries myCompanyRef2
