{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}

{-|
Module      : Definition
Description : Definition and presentation of relational objects in terms of
              Haskell and HList records
Copyright   : © Thor Michael Støre, 2015
License     : GPL v2 without "any later version" clause
Maintainer  : thormichael át gmail døt com
Stability   : experimental

Definition and presentation of relational objects in terms of Haskell and HList
records, and certain implementation level supporting functions.

Naming convention of the relational model is, to a degree, adopted to clarify
how it is mirrored into Haskell. With these aliases in place the following are
equivalent:

>>> :t fromList [] :: Set (Record '[Tagged "sno" String, Tagged "status" Int])

>>> :t relation [] :: Relation '[Attr "sno" String, Attr "status" Int]
-}

module Database.HaskRel.Relational.Definition (
  -- * Definition
  Attr, RTuple, Relation,
  -- ** Labels
  {-| To be in line with the naming convention of the relational model, the @Label@
type could be referred to as an "attribute name", and the @Labels@ type as
"attribute names", but since this would not clarify the matter significantly
HaskRel re-exports them as-is.

It is important to note how labels may either be pre-defined, or used ad-hoc:

>>> let qty = Label :: Label "qty"
>>> sum $ agg qty sp'
3100
>>> sum $ agg ( Label :: Label "qty" ) sp'
3100
  -}
  Label(Label), Labels,
  -- * Construction
  {-| These functions are based on common HList operations, and given aliases that
are in line with relational theory. As mentioned for each function they only
support 0 to 6-tuples, which is of course a drawback. For r-tuples of other
degrees the basic HList approaches to record construction may be used:

>>> let sno = Label :: Label "sno"
... etc ...
>>> pt$ sno .=. "S1" .*. sName .=. "Smith" .*. status .=. (20::Integer) .*. city .=. "London" .*. pName .=. "Ratchet" .*. color .=. "Mauve" .*. pno .=. "P9" .*. emptyRecord
...
>>> type SNO = Tagged "sno" String
... etc ...
>>> pt$ (hEndR $ hBuild "S1" "Smith" 20 "London" "Ratchet" "Mauve" "P9" :: Record '[SNO,SName,Status,City,PName,Color,PNO])
...

Same result.
  -}
  rTuple, rTuple', relation, relation',
  rHdr, as, nAs,
  -- * Constants
  tableDum, tableDee, empty,
  -- * Support
  unordRecEq, bodyAsList, relRearrange, relRearrange',
  -- * Presentation
  HaskRelTS(HaskRelTS), HListTypeSynonym,
  rPrint, rPrintTyped, p, pt, rShowType, showRelTab
    ) where

import Data.Set ( Set, fromList, foldr )
import qualified Data.Set ( map, empty )

import Data.HList.CommonMain

import Database.HaskRel.HFWTabulation
import Data.Typeable


-- == Definition

-- | A shorthand for "attribute", and a synonym of @Data.Tagged@.
type Attr = Tagged

{-| A "tuple as defined by the relational model", synonym for
@Data.HList.Record@.
-}
type RTuple = Record

-- | A synonym for @Set ( RTuple a )@.
type Relation a = Set ( RTuple a )

{-| A shorthand for "relational header", and a synonym of
@Data.HList.Record.Labels@. The header of either an r-tuple or a relation in
relational theory.
-}
type RHdr a = Labels a

{-
Though to make it more concise I'd love to express it something like:
 :: Relation' '[("sno", String), ("status", Int)]
-}

-- == Construction.

-- Instances that allow hToTuple and hFromTuple for unary records and labels
instance HTuple '[Label a] (Label a) where
    hToTuple (a `HCons` HNil) = a
    hFromTuple a = a `HCons` HNil

instance HTuple '[Tagged a a'] (Tagged a a') where
    hToTuple (a `HCons` HNil) = a
    hFromTuple a = a `HCons` HNil

{-| Constructs an r-tuple from tagged values. Supports only 0 to 6-tuples.

>>> pt$ rTuple (sno .=. "S1", sName .=. "Smith", status .=. (20::Integer), city .=. "London")
┌───────────────┬─────────────────┬───────────────────┬────────────────┐
│ sno :: String │ sName :: String │ status :: Integer │ city :: String │
├───────────────┼─────────────────┼───────────────────┼────────────────┤
│ S1            │ Smith           │ 20                │ London         │
└───────────────┴─────────────────┴───────────────────┴────────────────┘
-}
rTuple :: (HLabelSet (LabelsOf r), HTuple r t, HAllTaggedLV r) =>
     t -> RTuple r
rTuple t = mkRecord $ hFromTuple t

{-| Constructs an r-tuple from a tuples of untagged values, where the labels and
exact types are inferred from the context. Supports only 0 to 6-tuples.

>>> pt$ ( rTuple' ("S1", "Smith", 20, "London") :: RTuple '[SNO, SName, Status, City] )
...

Result as for 'rTuple'.
-}
rTuple'
  :: (RecordValues b, HTuple (RecordValuesR b) t,
      HMapAux HList TaggedFn (RecordValuesR b) b) =>
     t -> RTuple b
rTuple' t = hMapTaggedFn $ hFromTuple t

{-| Construct a relation value from a list of r-tuples of tagged values. Alias of
'Data.Set.fromList' with a result restricted to 'Relation', with a name from
relational theory. Supports only 0 to 6-tuples.

>>> :{
pt$ relation [rTuple (sno .=. "S1", sName .=. "Smith", status .=. (20::Integer), city .=. "London"),
              rTuple (sno .=. "S2", sName .=. "Jones", status .=. (10::Integer), city .=. "Paris")]
:}
┌───────────────┬─────────────────┬───────────────────┬────────────────┐
│ sno :: String │ sName :: String │ status :: Integer │ city :: String │
╞═══════════════╪═════════════════╪═══════════════════╪════════════════╡
│ S1            │ Smith           │ 20                │ London         │
│ S2            │ Jones           │ 10                │ Paris          │
└───────────────┴─────────────────┴───────────────────┴────────────────┘
-}
relation :: (Ord a, a ~ Record b) => [a] -> Relation b
relation = fromList

{-| Construct a relation value from a list of tuples of untagged values, where the
labels and exact types are inferred from the context. Supports only 0 to
6-tuples.

>>> pt$ ( relation' [("S1", "Smith", 20, "London"), ("S2", "Jones", 10, "Paris")] :: Relation '[SNO, SName, Status, City] )

Result as for 'relation'.
-}
relation'
  :: (Ord (HList b), RecordValues b, HTuple (RecordValuesR b) t,
      HMapAux HList TaggedFn (RecordValuesR b) b) =>
     [t] -> Relation b
relation' = fromList . map rTuple'

relation''
  :: (Ord (HList b), HLabelSet (LabelsOf b), HTuple b t,
      HAllTaggedLV b) =>
     [t] -> Relation b
relation'' = fromList . map rTuple


{-| Alias of @Data.HList.HList.hFromTuple@ with a name that is more descriptive
for the purpose of constructing "Labels", which are employed as relational
headings, from Haskell tuples of label values. When labels have been defined it
permits expressing:

>>> pt$ p `project` (undefined :: Labels '["pno","pName","color"])

As:

>>> pt$ p `project` (rHdr (pno,pName,color))

Supports 0 to 6-tuples.
-}
rHdr :: HTuple v t => t -> HList v
rHdr = hFromTuple


-- | Synonym of '.=.', for 'rename' and 'nAs'.
as :: Label l -> v -> Tagged l v
as = (.=.)

{-| N-adic constructor of 'as' statements, using @mkRecord . hFromTuple@. Only
supports 0 to 6-tuples.

>>> let pnu = Label :: Label "pnu"
>>> let colour = Label :: Label "colour"
>>> :t nAs (pno `as` pnu, color `as` colour)
nAs (pno `as` pnu, color `as` colour)
  :: Record '[Tagged "pno" (Label "pnu"), Tagged "color" (Label "colour")]
-}
nAs :: (HLabelSet (LabelsOf r), HTuple r t, HAllTaggedLV r) =>
     t -> Record r
nAs = mkRecord . hFromTuple


-- == Supporting functions

{-| Order-agnostic equality operation, which is neccessary for comparison correct
for r-tuples.
-}
unordRecEq
  :: (Eq (HList l), HRearrange3 (LabelsOf l) r l,
      HLabelSet (LabelsOf l), SameLength' r l,
      SameLength' r (LabelsOf l), SameLength' l r,
      SameLength' (LabelsOf l) r) =>
     RTuple l -> RTuple r -> Bool
unordRecEq l r = l == hRearrange' r


-- This variant is quite slow, though that should be because of type system
-- issues and not runtime performance; replicate 10 of a comparison has much
-- better performance than repeating it in a 10-tuple.
unordRecEq'
  :: (Eq (HList l), HRearrange3 (LabelsOf l) r l,
      HLabelSet (LabelsOf l), HLabelSet (LabelsOf r),
      H2ProjectByLabels (LabelsOf l) t r b, SameLength' r l,
      SameLength' r (LabelsOf l), SameLength' l r,
      SameLength' (LabelsOf l) r, HAllTaggedLV r) =>
     RTuple l -> RTuple t -> Bool
unordRecEq' l r = l == hProjectByLabels' r

-- | Rearrange a set of HList records to context. From the perspective of
-- relational theory this is a presentation function.
relRearrange
  :: (Ord (HList l), HRearrange3 (LabelsOf l) r l,
      HLabelSet (LabelsOf l), SameLength' r l,
      SameLength' r (LabelsOf l), SameLength' l r,
      SameLength' (LabelsOf l) r) =>
     Relation r -> Relation l
relRearrange = Data.Set.map hRearrange'

-- | Rearrange a set of HList records to the order of a set given by an
-- argument. The value of the second argument will be ignored. From the
-- perspective of relational theory this is a presentation function.
relRearrange'
  :: (Ord (HList l), HRearrange3 (LabelsOf l) r l,
      HLabelSet (LabelsOf l), SameLength' r l,
      SameLength' r (LabelsOf l), SameLength' l r,
      SameLength' (LabelsOf l) r) =>
     Relation r -> Relation l -> Relation l
relRearrange' rel ord = Data.Set.map hRearrange' rel

-- That ' is swapped between relRearrange and hRearrange is a work related
-- accident and not the way it must be.

rElementType :: Relation r -> RTuple r
rElementType rel = undefined


{-| Gives the body of a relation as a list. This will untag the values. -}
bodyAsList :: RecordValues r => Relation r -> [HList (RecordValuesR r)]
bodyAsList = Data.Set.foldr (\t b -> recordValues t : b ) []
-- TODO: That doesn't work recursively, which it most likely should.



-- == Constants of relational theory

{-| The nullary relation with an empty body

>>> pt$ tableDum
┌──┐
│  │
╞══╡
└──┘
>>> relation [] == tableDum
True
-}
tableDum :: Relation '[]
tableDum = Data.Set.empty

{-| The nullary relation of cardinality one.

>>> pt$ tableDee
┌──┐
│  │
╞══╡
│  │
└──┘
>>> relation [rTuple ()] == tableDee
True
>>> relation' [()] == tableDee
True
-}
tableDee :: Relation '[]
tableDee = fromList[emptyRecord]


-- Redefined and not just reexported to add a bit of documentation
-- TODO: Check if it's possible to have something that'll work for both tableDum and empty.

{-| The empty set without an explicit type. In a relational context this is a
relation with an empty body and no defined heading.

>>> relation [rTuple (sno .=. "S1", status .=. 5)] == empty
False

Note how the example for `tableDum` won't work for `empty` and vice-versa.
-}
empty :: Set a
empty = Data.Set.empty
-- TODO: Or :: Relation a?



-- == Presentation


-- | HaskRel type synonyms
data HaskRelTS = HaskRelTS

-- | HaskRel type synonyms
instance HListTypeSynonym HaskRelTS where
  hRecTS _ = "RTuple"
  hRecSetTS _ = "Relation"
  hTIPTS _ = "RTuple"
  hTIPSetTS _ = "Relation"


-- | Prints a tabular representation of an r-tuple or relation.
rPrint :: HFWPresent r => r -> IO ()
rPrint = hfwPrint

-- | Prints a tabular representation of an r-tuple or relation, with type
-- information.
rPrintTyped :: HFWPresent r => r -> IO ()
rPrintTyped = hfwPrintTypedTS HaskRelTS

-- | Synonym of `rPrint`.
p :: HFWPresent r => r -> IO ()
p = rPrint

-- | Synonym of `rPrintTyped`.
pt :: HFWPresent r => r -> IO ()
pt = rPrintTyped

{-| Specialization of @Database.HaskRel.HFWTabulation.showHTypeTS@ that employs
HaskRel specific type-synonyms.
-}
rShowType :: TypeRep -> String
rShowType = showHTypeTS HaskRelTS


-- Redefined for prettier signatures.
-- | Shows a tabular representation of a relation.
showRelTab ::
     (Typeable a, RecordValues a,
      HFoldr (Mapcar HPresentRecAttr) [[String]] (RecordValuesR a) [[String]]) =>
     Relation a -> String
showRelTab = showHRecSetTab