{-# LANGUAGE CPP #-}
{-# LANGUAGE UndecidableInstances #-}
-- | This module implements an AST type for SQL92. It allows us to realize
--   the call structure of the builders defined in "Database.Beam.Backend.SQL.SQL92"
module Database.Beam.Backend.SQL.AST where

import Prelude hiding (Ordering)

import Database.Beam.Backend.SQL.SQL92
import Database.Beam.Backend.SQL.SQL99
import Database.Beam.Backend.SQL.SQL2003
import Database.Beam.Backend.SQL.Types

import Data.Text (Text)
import Data.ByteString (ByteString)
import Data.Time
import Data.Word (Word16, Word32, Word64)
import Data.Typeable
import Data.Int

data Command
  = SelectCommand Select
  | InsertCommand Insert
  | UpdateCommand Update
  | DeleteCommand Delete
  deriving (Show, Eq)

instance IsSql92Syntax Command where
  type Sql92SelectSyntax Command = Select
  type Sql92UpdateSyntax Command = Update
  type Sql92InsertSyntax Command = Insert
  type Sql92DeleteSyntax Command = Delete

  selectCmd = SelectCommand
  insertCmd = InsertCommand
  updateCmd = UpdateCommand
  deleteCmd = DeleteCommand

data Select
    = Select
    { selectTable :: SelectTable
    , selectOrdering   :: [ Ordering ]
    , selectLimit, selectOffset :: Maybe Integer }
    deriving (Show, Eq)

instance IsSql92SelectSyntax Select where
  type Sql92SelectSelectTableSyntax Select = SelectTable
  type Sql92SelectOrderingSyntax Select = Ordering

  selectStmt = Select

data SelectTable
  = SelectTable
  { selectQuantifier :: Maybe SetQuantifier
  , selectProjection :: Projection
  , selectFrom       :: Maybe From
  , selectWhere      :: Maybe Expression
  , selectGrouping   :: Maybe Grouping
  , selectHaving     :: Maybe Expression }
  | UnionTables Bool SelectTable SelectTable
  | IntersectTables Bool SelectTable SelectTable
  | ExceptTable Bool SelectTable SelectTable
  deriving (Show, Eq)

instance IsSql92SelectTableSyntax SelectTable where
  type Sql92SelectTableSelectSyntax SelectTable = Select
  type Sql92SelectTableExpressionSyntax SelectTable = Expression
  type Sql92SelectTableProjectionSyntax SelectTable = Projection
  type Sql92SelectTableFromSyntax SelectTable = From
  type Sql92SelectTableGroupingSyntax SelectTable = Grouping
  type Sql92SelectTableSetQuantifierSyntax SelectTable = SetQuantifier

  selectTableStmt = SelectTable
  unionTables = UnionTables
  intersectTables = IntersectTables
  exceptTable = ExceptTable

data Insert
  = Insert
  { insertTable :: Text
  , insertFields :: [ Text ]
  , insertValues :: InsertValues }
  deriving (Show, Eq)

instance IsSql92InsertSyntax Insert where
  type Sql92InsertValuesSyntax Insert = InsertValues

  insertStmt = Insert

data InsertValues
  = InsertValues
  { insertValuesExpressions :: [ [ Expression ] ] }
  | InsertSelect
  { insertSelectStmt :: Select }
  deriving (Show, Eq)

instance IsSql92InsertValuesSyntax InsertValues where
  type Sql92InsertValuesExpressionSyntax InsertValues = Expression
  type Sql92InsertValuesSelectSyntax InsertValues = Select

  insertSqlExpressions = InsertValues
  insertFromSql = InsertSelect

data Update
  = Update
  { updateTable :: Text
  , updateFields :: [ (FieldName, Expression) ]
  , updateWhere :: Maybe Expression }
  deriving (Show, Eq)

instance IsSql92UpdateSyntax Update where
  type Sql92UpdateFieldNameSyntax Update = FieldName
  type Sql92UpdateExpressionSyntax Update = Expression

  updateStmt = Update

data Delete
  = Delete
  { deleteTable :: Text
  , deleteWhere :: Maybe Expression }
  deriving (Show, Eq)

instance IsSql92DeleteSyntax Delete where
  type Sql92DeleteExpressionSyntax Delete = Expression

  deleteStmt = Delete

data FieldName
  = QualifiedField Text Text
  | UnqualifiedField Text
  deriving (Show, Eq)

instance IsSql92FieldNameSyntax FieldName where
  qualifiedField = QualifiedField
  unqualifiedField = UnqualifiedField

data ComparatorQuantifier
  = ComparatorQuantifierAny
  | ComparatorQuantifierAll
  deriving (Show, Eq)

instance IsSql92QuantifierSyntax ComparatorQuantifier where
  quantifyOverAll = ComparatorQuantifierAll
  quantifyOverAny = ComparatorQuantifierAny

data ExtractField
  = ExtractFieldTimeZoneHour
  | ExtractFieldTimeZoneMinute

  | ExtractFieldDateTimeYear
  | ExtractFieldDateTimeMonth
  | ExtractFieldDateTimeDay
  | ExtractFieldDateTimeHour
  | ExtractFieldDateTimeMinute
  | ExtractFieldDateTimeSecond
  deriving (Show, Eq)

data CastTarget
  = CastTargetDataType DataType
  | CastTargetDomainName Text
  deriving (Show, Eq)

data DataType
  = DataTypeChar Bool {- Varying -} (Maybe Word) (Maybe Text)
  | DataTypeNationalChar Bool (Maybe Word)
  | DataTypeBit Bool (Maybe Word)
  | DataTypeNumeric (Maybe (Word, Maybe Word))
  | DataTypeDecimal (Maybe (Word, Maybe Word))
  | DataTypeInteger
  | DataTypeSmallInt
  | DataTypeBigInt
  | DataTypeFloat (Maybe Word)
  | DataTypeReal
  | DataTypeDoublePrecision
  | DataTypeDate
  | DataTypeTime (Maybe Word) {- time fractional seconds precision -} Bool {- With time zone -}
  | DataTypeTimeStamp (Maybe Word) Bool
  | DataTypeInterval ExtractField
  | DataTypeIntervalFromTo ExtractField ExtractField
  | DataTypeBoolean

  | DataTypeBinaryLargeObject
  | DataTypeCharacterLargeObject

  | DataTypeArray DataType Int
  | DataTypeRow [ (Text, DataType) ]

  | DataTypeDomain Text
  deriving (Show, Eq)

instance IsSql92DataTypeSyntax DataType where
  domainType = DataTypeDomain
  charType = DataTypeChar False
  varCharType = DataTypeChar True
  nationalCharType = DataTypeNationalChar False
  nationalVarCharType = DataTypeNationalChar True
  bitType = DataTypeBit False
  varBitType = DataTypeBit True
  numericType = DataTypeNumeric
  decimalType = DataTypeDecimal
  intType = DataTypeInteger
  smallIntType = DataTypeSmallInt
  floatType = DataTypeFloat
  doubleType = DataTypeDoublePrecision
  realType = DataTypeReal

  dateType = DataTypeDate
  timeType = DataTypeTime
  timestampType = DataTypeTimeStamp

instance IsSql99DataTypeSyntax DataType where
  characterLargeObjectType = DataTypeCharacterLargeObject
  binaryLargeObjectType = DataTypeCharacterLargeObject
  booleanType = DataTypeBoolean
  arrayType = DataTypeArray
  rowType = DataTypeRow

instance IsSql2008BigIntDataTypeSyntax DataType where
  bigIntType = DataTypeBigInt

data SetQuantifier
  = SetQuantifierAll | SetQuantifierDistinct
  deriving (Show, Eq)

instance IsSql92AggregationSetQuantifierSyntax SetQuantifier where
  setQuantifierDistinct = SetQuantifierDistinct
  setQuantifierAll = SetQuantifierAll

data Expression
  = ExpressionValue Value
  | ExpressionDefault
  | ExpressionRow [ Expression ]

  | ExpressionIn Expression [ Expression ]

  | ExpressionIsNull Expression
  | ExpressionIsNotNull Expression
  | ExpressionIsTrue Expression
  | ExpressionIsNotTrue Expression
  | ExpressionIsFalse Expression
  | ExpressionIsNotFalse Expression
  | ExpressionIsUnknown Expression
  | ExpressionIsNotUnknown Expression

  | ExpressionCase [(Expression, Expression)] Expression
  | ExpressionCoalesce [Expression]
  | ExpressionNullIf Expression Expression

  | ExpressionFieldName FieldName

  | ExpressionBetween Expression Expression Expression
  | ExpressionBinOp Text Expression Expression
  | ExpressionCompOp Text (Maybe ComparatorQuantifier) Expression Expression
  | ExpressionUnOp Text Expression

  | ExpressionPosition Expression Expression
  | ExpressionCast Expression CastTarget
  | ExpressionExtract ExtractField Expression
  | ExpressionCharLength Expression
  | ExpressionOctetLength Expression
  | ExpressionBitLength Expression
  | ExpressionAbs Expression
  | ExpressionLower Expression
  | ExpressionUpper Expression
  | ExpressionTrim Expression

  | ExpressionFunctionCall Expression [ Expression ]
  | ExpressionInstanceField Expression Text
  | ExpressionRefField Expression Text

  | ExpressionCountAll
  | ExpressionAgg Text (Maybe SetQuantifier) [ Expression ]
  | ExpressionBuiltinFunction Text [ Expression ]

  | ExpressionSubquery Select
  | ExpressionUnique Select
  | ExpressionDistinct Select
  | ExpressionExists Select

  | ExpressionOver Expression WindowFrame

  | ExpressionCurrentTimestamp
  deriving (Show, Eq)

instance IsSqlExpressionSyntaxStringType Expression Text

instance IsSql92ExpressionSyntax Expression where
  type Sql92ExpressionQuantifierSyntax Expression = ComparatorQuantifier
  type Sql92ExpressionValueSyntax Expression = Value
  type Sql92ExpressionSelectSyntax Expression = Select
  type Sql92ExpressionFieldNameSyntax Expression = FieldName
  type Sql92ExpressionCastTargetSyntax Expression = CastTarget
  type Sql92ExpressionExtractFieldSyntax Expression = ExtractField

  valueE = ExpressionValue
  rowE = ExpressionRow

  isNullE = ExpressionIsNull
  isNotNullE = ExpressionIsNotNull
  isTrueE = ExpressionIsTrue
  isNotTrueE = ExpressionIsNotTrue
  isFalseE = ExpressionIsFalse
  isNotFalseE = ExpressionIsNotFalse
  isUnknownE = ExpressionIsUnknown
  isNotUnknownE = ExpressionIsNotUnknown

  caseE = ExpressionCase
  coalesceE = ExpressionCoalesce
  nullIfE = ExpressionNullIf
  positionE = ExpressionPosition
  extractE = ExpressionExtract
  castE = ExpressionCast

  fieldE = ExpressionFieldName

  betweenE = ExpressionBetween
  andE = ExpressionBinOp "AND"
  orE = ExpressionBinOp "OR"

  eqE = ExpressionCompOp "=="
  neqE = ExpressionCompOp "<>"
  ltE = ExpressionCompOp "<"
  gtE = ExpressionCompOp ">"
  leE = ExpressionCompOp "<="
  geE = ExpressionCompOp ">="
  addE = ExpressionBinOp "+"
  subE = ExpressionBinOp "-"
  mulE = ExpressionBinOp "*"
  divE = ExpressionBinOp "/"
  modE = ExpressionBinOp "%"
  likeE = ExpressionBinOp "LIKE"
  overlapsE = ExpressionBinOp "OVERLAPS"

  notE = ExpressionUnOp "NOT"
  negateE = ExpressionUnOp "-"

  charLengthE = ExpressionCharLength
  octetLengthE = ExpressionOctetLength
  bitLengthE = ExpressionBitLength
  absE = ExpressionAbs
  lowerE = ExpressionLower
  upperE = ExpressionUpper
  trimE = ExpressionTrim

  subqueryE = ExpressionSubquery
  uniqueE = ExpressionUnique
  existsE = ExpressionExists

  currentTimestampE = ExpressionCurrentTimestamp

  defaultE = ExpressionDefault
  inE = ExpressionIn

instance IsSql99ExpressionSyntax Expression where
  distinctE = ExpressionDistinct
  similarToE = ExpressionBinOp "SIMILAR TO"
  functionCallE = ExpressionFunctionCall
  instanceFieldE = ExpressionInstanceField
  refFieldE = ExpressionRefField

instance IsSql92AggregationExpressionSyntax Expression where
  type Sql92AggregationSetQuantifierSyntax Expression = SetQuantifier

  countAllE = ExpressionCountAll
  countE q = ExpressionAgg "COUNT" q . pure
  sumE q   = ExpressionAgg "SUM" q . pure
  minE q   = ExpressionAgg "MIN" q . pure
  maxE q   = ExpressionAgg "MAX" q . pure
  avgE q   = ExpressionAgg "AVG" q . pure

instance IsSql99AggregationExpressionSyntax Expression where
  everyE q = ExpressionAgg "EVERY" q . pure
  someE q  = ExpressionAgg "SOME" q . pure
  anyE q   = ExpressionAgg "ANY" q . pure

instance IsSql2003EnhancedNumericFunctionsExpressionSyntax Expression where
  lnE    = ExpressionBuiltinFunction "LN" . pure
  expE   = ExpressionBuiltinFunction "EXP" . pure
  sqrtE  = ExpressionBuiltinFunction "SQRT" . pure
  ceilE  = ExpressionBuiltinFunction "CEIL" . pure
  floorE = ExpressionBuiltinFunction "FLOOR" . pure
  powerE a b = ExpressionBuiltinFunction "POWER" [a, b]

instance IsSql2003EnhancedNumericFunctionsAggregationExpressionSyntax Expression where
  stddevPopE q  = ExpressionAgg "STDDEV_POP" q . pure
  stddevSampE q = ExpressionAgg "STDDEV_SAMP" q . pure
  varPopE q     = ExpressionAgg "VAR_POP" q . pure
  varSampE q    = ExpressionAgg "VAR_SAMP" q . pure

  covarPopE q a b      = ExpressionAgg "COVAR_POP" q [a, b]
  covarSampE q a b     = ExpressionAgg "COVAR_SAMP" q [a, b]
  corrE q a b          = ExpressionAgg "CORR" q [a, b]
  regrSlopeE q a b     = ExpressionAgg "REGR_SLOPE" q [a, b]
  regrInterceptE q a b = ExpressionAgg "REGR_INTERCEPT" q [a, b]
  regrCountE q a b     = ExpressionAgg "REGR_COUNT" q [a, b]
  regrRSquaredE q a b  = ExpressionAgg "REGR_R2" q [a, b]
  regrAvgXE q a b      = ExpressionAgg "REGR_AVGX" q [a, b]
  regrAvgYE q a b      = ExpressionAgg "REGR_AVGY" q [a, b]
  regrSXXE q a b       = ExpressionAgg "REGR_SXX" q [a, b]
  regrSXYE q a b       = ExpressionAgg "REGR_SXY" q [a, b]
  regrSYYE q a b       = ExpressionAgg "REGR_SYY" q [a, b]

instance IsSql2003NtileExpressionSyntax Expression where
  ntileE = ExpressionAgg "NTILE" Nothing . pure

instance IsSql2003LeadAndLagExpressionSyntax Expression where
  leadE x Nothing Nothing   = ExpressionAgg "LEAD" Nothing [x]
  leadE x (Just y) Nothing  = ExpressionAgg "LEAD" Nothing [x, y]
  leadE x (Just y) (Just z) = ExpressionAgg "LEAD" Nothing [x, y, z]
  leadE x Nothing (Just z)  = ExpressionAgg "LEAD" Nothing [x, ExpressionValue (Value (1 :: Int)), z]

  lagE x Nothing Nothing  = ExpressionAgg "LAG" Nothing [x]
  lagE x (Just y) Nothing = ExpressionAgg "LAG" Nothing [x, y]
  lagE x (Just y) (Just z) = ExpressionAgg "LAG" Nothing [x, y, z]
  lagE x Nothing (Just z)  = ExpressionAgg "LAG" Nothing [x, ExpressionValue (Value (1 :: Int)), z]

instance IsSql2003NthValueExpressionSyntax Expression where
  nthValueE a b = ExpressionAgg "NTH_VALUE" Nothing [a, b]

instance IsSql2003ExpressionSyntax Expression where
  type Sql2003ExpressionWindowFrameSyntax Expression = WindowFrame

  overE = ExpressionOver

newtype Projection
  = ProjExprs [ (Expression, Maybe Text ) ]
  deriving (Show, Eq)

instance IsSql92ProjectionSyntax Projection where
  type Sql92ProjectionExpressionSyntax Projection = Expression

  projExprs = ProjExprs

data Ordering
  = OrderingAsc Expression
  | OrderingDesc Expression
  deriving (Show, Eq)

instance IsSql92OrderingSyntax Ordering where
  type Sql92OrderingExpressionSyntax Ordering = Expression

  ascOrdering = OrderingAsc
  descOrdering = OrderingDesc

newtype Grouping = Grouping [ Expression ] deriving (Show, Eq)

instance IsSql92GroupingSyntax Grouping where
  type Sql92GroupingExpressionSyntax Grouping = Expression

  groupByExpressions = Grouping

data TableSource
  = TableNamed Text
  | TableFromSubSelect Select
  deriving (Show, Eq)

instance IsSql92TableSourceSyntax TableSource where
  type Sql92TableSourceSelectSyntax TableSource = Select

  tableNamed = TableNamed
  tableFromSubSelect = TableFromSubSelect

data From
  = FromTable TableSource (Maybe Text)
  | InnerJoin From From (Maybe Expression)
  | LeftJoin From From (Maybe Expression)
  | RightJoin From From (Maybe Expression)
  | OuterJoin From From (Maybe Expression)
  deriving (Show, Eq)

instance IsSql92FromSyntax From where
  type Sql92FromTableSourceSyntax From = TableSource
  type Sql92FromExpressionSyntax From = Expression

  fromTable = FromTable
  innerJoin = InnerJoin
  leftJoin = LeftJoin
  rightJoin = RightJoin

data Value where
  Value :: (Show a, Eq a, Typeable a) => a -> Value

#define VALUE_SYNTAX_INSTANCE(ty) instance HasSqlValueSyntax Value ty where { sqlValueSyntax = Value }
VALUE_SYNTAX_INSTANCE(Int)
VALUE_SYNTAX_INSTANCE(Int16)
VALUE_SYNTAX_INSTANCE(Int32)
VALUE_SYNTAX_INSTANCE(Int64)
VALUE_SYNTAX_INSTANCE(Word)
VALUE_SYNTAX_INSTANCE(Word16)
VALUE_SYNTAX_INSTANCE(Word32)
VALUE_SYNTAX_INSTANCE(Word64)
VALUE_SYNTAX_INSTANCE(Integer)
VALUE_SYNTAX_INSTANCE(String)
VALUE_SYNTAX_INSTANCE(Text)
VALUE_SYNTAX_INSTANCE(ByteString)
VALUE_SYNTAX_INSTANCE(LocalTime)
VALUE_SYNTAX_INSTANCE(UTCTime)
VALUE_SYNTAX_INSTANCE(Day)
VALUE_SYNTAX_INSTANCE(TimeOfDay)
VALUE_SYNTAX_INSTANCE(SqlNull)
VALUE_SYNTAX_INSTANCE(Double)
VALUE_SYNTAX_INSTANCE(Bool)

instance HasSqlValueSyntax Value x => HasSqlValueSyntax Value (Maybe x) where
  sqlValueSyntax (Just x) = sqlValueSyntax x
  sqlValueSyntax Nothing = sqlValueSyntax SqlNull

instance Eq Value where
  Value a == Value b =
    case cast a of
      Just a' -> a' == b
      Nothing -> False
instance Show Value where
  showsPrec prec (Value a) =
    showParen (prec > app_prec) $
    ("Value " ++ ).
    showsPrec (app_prec + 1) a
    where app_prec = 10

-- Window functions

data WindowFrame
  = WindowFrame
  { windowFramePartitions :: Maybe [Expression]
  , windowFrameOrdering   ::  Maybe [Ordering]
  , windowFrameBounds     :: Maybe WindowFrameBounds
  } deriving (Show, Eq)

instance IsSql2003WindowFrameSyntax WindowFrame where
  type Sql2003WindowFrameExpressionSyntax WindowFrame = Expression
  type Sql2003WindowFrameOrderingSyntax WindowFrame = Ordering
  type Sql2003WindowFrameBoundsSyntax WindowFrame = WindowFrameBounds

  frameSyntax = WindowFrame

data WindowFrameBounds
  = WindowFrameBounds
  { boundsFrom :: WindowFrameBound
  , boundsTo   :: Maybe WindowFrameBound
  } deriving (Show, Eq)

instance IsSql2003WindowFrameBoundsSyntax WindowFrameBounds where
  type Sql2003WindowFrameBoundsBoundSyntax WindowFrameBounds = WindowFrameBound

  fromToBoundSyntax = WindowFrameBounds

data WindowFrameBound
  = WindowFrameUnbounded
  | WindowFrameBoundNRows Int
  deriving (Show, Eq)

instance IsSql2003WindowFrameBoundSyntax WindowFrameBound where
  unboundedSyntax = WindowFrameUnbounded
  nrowsBoundSyntax = WindowFrameBoundNRows