/**********************************************************************
 *
 * filter.cpp -- 
 * Copyright (C) 1999  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "filter.h"
#include "fileutil.h"
#include <assert.h>

#include <iostream>
using namespace std;


// default constructor does nothing
filterclass::filterclass () {
}

// default destructor does nothing
filterclass::~filterclass () {
}

// configure should be called once for each configuration line
// default configures the default filter options
void filterclass::configure (const text_t &key, const text_tarray &cfgline) {
  if (cfgline.size() >= 1) {
    const text_t &value = cfgline[0];

    if (key == "collection") collection = value;
    else if (key == "collectdir") collectdir = value;
    else if (key == "gsdlhome") gsdlhome = value;
    else if (key == "collecthome") collecthome = value;
    else if (key == "gdbmhome") dbhome = value;
    else if ((key == "filteroptdefault") && (cfgline.size() == 2)) {
      // see if this filter has an option with this name
      FilterOption_tmap::iterator thisfilteroption = 
	filterOptions.find(cfgline[0]);
      if (thisfilteroption != filterOptions.end())
	(*thisfilteroption).second.defaultValue = cfgline[1];
    }
  }
}


// init should be called after all the configuration is done but
// before any other methods are called
// default checks all the filter option defaults
bool filterclass::init (ostream &/*logout*/) {
  // check all the filter defaults
  FilterOption_tmap::iterator filteroption_here = filterOptions.begin();
  FilterOption_tmap::iterator filteroption_end = filterOptions.end();
  while (filteroption_here != filteroption_end) {
    (*filteroption_here).second.check_defaultValue ();
    
    ++filteroption_here;
  }

  if (collecthome.empty()) collecthome = filename_cat(gsdlhome,"collect");
  if (dbhome.empty()) dbhome = gsdlhome;
  
  // get the collection directory name
  if (collectdir.empty()) {
    collectdir = filename_cat (collecthome, collection);
  }
  
  return true;
}

// returns the name of this filter
// default returns "NullFilter"
text_t filterclass::get_filter_name () {
  return "NullFilter";
}

// returns the current filter options
void filterclass::get_filteroptions (InfoFilterOptionsResponse_t &response,
				     comerror_t &err, ostream &/*logout*/) {
  response.clear();
  response.filterOptions = filterOptions;
  err = noError;
}

// default returns nothing
void filterclass::filter (const FilterRequest_t &request,
			  FilterResponse_t &response,
			  comerror_t &err, ostream &/*logout*/) {
  ResultDocInfo_t resultdoc;

  response.clear();

  if ((request.filterResultOptions & FROID) ||
      (request.filterResultOptions & FRmetadata)) {
    // copy the OIDs from the request to the response
    text_tarray::const_iterator here = request.docSet.begin();
    text_tarray::const_iterator end = request.docSet.end();
    while (here != end) {
      resultdoc.OID = (*here);
      response.docInfo.push_back(resultdoc);

      ++here;
    }
  }

  response.numDocs = response.docInfo.size();
  response.isApprox = Exact;
  err = noError;
}


bool operator==(const filterptr &x, const filterptr &y) {
  return (x.f == y.f);
}

bool operator<(const filterptr &x, const filterptr &y) {
  return (x.f < y.f);
}


// thefilter remains the property of the calling code but
// should not be deleted until it is removed from this list.
void filtermapclass::addfilter (filterclass *thefilter) {
  // can't add a null filter
  assert (thefilter != NULL);
  if (thefilter == NULL) return;
  
  // can't add an filter with no name
  assert (!(thefilter->get_filter_name()).empty());
  if ((thefilter->get_filter_name()).empty()) return;

  filterptr fptr;
  fptr.f = thefilter;
  filterptrs[thefilter->get_filter_name()] = fptr;
}

// getfilter will return NULL if the filter could not be found
filterclass *filtermapclass::getfilter (const text_t &key) {
  // can't find an filter with no name
  assert (!key.empty());
  if (key.empty()) return NULL;

  iterator here = filterptrs.find (key);
  if (here == filterptrs.end()) return NULL;
  
  return (*here).second.f;
}




// some useful functions for dealing with document sets

