CREATE OR REPLACE FUNCTION get_time_clause(time_col_name NAME, time_col_type regtype, group_time BIGINT)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT CASE
       WHEN group_time IS NULL THEN
           format('%I', time_col_name)
       WHEN time_col_type IN ('BIGINT', 'INTEGER', 'SMALLINT') THEN
           format('(%1$I - (%1$I %% %2$L::%3$s))::%3$s', time_col_name, group_time, time_col_type)
       WHEN time_col_type IN ('TIMESTAMP', 'TIMESTAMPTZ') THEN
           --NOTE: note the conversion at the end. important for timestamp without timezone. right conversion?
           format('to_timestamp( (
                  (EXTRACT(EPOCH from %1$I)*1e6)::bigint - 
                  ( (EXTRACT(EPOCH from %1$I)*1e6)::bigint %% %2$L::bigint )
              )::double precision / 1e6
            )::%3$s', time_col_name, group_time, time_col_type) --group time is given in us
       END;
$BODY$;

CREATE OR REPLACE FUNCTION get_full_select_clause_nonagg(query ioql_query)
    RETURNS TEXT LANGUAGE SQL STABLE AS
$BODY$
SELECT format('SELECT %s', get_result_column_list_nonagg(query));
$BODY$;

CREATE OR REPLACE FUNCTION get_from_clause(crn chunk_replica_node)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT format('FROM %I.%I', crn.schema_name, crn.table_name);
$BODY$;

/* predicates */
CREATE OR REPLACE FUNCTION combine_predicates(VARIADIC clauses TEXT [])
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT string_agg(format('( %s )', clause.val), ' AND ')
FROM unnest(clauses) AS clause(val)
WHERE val IS NOT NULL;
$BODY$;

CREATE OR REPLACE FUNCTION get_time_predicate(time_col_name NAME, time_col_type regtype, cond time_condition_type)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT string_agg(clauses.val, ' AND ')
FROM (
       VALUES (format('%I >= ', time_col_name) ||  NULLIF(_sysinternal.time_literal_sql(cond.from_time, time_col_type), 'NULL')), 
              (format('%I < ', time_col_name)  ||  NULLIF(_sysinternal.time_literal_sql(cond.to_time, time_col_type), 'NULL'))
     ) AS clauses(val);
$BODY$;

--TODO: Review this
CREATE OR REPLACE FUNCTION get_select_field_predicate(items select_item [])
    RETURNS TEXT LANGUAGE SQL IMMUTABLE STRICT AS
$BODY$
SELECT string_agg(format('%I IS NOT NULL', field), ' AND ')
FROM unnest(items);
$BODY$;


CREATE OR REPLACE FUNCTION get_one_field_predicate_clause(field_pred field_predicate)
    RETURNS TEXT LANGUAGE SQL STABLE STRICT AS
$BODY$
SELECT format('(%1$I%2$s%3$L AND %1$I IS NOT NULL)', field_pred.field, field_pred.op, field_pred.constant)
$BODY$;

CREATE OR REPLACE FUNCTION get_field_predicate_clause(cond field_condition_type)
    RETURNS TEXT LANGUAGE SQL STABLE STRICT AS
$BODY$
SELECT '( ' || string_agg(get_one_field_predicate_clause(p), format(' %s ', cond.conjunctive)) || ' )'
FROM unnest(cond.predicates) AS p
$BODY$;

CREATE OR REPLACE FUNCTION get_constrained_partitioning_field_value(partitioning_field_name NAME,
                                                                    cond                    field_condition_type)
    RETURNS TEXT LANGUAGE SQL STABLE STRICT AS
$BODY$
SELECT p.constant
FROM unnest(cond.predicates) AS p
WHERE p.field = partitioning_field_name AND cond.conjunctive = 'AND' AND p.op = '='
$BODY$;

CREATE OR REPLACE FUNCTION get_partitioning_predicate(
    query ioql_query,
    epoch partition_epoch
)
    RETURNS TEXT LANGUAGE PLPGSQL IMMUTABLE STRICT AS
