/** -*- Mode: C++; tab-width: 4 -*-
 * vim: sw=4 ts=4:
 *
 * Gnome Apt package search class
 *
 * 	(C) 1998 Havoc Pennington <hp@pobox.com>
 * 	    2002-2004 Filip Van Raemdonck <mechanix@debian.org>
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * 	$Id$
 *
 **/

using namespace std;

#include <sys/types.h>
#include <regex.h>
#include <algorithm>
#include <string>

#include "cache.h"
#include "gnome-apt.h"
#include "pkgutil.h"
#include "search.h"

void SearchPattern::search (GAptCacheFile* cachefile, vector<pkgCache::Package*>& hits) {
  g_return_if_fail(cachefile != 0);

  if (regexps_.size() == 0) 
    return; // no matches for that!

  GAptCache* cache = cachefile->cache();

  g_return_if_fail(cache != 0);

  vector<regex_t*> res;

  vector<string>::const_iterator j = regexps_.begin();
  while (j != regexps_.end()) {
    int flags = 0x0;
    flags |= REG_EXTENDED | REG_NOSUB;
    if (case_sensitive_ == false) 
      {
        flags |= REG_ICASE;
      }

    regex_t* re = new regex_t;

    int err = regcomp(re, (*j).c_str(), flags);

    if (err != 0) {
      gchar buf[1024];
      regerror(err, re, buf, 1023);
      g_warning(buf);
      delete re;
      ++j;
      continue;
    }
    
    res.push_back(re);
    
    ++j;
  }
  
  if (res.empty()) return;

  pkgCache::PkgIterator i = cache->PkgBegin();
  while (!i.end()) {
		bool found = false;
		pkgRecords::Parser* parser = cache->pkgParser (i);

		if (!parser) {
			++i;
			continue;
		}

    int q = 0;
    while (q < TextFieldTypeEnd) {
      TextFieldType tft = static_cast<TextFieldType>(q);

      if (!get_textfield(tft))
        {
          ++q;
          continue;
        }

      const gchar* field = 0;

			if (tft == Package) {
				field = i.Name();
			} else if (tft == Maintainer) {
				field = parser->Maintainer().c_str();
			/* } else if (tft == Conffiles) {
				Not implemented */
			} else if (tft == ShortDescription) {
				field = parser->ShortDesc().c_str();
			} else if (tft == LongDescription) {
				field = parser->LongDesc().c_str();
			}

      if (field) {
        vector<regex_t*>::iterator r = res.begin();      
        while (r != res.end()) {
          int match = regexec(*r, field, 0, 0, 0);

          if (match == 0) {
            // match! 
						found = true;
          }
          else if (match == REG_NOMATCH) {
						/* Nothing */
          }
          else {
            gchar buf[1024];
            regerror(match, *r, buf, 1023);
            g_warning(buf);
          }
          ++r;
        }        
      }
      ++q;
    }
 
		if (found) {
			ga_debug ("Found match %s", i.Name());
			hits.push_back ((pkgCache::Package*) i);
    }
    ++i;
  }

  vector<regex_t*>::iterator x = res.begin();
  while (x != res.end()) {
    regfree(*x);
    delete *x;
    ++x;
  }
}

enum { GS_FILTER_CHANGED, GS_LAST_SIGNAL };
static guint gapt_search_signals[GS_LAST_SIGNAL] = { 0 };

static void
run_find_cb (GtkWidget* wdg, gpointer data) {
	GAptSearch* s = static_cast<GAptSearch*> (data);
	g_return_if_fail (s != 0);

	gdk_window_set_cursor (GTK_WIDGET (wdg)->window, gdk_cursor_new (GDK_WATCH));
	gdk_flush();

	s->run();

	gdk_window_set_cursor (GTK_WIDGET (wdg)->window, NULL);
	gdk_flush();

	g_signal_emit (G_OBJECT (s), gapt_search_signals[GS_FILTER_CHANGED], 0);
}

static void gapt_search_class_init (GAptSearchClass*);
static void gapt_search_init (GAptSearch*);

