{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Language.SQL.Keyword.Concat
-- Copyright   : 2013 Kei Hibino
-- License     : BSD3
--
-- Maintainer  : ex8k.hibino@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- Concatinations on 'Keyword' types
module Language.SQL.Keyword.Concat (
  -- * List concatination functions
  -- $listConcatination
  unwords',

  sepBy, parenSepBy,

  -- * Binary operators
  -- $binaryOperators
  defineBinOp,

  strBinOp,

  as, (<.>), (|*|),

  (.||.),
  (.=.), (.<.), (.<=.), (.>.), (.>=.), (.<>.),
  and, or, in',
  (<++>),

  fold,

  -- * Unary operator
  defineUniOp, paren,
  strUniOp
  ) where

import Prelude hiding (and, or, not)
import Data.List (intersperse)
import Data.Monoid (mempty, mconcat, (<>))

import Language.SQL.Keyword.Internal.Type (Keyword (..), word, wordShow, toDString, fromDString)


{- $listConcatination
Functions to concatinate 'Keyword' list.
-}

-- | Separate 'Keyword' list with delimiter 'Keyword' and map to 'String' list.
sepBy' :: [Keyword] -> Keyword -> [String]
ws `sepBy'` d =  map wordShow . intersperse d $ ws

-- | Concatinate 'Keyword' list like unwords on 'String' list.
unwords' :: [Keyword] -> Keyword
unwords' =  mconcat

-- | Concatinate 'String' list into one 'Keyword'.
concatStr :: [String] -> Keyword
concatStr =  word . concat

-- | Separate 'Keyword' list with delimiter 'Keyword' and concatinate into one 'Keyword'.
sepBy :: [Keyword] -> Keyword -> Keyword
ws `sepBy` d = concatStr $ ws `sepBy'` d

-- | Do 'sepBy' and enclose by paren
parenSepBy :: [Keyword] -> Keyword -> Keyword
ws `parenSepBy` d = concatStr $ "(" : (ws `sepBy'` d) ++ [")"]

{- $binaryOperators
Binary operators on SQL. Result is concatinated into one 'Keyword'.
-}

-- | Directly concatinate SQL string without whitespaces.
(<++>) :: Keyword -> Keyword -> Keyword
x <++> y = fromDString $ toDString x <> toDString y

concat' :: [Keyword] -> Keyword
concat' =  fromDString . mconcat . map toDString

-- | Define binary operator on 'Keyword' type.
--   Result is not delimited by whitespace like concat on 'String' list.
defineBinOp' :: Keyword -> Keyword -> Keyword -> Keyword
defineBinOp' op a b = concat' [a, op, b]

-- | Define binary operator on 'Keyword' type.
--   Result is delimited by whitespace like unwords on 'String' list.
defineBinOp :: Keyword -> Keyword -> Keyword -> Keyword
defineBinOp op a b = mconcat [a, op, b]

-- | Binary operator to create qualified name on SQL.
(<.>) :: Keyword -> Keyword -> Keyword
(<.>)  =  defineBinOp' "."

-- | Binary operator to create comma separated words.
(|*|) :: Keyword -> Keyword -> Keyword
(|*|)  =  defineBinOp' ", "

-- | Binary operator for SQL string expression concatination.
(.||.) :: Keyword -> Keyword -> Keyword
(.||.) =  defineBinOp "||"

-- | Binary eq operator for SQL expression.
(.=.) :: Keyword -> Keyword -> Keyword
(.=.)  =  defineBinOp "="

-- | Binary not eq operator for SQL expression.
(.<>.) :: Keyword -> Keyword -> Keyword
(.<>.) =  defineBinOp "<>"

-- | Binary lt operator for SQL expression.
(.<.) :: Keyword -> Keyword -> Keyword
(.<.)  =  defineBinOp "<"

-- | Binary le operator for SQL expression.
(.<=.) :: Keyword -> Keyword -> Keyword
(.<=.) =  defineBinOp "<="

-- | Binary gt operator for SQL expression.
(.>.) :: Keyword -> Keyword -> Keyword
(.>.)  =  defineBinOp ">"

-- | Binary ge operator for SQL expression.
(.>=.) :: Keyword -> Keyword -> Keyword
(.>=.) =  defineBinOp ">="

-- | Binary operator for SQL name alias.
as :: Keyword -> Keyword -> Keyword
as     =  defineBinOp AS

-- | Binary `AND` operator for SQL boolean expression.
and :: Keyword -> Keyword -> Keyword
and    =  defineBinOp AND

-- | Binary `OR` operator for SQL boolean expression.
or :: Keyword -> Keyword -> Keyword
or     =  defineBinOp OR

-- | Fold operation using binary operator with empty result of zero length case.
fold :: (Keyword -> Keyword -> Keyword) -- ^ Binary operator used in fold
     -> [Keyword]                       -- ^ List to fold
     -> Keyword                         -- ^ Result
fold op =  d  where
  d []       = mempty
  d xs@(_:_) = foldr1 op xs

-- | Define unary operator on 'Keyword' type represeted by specified 'Keyword'.
--   Result is delimited by whitespace like unwords on 'String' list.
defineUniOp :: Keyword -> Keyword -> Keyword
defineUniOp op e = mconcat [op, e]

-- | Uni operator to create Parend words.
paren :: Keyword -> Keyword
paren w = concat' ["(", w, ")"]

-- | Binary `IN` operator for SQL.
in' :: Keyword -> Keyword -> Keyword
in'    =  defineBinOp IN

infixr 6 <++>
infixr 5 .||.
infixr 4 .=., .<., .<=., .>., .>=., .<>.
infix  4 `in'`
infixr 3 `and`
infixr 2 `or`
infixr 1 |*|

-- | Define uni operator of string from 'Keyword' uni operator.
strUniOp :: (Keyword -> Keyword) -> String -> String
strUniOp u = wordShow . u . word

-- | Define binary operator of string from 'Keyword' binary operator.
strBinOp :: (Keyword -> Keyword -> Keyword) -> String -> String -> String
strBinOp op a b = wordShow $ op (word a) (word b)