$BODY$
DECLARE
    keyspace_value SMALLINT;
    field_value    TEXT;
BEGIN
    field_value := get_constrained_partitioning_field_value(epoch.partitioning_field, query.field_condition);

    EXECUTE format($$ SELECT %s(%L, %L) $$, epoch.partitioning_func, field_value, epoch.partitioning_mod)
    INTO keyspace_value;

    IF field_value IS NOT NULL THEN
        RETURN format('%s(%I, %L) = %L',
                      epoch.partitioning_func,
                      epoch.partitioning_field,
                      epoch.partitioning_mod,
                      keyspace_value);
    END IF;
    RETURN NULL;
END
$BODY$;

CREATE OR REPLACE FUNCTION default_predicates(query ioql_query, epoch partition_epoch)
    RETURNS TEXT LANGUAGE SQL STABLE AS
$BODY$
SELECT combine_predicates(
    get_time_predicate(get_time_field(query.namespace_name), get_time_field_type(query.namespace_name), query.time_condition),
    get_field_predicate_clause(query.field_condition),
    get_select_field_predicate(query.select_items),
    get_partitioning_predicate(query, epoch)
);
$BODY$;

CREATE OR REPLACE FUNCTION get_where_clause(VARIADIC clauses TEXT [])
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT coalesce('WHERE ' || combine_predicates(VARIADIC clauses), '');
$BODY$;

CREATE OR REPLACE FUNCTION get_time_periods_limit_for_max(max_time BIGINT, period_length BIGINT, num_periods INT)
    RETURNS time_range LANGUAGE SQL STABLE AS
$BODY$
--todo unit test;
-- start and end inclusive
SELECT ROW (
       (max_time - (max_time % period_length)) - (period_length :: BIGINT * (num_periods - 1) :: BIGINT),
       max_time
) :: time_range;
$BODY$;

CREATE OR REPLACE FUNCTION get_groupby_clause(agg aggregate_type)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE STRICT AS
$BODY$
SELECT CASE
       WHEN agg IS NULL THEN
           NULL
       WHEN agg.group_field IS NOT NULL THEN
           format('GROUP BY %s, group_time', agg.group_field)
       ELSE
           'GROUP BY group_time'
       END;
$BODY$;


CREATE OR REPLACE FUNCTION get_orderby_clause_nonagg(query ioql_query)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT CASE
       WHEN query.limit_by_field IS NOT NULL THEN
           format('ORDER BY %I DESC NULLS LAST, %s', get_time_field(query.namespace_name), (query.limit_by_field).field)
	  ELSE
           format('ORDER BY %I DESC NULLS LAST', get_time_field(query.namespace_name))
       END;
$BODY$;

CREATE OR REPLACE FUNCTION get_orderby_clause_agg(query ioql_query, time_col TEXT = 'group_time')
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT CASE
       WHEN query.limit_rows IS NULL THEN
           NULL --no need to order if not gonna limit
       WHEN (query.aggregate).group_field IS NOT NULL THEN
           format('ORDER BY %s DESC NULLS LAST, %s',
                  time_col,
                  (query.aggregate).group_field) --group field needs to be included so that 5 aggregates across partitions overlap
       ELSE
           format('ORDER BY %s DESC NULLS LAST', time_col)
       END;
$BODY$;

CREATE OR REPLACE FUNCTION get_limit_clause(limit_count INT)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT 'LIMIT ' || limit_count
$BODY$;

CREATE OR REPLACE FUNCTION base_query_raw(select_clause   TEXT, from_clause TEXT, where_clause TEXT,
                                          group_by_clause TEXT, order_by_clause TEXT, limit_clause TEXT)
    RETURNS TEXT LANGUAGE SQL IMMUTABLE AS
$BODY$
SELECT format(
    $$
        %s
        %s
        %s
        %s
        %s
        %s
    $$,
    select_clause,
    from_clause,
    where_clause,
    group_by_clause,
    order_by_clause,
    limit_clause
);
$BODY$;