GType
gapt_search_get_type (void) {
	static GType gas_type = 0;

	if (!gas_type) {
		static const GTypeInfo gas_info = {
			sizeof (GAptSearchClass), NULL, NULL,
			(GClassInitFunc) gapt_search_class_init,
			NULL, NULL, sizeof (GAptSearch), 0,
			(GInstanceInitFunc) gapt_search_init
		};

		gas_type = g_type_register_static (GTK_TYPE_TABLE, "GAptSearch", &gas_info, (GTypeFlags) 0);
	}
	return gas_type;
}

static void
gapt_search_finalize (GObject* obj) {
	delete GAPT_SEARCH (obj)->s_needle;
}

static void
gapt_search_class_init (GAptSearchClass* sc) {
	gapt_search_signals[GS_FILTER_CHANGED] = g_signal_new (
	      "filter-changed", G_TYPE_FROM_CLASS (sc),
	      GSignalFlags (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
	      G_STRUCT_OFFSET (GAptSearchClass, filter_changed), NULL, NULL,
	      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

	G_OBJECT_CLASS (sc)->finalize = gapt_search_finalize;
}

static void
gapt_search_init (GAptSearch* gas) {
	gas->s_needle = new SearchPattern;

	GtkWidget* tbl = GTK_WIDGET (gas);

	gtk_table_resize (GTK_TABLE (tbl), 2, 2);
	gtk_table_set_homogeneous (GTK_TABLE (tbl), FALSE);
	gtk_container_set_border_width (GTK_CONTAINER (tbl), GAPT_PAD_BIG);
	gtk_table_set_row_spacings (GTK_TABLE (tbl), 0);
	gtk_table_set_col_spacings (GTK_TABLE (tbl), GAPT_PAD_BIG);

	GtkWidget* lbl = gtk_label_new (_("Text Filter"));
	gtk_label_set_justify (GTK_LABEL (lbl), GTK_JUSTIFY_LEFT);
	gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 2, 0, 1, GTK_FILL, (GtkAttachOptions) 0, 0, 0);
	gtk_misc_set_alignment (GTK_MISC (lbl), 0, 0.5);

	GtkWidget* vbox = gtk_vbox_new (FALSE, 6);
	gtk_table_attach (GTK_TABLE (tbl), vbox, 1, 2, 1, 2, GTK_FILL, (GtkAttachOptions) 0, 0, 0);

	lbl = gtk_label_new (_("Filter expression:"));
	gtk_box_pack_start (GTK_BOX (vbox), lbl, FALSE, FALSE, 0);
	gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5);

	GtkWidget* hbox = gtk_hbox_new (FALSE, GAPT_PAD_BIG);
	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);

	gas->entry_ = gtk_entry_new();
	gtk_entry_set_activates_default (GTK_ENTRY (gas->entry_), TRUE);
	gtk_box_pack_start (GTK_BOX (hbox), gas->entry_, TRUE, TRUE, 0);

	lbl = gtk_image_new_from_stock (GTK_STOCK_FIND, GTK_ICON_SIZE_BUTTON);
	GtkWidget* btn = gtk_button_new();
	gtk_container_add (GTK_CONTAINER (btn), lbl);
	g_signal_connect (G_OBJECT (btn), "clicked", G_CALLBACK (run_find_cb), gas);
	gtk_box_pack_end (GTK_BOX (hbox), btn, FALSE, FALSE, 0);

	tbl = gtk_table_new (2, 2, FALSE);
	gtk_container_set_border_width (GTK_CONTAINER (tbl), 0);
	gtk_table_set_row_spacings (GTK_TABLE (tbl), 0);
	gtk_table_set_col_spacings (GTK_TABLE (tbl), GAPT_PAD_BIG);
	gtk_box_pack_start (GTK_BOX (vbox), tbl, FALSE, FALSE, 0);

	lbl = gtk_label_new (_("Fields tested"));
	gtk_label_set_justify (GTK_LABEL (lbl), GTK_JUSTIFY_LEFT);
	gtk_table_attach (GTK_TABLE (tbl), lbl, 0, 2, 0, 1, GTK_FILL, (GtkAttachOptions) 0, 0, 0);
	gtk_misc_set_alignment (GTK_MISC (lbl), 0, 0.5);

	gas->case_sensitive_toggle_ = gtk_check_button_new_with_label (_("Match case"));
	gtk_box_pack_start (GTK_BOX (vbox), gas->case_sensitive_toggle_, TRUE, TRUE, 0);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gas->case_sensitive_toggle_),
	      gas->s_needle->get_case_sensitive());

  vbox = gtk_vbox_new(FALSE, 0);
	gtk_table_attach (GTK_TABLE (tbl), vbox, 1, 2, 1, 2, GTK_FILL, (GtkAttachOptions) 0, 0, 0);

	gas->package_toggle_ = gtk_check_button_new_with_label (_("Package name"));
	gtk_box_pack_start (GTK_BOX (vbox), gas->package_toggle_, TRUE, TRUE, 0);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gas->package_toggle_),
	      gas->s_needle->get_textfield (SearchPattern::Package));

	gas->maintainer_toggle_ = gtk_check_button_new_with_label (_("Maintainer"));
	gtk_box_pack_start (GTK_BOX (vbox), gas->maintainer_toggle_, TRUE, TRUE, 0);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gas->maintainer_toggle_),
	      gas->s_needle->get_textfield (SearchPattern::Maintainer));

	gas->conffiles_toggle_ = gtk_check_button_new_with_label (_("Configuration files"));
	gtk_box_pack_start (GTK_BOX (vbox), gas->conffiles_toggle_, TRUE, TRUE, 0);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gas->conffiles_toggle_),
	      gas->s_needle->get_textfield (SearchPattern::Conffiles));

	gas->short_toggle_ = gtk_check_button_new_with_label (_("Short description"));
	gtk_box_pack_start (GTK_BOX (vbox), gas->short_toggle_, TRUE, TRUE, 0);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gas->short_toggle_),
	      gas->s_needle->get_textfield (SearchPattern::ShortDescription));

	gas->long_toggle_ = gtk_check_button_new_with_label (_("Long description"));
	gtk_box_pack_start (GTK_BOX (vbox), gas->long_toggle_, TRUE, TRUE, 0);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gas->long_toggle_),
	      gas->s_needle->get_textfield (SearchPattern::LongDescription));
}

