/*
 * This file is part of the KFTPGrabber project
 *
 * Copyright (C) 2003-2006 by the KFTPGrabber developers
 * Copyright (C) 2003-2006 Jernej Kos <kostko@jweb-network.net>
 *
 * 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
 * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
 * NON-INFRINGEMENT.  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., 51 Franklin Steet, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 */
#include "filter.h"

#include <qregexp.h>

#include <kapplication.h>
#include <klocale.h>
#include <kstaticdeleter.h>
#include <kconfig.h>

namespace KFTPCore {

namespace Filter {

Condition::Condition(Field field, Type type, const QVariant &value)
  : m_field(field),
    m_type(type),
    m_value(value)
{
}

bool Condition::matches(const KFTPEngine::DirectoryEntry &entry) const
{
  bool result = false;
  QString check;
  
  switch (m_field) {
    default:
    case Filename: check = entry.filename(); break;
    case EntryType: check = entry.type(); break;
    case Size: check = QString::number(entry.size()); break;
  }
  
  switch (m_type) {
    case None: result = false; break;
    
    case Contains: result = (check.contains(m_value.toString()) > 0); break;
    case ContainsNot: result = (check.contains(m_value.toString()) == 0); break;
    
    case Is: result = (check == m_value.toString()); break;
    case IsNot: result = (check != m_value.toString()); break;
    
    case Matches: {
      QRegExp r(m_value.toString());
      result = (r.search(check) > -1);
      break;
    }
    case MatchesNot: {
      QRegExp r(m_value.toString());
      result = (r.search(check) == -1);
      break;
    }
    
    case Greater: result = (check.toULongLong() > m_value.toULongLong()); break;
    case Smaller: result = (check.toULongLong() < m_value.toULongLong()); break;
  }
  
  return result;
}

ConditionChain::ConditionChain()
  : QPtrList<Condition>(),
    m_type(All)
{
  setAutoDelete(true);
}

ConditionChain::ConditionChain(Type type)
  : QPtrList<Condition>(),
    m_type(type)
{
  setAutoDelete(true);
}

bool ConditionChain::matches(const KFTPEngine::DirectoryEntry &entry) const
{
  if (isEmpty())
    return false;
  
  QPtrList<Condition>::ConstIterator le = end();
  for (QPtrList<Condition>::ConstIterator i = begin(); i != le; ++i) {
    bool match = (*i)->matches(entry);
    
    if (match && m_type == Any)
      return true;
    else if (!match && m_type == All)
      return false;
  }
  
  if (m_type == Any)
    return false;
  
  return true;
}

Action::Action()
  : m_valid(false)
{
}

Action::Action(Type type, const QVariant &value)
  : m_valid(true),
    m_type(type),
    m_value(value)
{
}

ActionChain::ActionChain()
  : QPtrList<Action>()
{
  setAutoDelete(true);
}

const Action *ActionChain::getAction(Action::Type type) const
{
  ActionChain::ConstIterator le = end();
  for (ActionChain::ConstIterator i = begin(); i != le; ++i)
    if ((*i)->type() == type)
      return (*i);
  
  return 0;
}

Rule::Rule()
  : m_name(QString::null),
    m_enabled(false)
{
}

Rule::Rule(const Rule *rule)
  : m_name(rule->name()),
    m_enabled(rule->isEnabled())
{
  // Copy conditions
  const ConditionChain *conditionList = rule->conditions();
  m_conditionChain.setType(conditionList->type());
  
  ConditionChain::ConstIterator cle = conditionList->end();
  for (ConditionChain::ConstIterator i = conditionList->begin(); i != cle; ++i) {
    const Condition *c = (*i);
    
    m_conditionChain.append(new Condition(c->field(), c->type(), c->value()));
  }
  
  // Copy actions
  const ActionChain *actionList = rule->actions();
  
  ActionChain::ConstIterator ale = actionList->end();
  for (ActionChain::ConstIterator i = actionList->begin(); i != ale; ++i) {
    const Action *a = (*i);
    
    m_actionChain.append(new Action(a->type(), a->value()));
  }
}

Rule::Rule(const QString &name)
  : m_name(name),
    m_enabled(true)
{
  // Add a simple condition and a simple action
  m_conditionChain.append(new Condition(Filename, Condition::Contains, QVariant("")));
  m_actionChain.append(new Action(Action::None, QVariant()));
}

Filters *Filters::m_self = 0;
static KStaticDeleter<Filters> staticFiltersDeleter;

Filters *Filters::self()
{
  if (!m_self) {
    staticFiltersDeleter.setObject(m_self, new Filters());
  }
  
  return m_self;
}

Filters::Filters()
  : QPtrList<Rule>(),
    m_enabled(true)
{
  setAutoDelete(true);
  
  // Generate human readable strings
  m_fieldNames << i18n("Filename");
  m_fieldNames << i18n("Entry Type");
  m_fieldNames << i18n("Size");
  
  m_actionNames << " ";
  m_actionNames << i18n("Change priority");
  m_actionNames << i18n("Skip when queuing");
  m_actionNames << i18n("Colorize in list view");
  m_actionNames << i18n("Hide from list view");
  m_actionNames << i18n("Lowercase destination");
  
  // Load the filters
  load();
}

Filters::~Filters()
{
  if (m_self == this)
    staticFiltersDeleter.setObject(m_self, 0, false);
}

void Filters::save()
{
  int num = 0;
  KConfig *config = kapp->config();
  
  config->setGroup("Filters");
  config->writeEntry("count", count());
  
  // Remove any existing sections
  for (int i = 0; ; i++) {
    QString groupName = QString("Filter #%1").arg(i);
    
    if (config->hasGroup(groupName))
      config->deleteGroup(groupName);
    else
      break;
  }
  
  Filters::ConstIterator le = end();
  for (Filters::ConstIterator i = begin(); i != le; ++i, num++) {
    const Rule *rule = (*i);
    
    config->setGroup(QString("Filter #%1").arg(num));
    config->writeEntry("name", rule->name());
    config->writeEntry("enabled", rule->isEnabled());
    
    // Write conditions
    int cnum = 0;
    const ConditionChain *conditions = rule->conditions();
    config->writeEntry("conditions", conditions->count());
    config->writeEntry("conditions-type", (int) conditions->type());
    
    ConditionChain::ConstIterator cle = conditions->end();
    for (ConditionChain::ConstIterator j = conditions->begin(); j != cle; ++j, cnum++) {
      const Condition *c = (*j);
      QString prefix = QString("condition%1-").arg(cnum);
      
      config->writeEntry(prefix + "field", (int) c->field());
      config->writeEntry(prefix + "type", (int) c->type());
      config->writeEntry(prefix + "value", c->value());
      config->writeEntry(prefix + "valueType", (int) c->value().type());
    }
    
    // Write actions
    int anum = 0;
    const ActionChain *actions = rule->actions();
    config->writeEntry("actions", actions->count());
    
    ActionChain::ConstIterator ale = actions->end();
    for (ActionChain::ConstIterator j = actions->begin(); j != ale; ++j, anum++) {
      const Action *a = (*j);
      QString prefix = QString("action%1-").arg(anum);
      
      config->writeEntry(prefix + "type", (int) a->type());
      config->writeEntry(prefix + "value", a->value());
      config->writeEntry(prefix + "valueType", (int) a->value().type());
    }
  }
}

void Filters::load()
{
  int num = 0;
  KConfig *config = kapp->config();
  
  config->setGroup("Filters");
  num = config->readNumEntry("count");
  
  for (int i = 0; i < num; i++) {
    Rule *rule = new Rule();
    
    config->setGroup(QString("Filter #%1").arg(i));
    rule->setName(config->readEntry("name", i18n("Unnamed Rule")));
    rule->setEnabled(config->readBoolEntry("enabled", true));
    
    // Read conditions
    ConditionChain *conditions = const_cast<ConditionChain*>(rule->conditions());
    int cnum = config->readNumEntry("conditions");
    conditions->setType((ConditionChain::Type) config->readNumEntry("conditions-type"));
    
    for (int j = 0; j < cnum; j++) {
      QString prefix = QString("condition%1-").arg(j);
      
      conditions->append(new Condition((Field) config->readNumEntry(prefix + "field"),
                                       (Condition::Type) config->readNumEntry(prefix + "type"),
                                       config->readPropertyEntry(prefix + "value",
                                       (QVariant::Type) config->readNumEntry(prefix + "valueType")))); 
    }
    
    // Read actions
    ActionChain *actions = const_cast<ActionChain*>(rule->actions());
    int anum = config->readNumEntry("actions");
    
    for (int j = 0; j < anum; j++) {
      QString prefix = QString("action%1-").arg(j);
      
      actions->append(new Action((Action::Type) config->readNumEntry(prefix + "type"),
                                 config->readPropertyEntry(prefix + "value",
                                 (QVariant::Type) config->readNumEntry(prefix + "valueType"))));
    }
    
    append(rule);
  }
}

const ActionChain *Filters::process(const KFTPEngine::DirectoryEntry &entry) const
{
  if (m_enabled) {
    QPtrList<Rule>::ConstIterator le = end();
    for (QPtrList<Rule>::ConstIterator i = begin(); i != le; ++i) {
      const Rule *rule = (*i);
      
      if (rule->isEnabled() && rule->conditions()->matches(entry))
        return rule->actions();
    }
  }
  
  // Nothing has matched
  return 0;
}

const Action *Filters::process(const KFTPEngine::DirectoryEntry &entry, QValueList<Action::Type> types) const
{
  const ActionChain *chain = process(entry);
  
  if (!chain || chain->isEmpty())
    return 0;
  
  // Find an action that matches the filter
  ActionChain::ConstIterator le = chain->end();
  for (ActionChain::ConstIterator i = chain->begin(); i != le; ++i) {
    if (types.contains((*i)->type()))
      return (*i);
  }
  
  return 0;
}

const Action *Filters::process(const KFTPEngine::DirectoryEntry &entry, Action::Type filter) const
{
  const ActionChain *chain = process(entry);
  
  if (!chain || chain->isEmpty())
    return 0;
  
  // Find an action that matches the filter
  ActionChain::ConstIterator le = chain->end();
  for (ActionChain::ConstIterator i = chain->begin(); i != le; ++i) {
    if ((*i)->type() == filter)
      return (*i);
  }
  
  return 0;
}

const Action *Filters::process(const KURL &url, filesize_t size, bool directory, Action::Type filter) const
{
  KFTPEngine::DirectoryEntry entry;
  entry.setFilename(url.filename());
  entry.setSize(size);
  entry.setType(directory ? 'd' : 'f');
  
  return process(entry, filter);
}

const ActionChain *Filters::process(const KURL &url, filesize_t size, bool directory) const
{
  KFTPEngine::DirectoryEntry entry;
  entry.setFilename(url.filename());
  entry.setSize(size);
  entry.setType(directory ? 'd' : 'f');
  
  return process(entry);
}

const Action *Filters::process(const KURL &url, Action::Type filter) const
{
  return process(url, 0, false, filter);
}

}

}
