{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE UndecidableInstances, FlexibleContexts, ScopedTypeVariables #-}

{-|
Module      : Database.HaskRel.Relational.Variable
Description : Relation variable definition and support functions
Copyright   : © Thor Michael Støre, 2015
License     : GPL v2 without "any later version" clause
Maintainer  : thormichael át gmail døt com
Stability   : experimental

Relation variable definition, along with IO level support functions.
-}
module Database.HaskRel.Relational.Variable (
  -- * Relation variable definitions
  Relvar (Relvar, relvarPath), relvarType,
  -- * Relational input
  readRel, readRelvar, hListsToRel,
  -- * Relational output
  writeRelvarBody, writeRelvarBody', showRelStr
  ) where

import Data.HList.CommonMain

import Data.Set ( Set, fromList )
import Data.Typeable ( Typeable )

import Database.HaskRel.HFWTabulation
  ( HFWPresent, HPresentTypedRecAttr, HPresentRecAttr, showHListSetType,
    hfwPrint, hfwPrintTyped, hfwPrintTypedTS )
import Database.HaskRel.Relational.Definition ( Relation, bodyAsList )


-- == Relation variable reference == --

{-| Relation variable reference. This type has a phantom type variable, which
generally calls for the type to be explicity specified:

@ s = Relvar "SuppliersPartsDB/S.rv" :: Relvar '[SNO, SName, Status, City]@
-}
data Relvar (a::[*]) = Relvar { relvarPath :: FilePath }

-- | Gives the type a relvar results in. Note that the value this results in
-- will always be @undefined@.
relvarType :: Relvar a -> Relation a
relvarType rv = undefined


-- TODO: It should be enforced that a given relvar has a given type
-- TODO: This is also afflicted by the issue that showHListSetType was developed for TIPs

instance Typeable a => Show ( Relvar a ) where
    show a = "Relvar \"" ++ relvarPath a ++ "\" :: Relvar" ++ showHListSetType ( relvarType a )


-- | Converts a list of HLists into a relation value.
hListsToRel
  :: (Ord (HList b), RecordValues b,
      HMapAux HList TaggedFn (RecordValuesR b) b) =>
     [HList (RecordValuesR b)] -> Relation b
hListsToRel = fromList . map hMapTaggedFn


{-| Reads a relation value from a string containing HLists of unpacked attribues.

>>> pt$ ( readRel "H[\"S1\",10],H[\"S2\",50]" :: Relation '[SNO,Status])
┌───────────────┬───────────────────┐
│ sno :: String │ status :: Integer │
╞═══════════════╪═══════════════════╡
│ S1            │ 10                │
│ S2            │ 50                │
└───────────────┴───────────────────┘
-}
readRel :: forall b .
     (Ord (HList b), Read (HList (RecordValuesR b)), RecordValues b,
      HMapAux HList TaggedFn (RecordValuesR b) b) =>
     String -> Relation b
readRel s = hListsToRel $ read $ "[" ++ s ++ "]"

-- | Reads a relation value of a given type from a string.
readRel' :: forall b .
     (Ord (HList b), Read (HList (RecordValuesR b)), RecordValues b,
      HMapAux HList TaggedFn (RecordValuesR b) b) =>
     String -> Relation b -> Relation b
readRel' s rt = hListsToRel $ read $ "[" ++ s ++ "]"

-- | Read a relation variable from the file referenced by the first argument
readRelvar
  :: (Ord (HList b), Read (HList (RecordValuesR b)), RecordValues b,
      HMapAux HList TaggedFn (RecordValuesR b) b) =>
     Relvar b -> IO (Relation b)
readRelvar rv = do
    relStr <- readFile ( relvarPath rv )
    return $ readRel relStr


-- | Prints a relation as a list without the outer brackets.
showRelStr :: (Show (HList (RecordValuesR r)), RecordValues r) =>
     Relation r -> String
showRelStr = init . tail . show . bodyAsList

-- TODO: This uses text-processing on the result of a call to show, fix so that
-- it doesn't break if the show-format is changed.
-- | Writes a body of a relvar to a given file
writeRelvarBody :: Show r => FilePath -> r -> IO ()
writeRelvarBody n hll = writeFile n ( init $ tail $ show hll )

-- | Writes a body of a relvar to a given relvar file reference
writeRelvarBody' :: Show r => Relvar rv -> r -> IO ()
writeRelvarBody' rv hll = writeFile ( relvarPath rv ) ( init $ tail $ show hll )

-- == HFWPresent instance for relvars

instance (Ord (HList b), Read (HList (RecordValuesR b)), Typeable b,
      RecordValues b, HMapAux HList TaggedFn (RecordValuesR b) b,
      HFoldr (Mapcar HPresentTypedRecAttr) [[String]] (RecordValuesR b) [[String]],
      HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR b) [[String]]) =>
         HFWPresent ( Relvar b )
    where
      hfwPrint r' = do
          r <- readRelvar r'
          hfwPrint r
      hfwPrintTypedTS ts r' = do
          r <- readRelvar r'
          hfwPrintTypedTS ts r