type query = 
  (*| Select of column list * table list option * filter option*)
  | Select of column list * table_expression
  | CreateSchema of string
  | CreateTable of table_scope option * table
  | DropSchema of string
  | DropTable of table
  | DropColumn of string
and table_scope = 
  | Global
  | Local
and column = 
  | Asterisk
  | Column of expression_primary * as_clause option
and as_clause =
  | As of string
and table_expression =
  | TableExpression of table list option * filter option * group option
and table = 
  | Table of string
  | Join of table * join_type * table * condition option
and join_type =
  | Inner
  | Left
  | Right
  | Full
  | Cross
  | Union
  | Natural
and condition = 
  | Condition of expression_primary * predicate
  | And of condition * condition
  | Or of condition * condition
  | Not of condition
and predicate = 
  | Comparison of operator * expression_primary
  | Between of expression_primary * expression_primary
  | NotBetween of expression_primary * expression_primary
  | In of expression_primary list
  | NotIn of expression_primary list
  | Like of expression_primary  
  | NotLike of expression_primary
and operator =
  | Equals
  | NotEquals
  | LessThan
  | GreaterThan
  | LessEquals
  | GreaterEquals
and filter = condition
and group =
  | Group of quantifier option * expression_primary list option
and aggregate = 
  | Aggregate of func * filter option
and func =
  | Function of function_type * quantifier option * expression_primary
and function_type = 
  | Avg
  | Max
  | Min
  | Sum
  | Count
and quantifier = 
  | All
  | Distinct
and expression_primary = 
  | Ref of string
  | StringLiteral of string
  | DateLiteral of string
  | TimeLiteral of string
  | TimestampLiteral of string
  | IntegerLiteral of int
  | FloatLiteral of float
  | Concatenation of expression_primary * expression_primary
  | Numeric of expression_primary * sign * expression_primary
  | Signed of sign * expression_primary
  | Substring of expression_primary * expression_primary
and sign =
  | Plus 
  | Minus
  | Times
  | Divide


let rec pp_query fmt ast = 
  match ast with
  | Select(cols, table_exp) ->  Format.fprintf fmt "%s" (String.cat "SELECT " (pp_columns cols) ^ pp_table_expression table_exp)
  | _ -> failwith "not yet supported"

and pp_columns cols =
  match cols with
  | [] -> "" 
  | [col] -> pp_column col
  | col::l -> pp_column col ^ ", " ^ pp_columns l

and pp_column col = 
  match col with
  | Column(exp,_) -> pp_expression exp
  | Asterisk -> "*"

and pp_expression exp = 
  match exp with 
  | Ref(name) -> name
  | StringLiteral(s) -> "'" ^ s ^ "'"
  | DateLiteral(d) -> "'" ^ d ^ "'"
  | TimeLiteral(t) -> "'" ^ t ^ "'"
  | TimestampLiteral(ts) -> "'" ^ ts ^ "'" 
  | IntegerLiteral(i) -> string_of_int i
  | FloatLiteral(f) -> string_of_float f
  | Numeric(n1, s, n2) -> pp_expression n1 ^ pp_sign s ^ pp_expression n2
  | _ -> "Expression not yet supported"

and pp_sign sign =
  match sign with 
  | Plus -> "+"
  | Minus -> "-"
  | Times -> "*"
  | Divide -> "/"

and pp_table_expression table_exp =
  match table_exp with
  | TableExpression(tables, filter, _) -> pp_tables tables ^ pp_filter filter

and pp_tables tables =
  let rec aux t = 
    match t with
    | [] -> ""
    | table::[] -> pp_table table
    | table::l -> pp_table table ^ ", " ^ aux l
  in
  match tables with
  | None -> ""
  | Some(t) -> " FROM " ^ aux t

and pp_table table =
  match table with
  | Table(table) -> table
  | Join(t1, j, t2, _) -> pp_table t1 ^ pp_join_type j ^ pp_table t2

and pp_join_type j =
  match j with
  | Inner -> "inner"
  | Left -> "left"
  | Right -> "right"
  | Full -> "full"
  | Cross -> "cross"
  | Union -> "union"
  | Natural -> "natural"

and pp_filter filter =
  let rec aux f = 
    match f with
    | Condition(exp, pred) -> pp_expression exp ^ " " ^ pp_predicate pred
    | And(c1, c2) -> aux c1 ^ " AND " ^ aux c2 
    | Or(c1, c2) -> aux c1 ^ " OR " ^ aux c2 
    | Not(c) -> "NOT" ^ aux c
  in
  match filter with
  | None -> ""
  | Some(f) -> " WHERE " ^ aux f

and pp_predicate pred =
  match pred with
  | Comparison(op, exp) -> pp_operator op ^ pp_expression exp 
  | Between(exp1, exp2) -> "BETWEEN " ^ pp_expression exp1 ^ " AND " ^pp_expression exp2
  | NotBetween(exp1, exp2) -> "NOT BETWEEN " ^ pp_expression exp1 ^ " AND " ^pp_expression exp2
  (*| Like(exp) -> "LIKE " ^ pp_expression exp
  | NotLike(exp) -> " NOT LIKE " ^ pp_expression exp*)
  | _ -> failwith "Predicate not supported"

and pp_operator op = 
  match op with 
  | Equals -> "="
  | NotEquals -> "!="
  | LessThan -> "<"
  | GreaterThan -> ">"
  | LessEquals -> "<="
  | GreaterEquals -> ">="