{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}

-- |
-- Module      : Database.HDBC.Record.KeyConstraint
-- Copyright   : 2013 Kei Hibino
-- License     : BSD3
--
-- Maintainer  : ex8k.hibino@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- This module provides proof object definitions
-- of table constraint specifiey by keys.
module Database.Record.KeyConstraint (
  -- * Constraint specified by keys
  ColumnConstraint, index, unsafeSpecifyColumnConstraint,

  Unique, UniqueColumnConstraint,
  NotNull, NotNullColumnConstraint,

  Primary, PrimaryColumnConstraint,

  KeyConstraint, indexes, unsafeSpecifyKeyConstraint,

  UniqueConstraint, PrimaryConstraint,

  -- * Deriviations
  uniqueColumn, notNullColumn,

  leftColumnConstraint,

  unsafeSpecifyNotNullValue,

  deriveComposite,

  unique,

  -- * Inferences
  HasColumnConstraint (columnConstraint),

  derivedUniqueColumnConstraint,
  derivedNotNullColumnConstraint,

  HasKeyConstraint (keyConstraint),

  derivedCompositePrimary,
  derivedUniqueConstraint
  ) where


-- | Proof object to specify table constraint
--   for table record type 'r' and constraint 'c'
--   specified by a single column.
newtype ColumnConstraint c r = ColumnConstraint Int

-- | Index of key which specifies table constraint.
index :: ColumnConstraint c r -> Int
index (ColumnConstraint i) = i

-- | Constraint type. Unique key.
data Unique

-- | Constraint type. Not-null key.
data NotNull

-- | Constraint type. Primary key.
data Primary

-- | Specialized unique constraint.
type UniqueColumnConstraint  = ColumnConstraint Unique

-- | Specialized not-null constraint.
type NotNullColumnConstraint = ColumnConstraint NotNull

-- | Specialized primary constraint.
type PrimaryColumnConstraint = ColumnConstraint Primary

-- | Unsafely generate 'ColumnConstraint' proof object using specified key index.
unsafeSpecifyColumnConstraint :: Int               -- ^ Key index which specify this constraint
                           -> ColumnConstraint c r -- ^ Result constraint proof object
unsafeSpecifyColumnConstraint =  ColumnConstraint

-- | Derivation rule for 'UniqueColumnConstraint'. Derive Unique from Primary.
uniqueColumn :: PrimaryColumnConstraint r -> UniqueColumnConstraint r
uniqueColumn =  unsafeSpecifyColumnConstraint . index

-- | Derivation rule for 'NotNullColumnConstraint'. Derive NotNull from Primary.
notNullColumn :: PrimaryColumnConstraint r -> NotNullColumnConstraint r
notNullColumn =  unsafeSpecifyColumnConstraint . index


-- | Derivation rule of 'ColumnConstraint' 'NotNull' for tuple (,) type.
leftColumnConstraint :: ColumnConstraint NotNull a -> ColumnConstraint NotNull (a, b)
leftColumnConstraint pa = ColumnConstraint (index pa)

-- | Interface of inference rule for 'ColumnConstraint' proof object.
class HasColumnConstraint c a where
  -- | Infer 'ColumnConstraint' proof object.
  columnConstraint :: ColumnConstraint c a

-- | Inference rule of 'ColumnConstraint' 'NotNull' for tuple (,) type.
instance HasColumnConstraint NotNull a => HasColumnConstraint NotNull (a, b) where
  columnConstraint = leftColumnConstraint columnConstraint

-- | Inferred 'UniqueColumnConstraint' proof object.
--   Record type 'r' has unique key which is derived 'r' has primary key.
derivedUniqueColumnConstraint :: HasColumnConstraint Primary r => UniqueColumnConstraint r
derivedUniqueColumnConstraint =  uniqueColumn columnConstraint

-- | Inferred 'NotNullColumnConstraint' proof object.
--   Record type 'r' has not-null key which is derived 'r' has primary key.
derivedNotNullColumnConstraint :: HasColumnConstraint Primary r => NotNullColumnConstraint r
derivedNotNullColumnConstraint =  notNullColumn columnConstraint


-- | Unsafely generate 'NotNullColumnConstraint' proof object of single column value.
unsafeSpecifyNotNullValue :: NotNullColumnConstraint a
unsafeSpecifyNotNullValue =  unsafeSpecifyColumnConstraint 0


-- | Proof object to specify table constraint
--   for table record type 'r' and constraint 'c'.
--   Constraint is specified by composite key.
newtype KeyConstraint c r = KeyConstraint [Int]

-- | Index of key which specifies table constraint.
indexes :: KeyConstraint c r -> [Int]
indexes (KeyConstraint is) = is

-- | Unsafely generate 'KeyConstraint' proof object using specified key indexes.
unsafeSpecifyKeyConstraint :: [Int]             -- ^ Key index which specify this constraint
                           -> KeyConstraint c r -- ^ Result constraint proof object
unsafeSpecifyKeyConstraint =  KeyConstraint

-- | Derivation rule for 'KeyConstraint'. Derive from 'ColumnConstraint'.
deriveComposite :: ColumnConstraint c r -> KeyConstraint c r
deriveComposite =  unsafeSpecifyKeyConstraint . (:[]) . index

-- | Specialized unique constraint.
type UniqueConstraint  = KeyConstraint Unique

-- | Specialized primary constraint.
type PrimaryConstraint = KeyConstraint Primary

-- | Derivation rule for 'UniqueConstraint'.
unique :: PrimaryConstraint r -> UniqueConstraint r
unique =  unsafeSpecifyKeyConstraint . indexes

-- | Interface of inference rule for 'KeyConstraint' proof object.
class HasKeyConstraint c a where
  -- | Infer 'ColumnConstraint' proof object.
  keyConstraint :: KeyConstraint c a

-- | Inferred 'KeyConstraint' proof object.
--   Record type 'r' has composite key which is derived 'r' has single column key.
derivedCompositeConstraint :: HasColumnConstraint c r => KeyConstraint c r
derivedCompositeConstraint =  deriveComposite columnConstraint

-- | Inferred 'PrimaryConstraint' proof object.
--   Record type 'r' has composite primary key which is derived 'r' has single column primary key.
derivedCompositePrimary :: HasColumnConstraint Primary r => PrimaryConstraint r
derivedCompositePrimary =  derivedCompositeConstraint

-- | Inferred 'UniqueConstraint' proof object.
--   Record type 'r' has unique key which is derived 'r' has primary key.
derivedUniqueConstraint :: HasKeyConstraint Primary r => UniqueConstraint r
derivedUniqueConstraint =  unique keyConstraint