-- |
-- Module      : Database.Relational.Query.Internal.Product
-- Copyright   : 2013 Kei Hibino
-- License     : BSD3
--
-- Maintainer  : ex8k.hibino@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- This module defines product structure to compose SQL join.
module Database.Relational.Query.Internal.Product (
  -- * Product tree type
  NodeAttr (..), ProductTree (..),
  Node, node, nodeAttr, nodeTree,
  growRight, -- growLeft,
  growProduct, product, restrictProduct,
  ) where

import Prelude hiding (and, product)
import Database.Relational.Query.Context (Flat)
import Database.Relational.Query.Expr (exprAnd)
import qualified Database.Relational.Query.Expr as Expr
import Data.Monoid ((<>))
import Data.Foldable (Foldable (foldMap))


type Expr = Expr.Expr Flat

-- | node attribute for product.
data NodeAttr = Just' | Maybe deriving Show

-- | Product tree type. Product tree is constructed by left node and right node.
data ProductTree q = Leaf q
                   | Join !(Node q) !(Node q) !(Maybe (Expr Bool))
                   deriving Show

-- | Product node. node attribute and product tree.
data Node q = Node !NodeAttr !(ProductTree q)  deriving Show

-- | Get node attribute.
nodeAttr :: Node q -> NodeAttr
nodeAttr (Node a _) = a  where

-- | Get tree from node.
nodeTree :: Node q -> ProductTree q
nodeTree (Node _ t) = t

-- | Foldable instance of ProductTree
instance Foldable ProductTree where
  foldMap f = rec  where
    rec (Leaf q) = f q
    rec (Join (Node _ lp) (Node _ rp) _ ) = rec lp <> rec rp

-- | Make product node from node attribute and product tree.
node :: NodeAttr      -- ^ Node attribute
     -> ProductTree q -- ^ Product tree
     -> Node q        -- ^ Result node
node =  Node

-- | Push new tree into product right term.
growRight :: Maybe (Node q)            -- ^ Current tree
          -> (NodeAttr, ProductTree q) -- ^ New tree to push into right
          -> Node q                    -- ^ Result node
growRight = d  where
  d Nothing  (naR, q) = node naR q
  d (Just l) (naR, q) = node Just' $ Join l (node naR q) Nothing

-- -- | Push new tree node into product left term.
-- growLeft :: Node q         -- ^ New node to push into left
--          -> NodeAttr       -- ^ Node attribute to replace rigth node attribute.
--          -> Maybe (Node q) -- ^ Current tree
--          -> Node q         -- ^ Result node
-- growLeft =  d  where
--   d q _naR Nothing  = q -- error is better?
--   d q naR  (Just r) = node Just' $ Join q (node naR (nodeTree r)) Nothing

-- | Push new leaf node into product right term.
growProduct :: Maybe (Node q) -- ^ Current tree
            -> (NodeAttr, q)  -- ^ New leaf to push into right
            -> Node q         -- ^ Result node
growProduct =  match  where
  match t (na, q) =  growRight t (na, Leaf q)

-- | Just make product of two node.
product :: Node q            -- ^ Left node
        -> Node q            -- ^ Right node
        -> Maybe (Expr Bool) -- ^ Join restriction
        -> ProductTree q     -- ^ Result tree
product =  Join

-- | Add restriction into top product of product tree.
restrictProduct' :: ProductTree q -- ^ Product to restrict
                 -> Expr Bool     -- ^ Restriction to add
                 -> ProductTree q -- ^ Result product
restrictProduct' =  d  where
  d (Join lp rp Nothing)   rs' = Join lp rp (Just rs')
  d (Join lp rp (Just rs)) rs' = Join lp rp (Just $ rs `exprAnd` rs')
  d leaf'@(Leaf _)         _   = leaf' -- or error on compile

-- | Add restriction into top product of product tree node.
restrictProduct :: Node q    -- ^ Target node which has product to restrict
                -> Expr Bool -- ^ Restriction to add
                -> Node q    -- ^ Result node
restrictProduct (Node a t) e = node a (restrictProduct' t e)