GtkWidget*
gapt_search_new (GAptCacheFile* cf) {
	GtkWidget* wdg = GTK_WIDGET (g_object_new (GAPT_SEARCH_TYPE, NULL));
	GAPT_SEARCH (wdg)->cachefile = cf;
	GAPT_SEARCH (wdg)->s_ran = FALSE;

	return wdg;
}

void
GAptSearch::run (void) {
  const char* s = gtk_entry_get_text(GTK_ENTRY(entry_));
  string regexp = s ? s : "";

	s_needle->set_case_sensitive (GTK_TOGGLE_BUTTON (case_sensitive_toggle_)->active);
	s_needle->set_textfield (SearchPattern::Package, GTK_TOGGLE_BUTTON (package_toggle_)->active);
	s_needle->set_textfield (SearchPattern::Maintainer, GTK_TOGGLE_BUTTON (maintainer_toggle_)->active);
	s_needle->set_textfield (SearchPattern::Conffiles, GTK_TOGGLE_BUTTON (conffiles_toggle_)->active);
	s_needle->set_textfield (SearchPattern::ShortDescription, GTK_TOGGLE_BUTTON (short_toggle_)->active);
	s_needle->set_textfield (SearchPattern::LongDescription, GTK_TOGGLE_BUTTON (long_toggle_)->active);

	if (!s_hits.empty()) s_hits.erase (s_hits.begin(), s_hits.end());
	if (!regexp.empty()) {
		s_ran = true;
		s_needle->set_regexp (regexp);
		s_needle->search (cachefile, s_hits);
		ga_debug ("been looking for `%s'", s);
	} else {
		s_ran = false;
	}

  return;
}

bool
GAptSearch::filter_package (pkgCache::Package* p) {
	ga_debug ("s_hits.len(): %d", s_hits.size());
	if (!s_ran) {
		return true;
	}

	vector<pkgCache::Package*>::iterator it = s_hits.begin();
	while (it != s_hits.end()) {
		if ((*it) == p) {
			return true;
		}
		++it;
	}
	return false;
}
