/*
 * filter.c:
 *
 * Author:
 *        Jukka-Pekka Iivonen <jiivonen@hutcs.cs.hut.fi>
 *
 * (C) Copyright 2000, 2001, 2002 by Jukka-Pekka Iivonen <iivonen@iki.fi>
 *
 * 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 2 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 <https://www.gnu.org/licenses/>.
 */

#include <gnumeric-config.h>
#include <glib/gi18n-lib.h>
#include <gnumeric.h>

#include <sheet.h>
#include <sheet-filter.h>
#include <workbook-control.h>
#include <cell.h>
#include <ranges.h>
#include <value.h>
#include <selection.h>
#include <criteria.h>

#include <tools/filter.h>
#include <tools/analysis-tools.h>

G_DEFINE_TYPE (GnmAdvancedFilterTool, gnm_advanced_filter_tool, GNM_TYPE_GENERIC_B_ANALYSIS_TOOL)

enum {
	ADVANCED_FILTER_PROP_0,
	ADVANCED_FILTER_PROP_UNIQUE_ONLY_FLAG
};

static void
gnm_advanced_filter_tool_set_property (GObject *object, guint property_id,
				       GValue const *value, GParamSpec *pspec)
{
	GnmAdvancedFilterTool *tool = GNM_ADVANCED_FILTER_TOOL (object);

	switch (property_id) {
	case ADVANCED_FILTER_PROP_UNIQUE_ONLY_FLAG:
		tool->unique_only_flag = g_value_get_boolean (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
gnm_advanced_filter_tool_get_property (GObject *object, guint property_id,
				       GValue *value, GParamSpec *pspec)
{
	GnmAdvancedFilterTool *tool = GNM_ADVANCED_FILTER_TOOL (object);

	switch (property_id) {
	case ADVANCED_FILTER_PROP_UNIQUE_ONLY_FLAG:
		g_value_set_boolean (value, tool->unique_only_flag);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
gnm_advanced_filter_tool_init (GnmAdvancedFilterTool *tool)
{
	tool->unique_only_flag = FALSE;
}

static gboolean
gnm_advanced_filter_tool_update_dao (GnmAnalysisTool *tool, data_analysis_output_t *dao)
{
	GnmGenericBAnalysisTool *gtool = GNM_GENERIC_B_ANALYSIS_TOOL (tool);
	int rows, cols;
	rows = gtool->base.range_1->v_range.cell.b.row
		- gtool->base.range_1->v_range.cell.a.row + 1;
	cols = gtool->base.range_1->v_range.cell.b.col
		- gtool->base.range_1->v_range.cell.a.col + 1;
	if (cols < 2)
		cols = 2;
	dao_adjust (dao, cols, 3 + rows);
	return FALSE;
}

static char *
gnm_advanced_filter_tool_update_descriptor (G_GNUC_UNUSED GnmAnalysisTool *tool, data_analysis_output_t *dao)
{
	return dao_command_descriptor (dao, _("Advanced Filter (%s)"));
}

static gboolean
gnm_advanced_filter_tool_prepare_output_range (G_GNUC_UNUSED GnmAnalysisTool *tool, WorkbookControl *wbc, data_analysis_output_t *dao)
{
	dao_prepare_output (wbc, dao, _("Advanced Filter"));
	return FALSE;
}

static gboolean
gnm_advanced_filter_tool_format_output_range (G_GNUC_UNUSED GnmAnalysisTool *tool, WorkbookControl *wbc, data_analysis_output_t *dao)
{
	return dao_format_output (wbc, dao, _("Advanced Filter"));
}

static void
filter (data_analysis_output_t *dao, Sheet *sheet, GSList *rows,
	gint input_col_b, gint input_col_e, gint input_row_b, gint input_row_e)
{
        GnmCell *cell;
	int  i, r=0;

	if (dao->type == GNM_DAO_OUTPUT_INPLACE) {
		sheet->has_filtered_rows = TRUE;
		colrow_set_visibility (sheet, FALSE,
				       FALSE, input_row_b+1, input_row_e);
		for (i=input_row_b; i<=input_row_e; i++) {
			ColRowInfo *ri = sheet_row_fetch (sheet, i);
			ri->in_advanced_filter = TRUE;
		}
		while (rows != NULL) {
			gint row = GPOINTER_TO_INT (rows->data);
			colrow_set_visibility (sheet, FALSE, TRUE, row, row);
			rows = rows->next;
		}
		sheet_redraw_all (sheet, TRUE);
/* FIXME: what happens if we just have hidden the selection? */

	} else {
		for (i=input_col_b; i<=input_col_e; i++) {
			cell = sheet_cell_get (sheet, i, input_row_b);
			if (cell == NULL)
				dao_set_cell (dao, i - input_col_b, r, NULL);
			else {
				GnmValue *value = value_dup (cell->value);
				dao_set_cell_value (dao, i - input_col_b, r,
						    value);
			}
		}
		++r;

		while (rows != NULL) {
			gint row = GPOINTER_TO_INT (rows->data);
			for (i=input_col_b; i<=input_col_e; i++) {
				cell = sheet_cell_get (sheet, i, row);
				if (cell == NULL)
					dao_set_cell (dao, i - input_col_b, r,
						      NULL);
				else {
					GnmValue *value =
						value_dup (cell->value);
					dao_set_cell_value (dao,
							    i - input_col_b, r,
							    value);
				}
			}
			++r;
			rows = rows->next;
		}
	}
}

static gboolean
gnm_advanced_filter_tool_perform_calc (GnmAnalysisTool *tool, WorkbookControl *wbc, data_analysis_output_t *dao)
{
	GnmAdvancedFilterTool *atool = GNM_ADVANCED_FILTER_TOOL (tool);
	GnmGenericBAnalysisTool *gtool = &atool->parent;
	GnmRange range;
	char *name;
	GnmValue  *database = gtool->base.range_1;
	GnmValue  *criteria = gtool->base.range_2;
	gint err = analysis_tools_noerr;
        GSList  *crit, *rows;
	GnmEvalPos ep;

	dao_set_italic (dao, 0, 0, 0, 2);
	set_cell_text_col (dao, 0, 0, _("/Advanced Filter:"
					"/Source Range:"
					"/Criteria Range:"));
	range_init_value (&range, database);
	name = global_range_name (database->v_range.cell.a.sheet, &range);
	dao_set_cell (dao, 1, 1, name);
	g_free (name);
	range_init_value (&range, criteria);
	name = global_range_name (criteria->v_range.cell.a.sheet, &range);
	dao_set_cell (dao, 1, 2, name);
	g_free (name);

	dao->offset_row = 3;

	crit = gnm_criteria_parse_database (
		eval_pos_init_sheet (&ep, dao->dst_sheet),
		database, criteria);

	if (crit == NULL) {
		err = analysis_tools_invalid_field;
		goto finish;
	}

	rows = gnm_criteria_match (database->v_range.cell.a.sheet,
				     database->v_range.cell.a.col,
				     database->v_range.cell.a.row + 1,
				     database->v_range.cell.b.col,
				     database->v_range.cell.b.row,
				     crit, atool->unique_only_flag);

	gnm_criteria_list_free (crit);

	if (rows == NULL) {
		err = analysis_tools_no_records_found;
		goto finish;
	}

	filter (dao, database->v_range.cell.a.sheet, rows,
		database->v_range.cell.a.col,
		database->v_range.cell.b.col, database->v_range.cell.a.row,
		database->v_range.cell.b.row);

finish:
	if (err != analysis_tools_noerr) {
		dao_set_merge (dao, 0,0, 1, 0);
		if (err == analysis_tools_no_records_found)
			dao_set_cell (dao, 0, 0, _("No matching records were found."));
		else if (err == analysis_tools_invalid_field)
			dao_set_cell (dao, 0, 0, _("The given criteria are invalid."));
		else
			dao_set_cell_printf (dao, 0, 0,
					     _("An unexpected error has occurred: "
					       "%d."), err);
	}

	dao_redraw_respan (dao);

	return analysis_tools_noerr;
}

static void
gnm_advanced_filter_tool_class_init (GnmAdvancedFilterToolClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	GnmAnalysisToolClass *at_class = GNM_ANALYSIS_TOOL_CLASS (klass);

	gobject_class->set_property = gnm_advanced_filter_tool_set_property;
	gobject_class->get_property = gnm_advanced_filter_tool_get_property;

	at_class->update_dao = gnm_advanced_filter_tool_update_dao;
	at_class->update_descriptor = gnm_advanced_filter_tool_update_descriptor;
	at_class->prepare_output_range = gnm_advanced_filter_tool_prepare_output_range;
	at_class->format_output_range = gnm_advanced_filter_tool_format_output_range;
	at_class->perform_calc = gnm_advanced_filter_tool_perform_calc;

	g_object_class_install_property (gobject_class,
		ADVANCED_FILTER_PROP_UNIQUE_ONLY_FLAG,
		g_param_spec_boolean ("unique-only-flag", NULL, NULL,
				      FALSE, G_PARAM_READWRITE));
}

GnmAnalysisTool *
gnm_advanced_filter_tool_new (void)
{
	return g_object_new (GNM_TYPE_ADVANCED_FILTER_TOOL, NULL);
}

/*
 * Advanced Filter tool.
 */
gint
advanced_filter (WorkbookControl        *wbc,
		 data_analysis_output_t *dao,
		 GnmValue               *database,
		 GnmValue               *criteria,
		 gboolean               unique_only_flag)
{
        GSList  *crit, *rows;
	GnmEvalPos ep;
	GnmRange r, s;
	SheetView *sv;
	Sheet *sheet = criteria->v_range.cell.a.sheet;

	/* I don't like this -- minimal fix for now.  509427.  */
	if (!VALUE_IS_CELLRANGE (criteria))
		return analysis_tools_invalid_field;

	crit = gnm_criteria_parse_database (
		eval_pos_init_sheet (&ep, wb_control_cur_sheet (wbc)),
		database, criteria);

	if (crit == NULL)
		return analysis_tools_invalid_field;

	rows = gnm_criteria_match (sheet,
				     database->v_range.cell.a.col,
				     database->v_range.cell.a.row + 1,
				     database->v_range.cell.b.col,
				     database->v_range.cell.b.row,
				     crit, unique_only_flag);

	gnm_criteria_list_free (crit);

	if (rows == NULL)
		return analysis_tools_no_records_found;

	dao_prepare_output (wbc, dao, _("Filtered"));

	filter (dao, sheet, rows,
		database->v_range.cell.a.col,
		database->v_range.cell.b.col, database->v_range.cell.a.row,
		database->v_range.cell.b.row);

	sv = sheet_get_view (sheet, wb_control_view (wbc));
	s = r = *(selection_first_range (sv, NULL, NULL));
	r.end.row = r.start.row;
	sv_selection_reset (sv);
	sv_selection_add_range (sv, &r);
	sv_selection_add_range (sv, &s);

	wb_control_menu_state_update (wbc, MS_FILTER_STATE_CHANGED);

	return analysis_tools_noerr;
}

static gboolean
cb_show_all (GnmColRowIter const *iter, Sheet *sheet)
{
	if (iter->cri->in_advanced_filter) {
		ColRowInfo *ri = sheet_row_fetch (sheet, iter->pos);
		if (!iter->cri->visible)
			colrow_set_visibility (sheet, FALSE, TRUE,
					       iter->pos, iter->pos);
		ri->in_advanced_filter = FALSE;
	}
	return FALSE;
}

void
filter_show_all (WorkbookControl *wbc)
{
	Sheet *sheet = wb_control_cur_sheet (wbc);

	/* FIXME: This is slow. We should probably have a linked list
	 * containing the filtered rows in the sheet structure. */
	sheet_colrow_foreach (sheet, FALSE, 0, -1,
			(ColRowHandler) cb_show_all, sheet);
	sheet->has_filtered_rows = FALSE;
	sheet_redraw_all (sheet, TRUE);

	wb_control_menu_state_update (wbc, MS_FILTER_STATE_CHANGED);
}
