/*
 * Copyright (C) 2012 - Juan Ferrer Toribio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "sql-select.h"

/**
 * SECTION: sql-select
 * @Short_description: an SQL SELECT statement 
 * @Title: SqlSelect
 * 
 * This object represents a SELECT SQL statement
 **/
G_DEFINE_TYPE (SqlSelect, sql_select, SQL_TYPE_DML);

SqlObject * sql_select_new ()
{
	return g_object_new (SQL_TYPE_SELECT, NULL);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private

static const char * SQL_SELECT_TYPE[] =
{
	 ""
	,"UNION ALL"
	,"UNION ANY"
	,"INTERSECT"
	,"EXCEPT"
};

static void sql_select_render (SqlSelect * obj, SqlRender * render)
{
	sql_render_add_list (render, TRUE, "SELECT", obj->fields, ",");
	sql_render_add_list (render, FALSE, "FROM", SQL_DML (obj)->targets, ",");

	if (SQL_DML (obj)->targets)
	{
		sql_render_add_item (render, FALSE, "WHERE", SQL_DML (obj)->where);
		sql_render_add_list (render, FALSE, "GROUP BY", obj->group, ",");
		sql_render_add_item (render, FALSE, "HAVING", obj->having);
		sql_render_add_list (render, FALSE, "ORDER", obj->order, ",");
		
		if (obj->limit_count)
			sql_render_printf (render, "LIMIT %u OFFSET %u"
				,obj->limit_count
				,obj->limit_offset
			);
	}
	
	if (obj->next)
		sql_render_add_item (render, FALSE, SQL_SELECT_TYPE[obj->type], obj->next);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Public

void sql_select_add_expr (SqlSelect * obj, SqlExpr * expr)
{
	g_return_if_fail (SQL_IS_SELECT (obj));
	g_return_if_fail (SQL_IS_EXPR (expr));
}

void sql_select_add_group (SqlSelect * obj, SqlExpr * expr)
{
	g_return_if_fail (SQL_IS_SELECT (obj));
	g_return_if_fail (SQL_IS_EXPR (expr));
}

void sql_select_set_having (SqlSelect * obj, SqlExpr * having)
{
	g_return_if_fail (SQL_IS_SELECT (obj));
	g_return_if_fail (SQL_IS_EXPR (having) || having);

	sql_object_remove (obj, obj->having);
	obj->having = sql_object_add (obj, having);
}

void sql_select_add_order (SqlSelect * obj, SqlExpr * expr, SqlSelectOrderWay way)
{
	g_return_if_fail (SQL_IS_SELECT (obj));
	g_return_if_fail (SQL_IS_EXPR (expr));
}

void sql_select_set_distinct (SqlSelect * obj, gboolean distinct)
{
	g_return_if_fail (SQL_IS_SELECT (obj));

	obj->distinct = distinct;
}

void sql_select_set_limit (SqlSelect * obj, guint count, guint offset)
{
	g_return_if_fail (SQL_IS_SELECT (obj));

	obj->limit_count = count;
	obj->limit_offset = offset;
}

void sql_select_set_next (SqlSelect * obj, SqlSelect * next, SqlSelectType type)
{
	g_return_if_fail (SQL_IS_SELECT (obj));
	g_return_if_fail (SQL_IS_SELECT (next) || !next);

	sql_object_remove (obj, obj->next);
	obj->next = sql_object_add (obj, next);
	obj->type = next ? type : SQL_SELECT_NONE;
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties

enum
{
	 PROP_DISTINCT = 1
	,PROP_FIELDS
	,PROP_GROUP
	,PROP_HAVING
	,PROP_ORDER
	,PROP_LIMIT_COUNT
	,PROP_LIMIT_OFFSET
	,PROP_TYPE
	,PROP_NEXT
};

static void sql_select_set_property (SqlSelect * obj, guint id,
	const GValue * value, GParamSpec * pspec)
{
	switch (id)
	{
		case PROP_DISTINCT:
			obj->distinct = g_value_get_boolean (value);
			break;
		case PROP_FIELDS:
			sql_object_remove (obj, obj->fields);
			obj->fields = sql_object_add (obj, g_value_get_object (value));
			break;
		case PROP_GROUP:
			sql_object_remove (obj, obj->group);
			obj->group = sql_object_add (obj, g_value_get_object (value));
			break;
		case PROP_HAVING:
			sql_select_set_having (obj, g_value_get_object (value));
			break;
		case PROP_ORDER:
			sql_object_remove (obj, obj->order);
			obj->order = sql_object_add (obj, g_value_get_object (value));
			break;
		case PROP_LIMIT_COUNT:
			obj->limit_count = g_value_get_uint (value);
			break;
		case PROP_LIMIT_OFFSET:
			obj->limit_offset = g_value_get_uint (value);
			break;
		case PROP_NEXT:
			sql_select_set_next (obj, g_value_get_object (value), obj->type);
			break;
		case PROP_TYPE:
			obj->type = g_value_get_int (value);
			break;
		default:		
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

static void sql_select_get_property (SqlSelect * obj, guint id,
	GValue * value, GParamSpec * pspec)
{	
	switch (id)
	{
		case PROP_DISTINCT:
			 g_value_set_boolean (value, obj->distinct);
			break;
		case PROP_FIELDS:
			g_value_set_object (value, obj->fields);
			break;
		case PROP_GROUP:
			g_value_set_object (value, obj->group);
			break;
		case PROP_HAVING:
			g_value_set_object (value, obj->having);
			break;
		case PROP_ORDER:
			g_value_set_object (value, obj->order);
			break;
		case PROP_LIMIT_COUNT:
			 g_value_set_uint (value, obj->limit_count);
			break;
		case PROP_LIMIT_OFFSET:
			g_value_set_uint (value, obj->limit_offset);
			break;
		case PROP_NEXT:
			g_value_set_object (value, obj->next);
			break;
		case PROP_TYPE:
			g_value_set_int (value, obj->type);
			break;
		default:		
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class

static void sql_select_init (SqlSelect * obj)
{
	obj->distinct = FALSE;
	obj->fields = NULL;
	obj->group = NULL;
	obj->having = NULL;
	obj->order = NULL;
	obj->next = NULL;
	obj->type = SQL_SELECT_NONE;
}

static void sql_select_finalize (SqlSelect * obj)
{
	g_object_unref (obj->group);
	g_object_unref (obj->order);
	sql_object_remove (obj, obj->fields);
	sql_object_remove (obj, obj->having);
	sql_object_remove (obj, obj->next);
	G_OBJECT_CLASS (sql_select_parent_class)->finalize (G_OBJECT (obj));
}

static void sql_select_class_init (SqlSelectClass * klass)
{
	GObjectClass * k = G_OBJECT_CLASS (klass);
	k->finalize = (GObjectFinalizeFunc) sql_select_finalize;
	k->set_property = (GObjectSetPropertyFunc) sql_select_set_property;
	k->get_property = (GObjectGetPropertyFunc) sql_select_get_property;
	SQL_OBJECT_CLASS (k)->render = (SqlRenderFunc) sql_select_render;

	g_object_class_install_property (k, PROP_LIMIT_COUNT,
		g_param_spec_boolean ("distinct"
			,"Distinct"
			,"Determines if the #SqlSelect uses the DISTINCT clause"
			,FALSE
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_FIELDS,
		sql_param_list ("fields"
			,"Fields"
			,"The list of fields"
			,SQL_TYPE_EXPR
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_GROUP,
		sql_param_object ("group"
			,"Group"
			,"The GROUP BY section"
			,SQL_TYPE_EXPR
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_HAVING,
		sql_param_object ("having"
			,"Having"
			,"The HAVING clause"
			,SQL_TYPE_EXPR
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_ORDER,
		sql_param_list ("order"
			,"Order"
			,"The ORDER BY section"
			,SQL_TYPE_SELECT_ORDER
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_LIMIT_COUNT,
		g_param_spec_uint ("limit-count"
			,"Limit count"
			,"The COUNT field of the LIMIT clause"
			,0, G_MAXUINT, 0
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_LIMIT_OFFSET,
		g_param_spec_uint ("limit-offset"
			,"Limit offset"
			,"The OFFSET field of the LIMIT clause"
			,0, G_MAXUINT, 0
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_NEXT,
		sql_param_object ("next"
			,"Next"
			,"The next #SqlSelect in case of a statement with more than one"
			,SQL_TYPE_SELECT
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_TYPE,
		g_param_spec_int ("type"
			,"Type"
			,"One of the possible options of #SqlSelectType"
			,0, SQL_SELECT_COUNT - 1, 0
			,G_PARAM_READWRITE
	));
}