// returns -1 if t1 is a child of t2
// returns 0  if t1 and t2 are not parent-child related
// returns 1  if t1 is a parent of t2
int child_compare (const text_t &t1, const text_t &t2) {
  text_t::const_iterator t1_here = t1.begin();
  text_t::const_iterator t1_end = t1.end();
  text_t::const_iterator t2_here = t2.begin();
  text_t::const_iterator t2_end = t2.end();

  while ((t1_here != t1_end) && (t2_here != t2_end)) {
    if (*t1_here != *t2_here) return 0; // unrelated
    ++t1_here;
    ++t2_here;
  }

  if ((t1_here == t1_end) && (t2_here == t2_end)) return 0; // equal
  if (t1_here != t1_end) {
    if (*t1_here == '.') return -1; // t1 is child
    else return 0; // unrelated
  }

  if (t2_here != t2_end) {
    if (*t2_here == '.') return 1; // t2 is child
    else return 0; // unrelated
  }

  return 0; // shouldn't get here...
}

// intersect places the result in set1
void intersect (text_tset &set1, const text_tset &set2) {
  text_tset resultset;
  int childcomp = 0;

  text_tset::const_iterator set1_here = set1.begin();
  text_tset::const_iterator set1_end = set1.end();
  text_tset::const_iterator set2_here = set2.begin();
  text_tset::const_iterator set2_end = set2.end();
  while ((set1_here != set1_end) && (set2_here != set2_end)) {
    if (*set1_here == *set2_here) {
      // equal
      resultset.insert (*set1_here);
      ++set1_here;
      ++set2_here;

    } else if ((childcomp=child_compare(*set1_here, *set2_here)) != 0) {
      if (childcomp < 0) {
	// set1_here is child
	resultset.insert (*set1_here);
	++set1_here;
      } else {
	// set2_here is child
	resultset.insert (*set2_here);
	++set2_here;
      }

    } else if (*set1_here < *set2_here) {
      // set1 is less
      ++set1_here;

    } else {
      // set2 is less
      ++set2_here;
    }
  }

  set1 = resultset;
}

void intersect (text_tarray &set1, const text_tset &set2) {
  text_tarray resultset;

  text_tarray::const_iterator set1_here = set1.begin();
  text_tarray::const_iterator set1_end = set1.end();

  while (set1_here != set1_end) {
    if (in_set (set2, *set1_here))
      resultset.push_back (*set1_here);
    ++set1_here;
  }
  set1 = resultset;
}

void intersect (text_tarray &set1, const text_tarray &set2) {
  text_tarray resultset;

  text_tarray::const_iterator set1_here = set1.begin();
  text_tarray::const_iterator set1_end = set1.end();

  while (set1_here != set1_end) {
    if (in_set (set2, *set1_here))
      resultset.push_back (*set1_here);
    ++set1_here;
  }
  set1 = resultset;
}

// tests to see if el is in set
bool in_set (const text_tset &set1, const text_t &el) {
  text_t::const_iterator here = el.begin();
  text_t::const_iterator end = el.end();
  text_t tryel, tryel_add;
  bool first = true;

  // the element is in the set if any of its parents are
  // in the set
  do {
    // get next possible element to try
    here = getdelimitstr (here, end, '.', tryel_add);
    if (!first) tryel += ".";
    first = false;
    tryel += tryel_add;

    // see if this element is in the set
    if (set1.find(tryel) != set1.end()) return true;
  } while (here != end);

  return false;
}

bool in_set (const text_tarray &set1, const text_t &el) {
  text_t::const_iterator here = el.begin();
  text_t::const_iterator end = el.end();
  text_t tryel, tryel_add;
  bool first = true;

  // the element is in the set if any of its parents are
  // in the set
  do {
    // get next possible element to try
    here = getdelimitstr (here, end, '.', tryel_add);
    if (!first) tryel += ".";
    first = false;
    tryel += tryel_add;

    // see if this element is in the set
    text_tarray::const_iterator h = set1.begin();
    text_tarray::const_iterator e = set1.end();
    while (h != e) {
      if (*h == tryel) return true;
      ++h;
    }
  } while (here != end);

  return false;
}
