/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010, 2011, 2012 Alexis Megas,
** Gunther van Dooble, and the Dooble Team.
** All rights reserved.
**
** License: GPL2 only:
** 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; version 2 of the License only.
**
** 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
** or see here: http://www.gnu.org/licenses/gpl.html
**
** For the WebKit library, please see: http://webkit.org.
**
** THE CODE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
** IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS APPLICATION, EVEN IF ADVISED
** OF THE POSSIBILITY OF SUCH DAMAGE.
**
** Please report all praise, requests, bugs, and problems to the project
** team and administrators: http://sf.net/projects/dooble.
**
** You can find us listed at our project page. New team members are welcome.
** The name of the authors should not be used to endorse or promote products
** derived from Dooble without specific prior written permission.
** If you use this code for other projects, please let us know.
**
** Web sites:
**   http://sf.net/projects/dooble
**   http://dooble.sf.net
****************************************************************************/

#include <QSqlQuery>
#include <QProgressBar>
#include <QSqlDatabase>

#include "dmisc.h"
#include "dooble.h"
#include "dexceptionsmodel.h"

dexceptionsmodel::dexceptionsmodel(const QString &tableName):
  QStandardItemModel()
{
  setSortRole(Qt::UserRole);
  setObjectName(tableName);
  connect(this,
	  SIGNAL(itemChanged(QStandardItem *)),
	  this,
	  SLOT(slotCheckBoxItemChanged(QStandardItem *)));

  QStringList list;

  list << tr("Site");
  list << tr("Originating URL");
  list << tr("Event Date");
  list << tr("Exempt");
  setHorizontalHeaderLabels(list);
  createExceptionsDatabase();
}

dexceptionsmodel::~dexceptionsmodel()
{
  removeRows(0, rowCount());
}

bool dexceptionsmodel::allowed(const QString &host) const
{
  QList<QStandardItem *> list(findItems(host.trimmed()));

  if(!list.isEmpty())
    {
      if(this->item(list.at(0)->row(), 3)->checkState() == Qt::Checked)
	return true;
      else
	return false;
    }
  else
    {
      return false;
    }
}

bool dexceptionsmodel::allow(const QString &host)
{
  if(host.trimmed().isEmpty())
    return false;

  bool state = false;
  QList<QStandardItem *> list(findItems(host.trimmed()));

  disconnect(this,
	     SIGNAL(itemChanged(QStandardItem *)),
	     this,
	     SLOT(slotCheckBoxItemChanged(QStandardItem *)));

  if(list.isEmpty())
    {
      list.clear();

      QStandardItem *item = 0;

      item = new QStandardItem(host.trimmed());
      item->setData(item->text(), Qt::UserRole);
      item->setEditable(false);
      list << item;
      item = new QStandardItem();
      item->setData("", Qt::UserRole);
      item->setEditable(false);
      list << item;

      QDateTime now(QDateTime::currentDateTime());

      item = new QStandardItem(now.toString("MM/dd/yyyy hh:mm:ss AP"));
      item->setData(now, Qt::UserRole);
      item->setEditable(false);
      list << item;
      item = new QStandardItem();
      item->setData(true, Qt::UserRole);
      item->setEditable(false);
      item->setCheckable(true);
      item->setCheckState(Qt::Checked);
      list << item;
      appendRow(list);
    }
  else
    this->item(list.at(0)->row(), 3)->setCheckState(Qt::Checked);

  connect(this,
	  SIGNAL(itemChanged(QStandardItem *)),
	  this,
	  SLOT(slotCheckBoxItemChanged(QStandardItem *)));
  createExceptionsDatabase();

  if(!list.isEmpty() && dmisc::passphraseWasAuthenticated())
    {
      {
	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",
						    objectName());

	db.setDatabaseName(dooble::s_homePath +
			   QDir::separator() +
			   QString("%1.db").arg(objectName()));

	if(db.open())
	  {
	    QSqlQuery query(db);

	    if(query.exec(QString("SELECT host FROM %1").arg(objectName())))
	      while(query.next())
		{
		  QString l_host
		    (QString::fromUtf8
		     (dmisc::decodedString
		      (QByteArray::fromBase64(query.value(0).
					      toByteArray()))));

		  if(host != l_host)
		    continue;

		  QSqlQuery deleteQuery(db);

		  deleteQuery.exec("PRAGMA synchronous = OFF");
		  deleteQuery.prepare(QString("DELETE FROM %1 WHERE "
					      "host = ?"));
		  deleteQuery.bindValue(0, query.value(0));
		  deleteQuery.exec();
		  break;
		}

	    query.exec("PRAGMA synchronous = OFF");
	    query.prepare
	      (QString("INSERT OR REPLACE INTO %1 "
		       "(host, originating_url, datetime)"
		       "VALUES (?, ?, ?)").arg(objectName()));
	    query.bindValue
	      (0, dmisc::encodedString(host.trimmed().toUtf8(),
				       true).toBase64());
	    query.bindValue
	      (1, dmisc::encodedString(this->item(list.at(0)->row(),
						  1)->text().toUtf8(),
				       true).toBase64());
	    query.bindValue
	      (2,
	       dmisc::encodedString(this->item(list.at(0)->row(),
					       2)->text().toUtf8(),
				    true).toBase64());
	    state = query.exec();
	  }

	db.close();
      }

      QSqlDatabase::removeDatabase(objectName());
    }

  return state;
}

void dexceptionsmodel::add(const QString &host,
			   const QUrl &url,
			   const QDateTime &dateTime)
{
  if(host.trimmed().isEmpty() || !findItems(host.trimmed()).isEmpty())
    return;

  disconnect(this,
	     SIGNAL(itemChanged(QStandardItem *)),
	     this,
	     SLOT(slotCheckBoxItemChanged(QStandardItem *)));

  QList<QStandardItem *> list;

  QStandardItem *item = 0;

  item = new QStandardItem(host.trimmed());
  item->setData(item->text(), Qt::UserRole);
  item->setEditable(false);
  list << item;
  item = new QStandardItem(url.toString(QUrl::StripTrailingSlash));
  item->setData(item->text(), Qt::UserRole);
  item->setEditable(false);
  list << item;
  item = new QStandardItem(dateTime.toString("MM/dd/yyyy hh:mm:ss AP"));
  item->setData(dateTime, Qt::UserRole);
  item->setEditable(false);
  list << item;
  item = new QStandardItem();
  item->setData(false, Qt::UserRole);
  item->setEditable(false);
  item->setCheckable(true);
  item->setCheckState(Qt::Unchecked);
  list << item;
  appendRow(list);
  connect(this,
	  SIGNAL(itemChanged(QStandardItem *)),
	  this,
	  SLOT(slotCheckBoxItemChanged(QStandardItem *)));
}

void dexceptionsmodel::populate(void)
{
  removeRows(0, rowCount());

  if(!dmisc::passphraseWasAuthenticated())
    return;

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", objectName());

    db.setDatabaseName(dooble::s_homePath +
		       QDir::separator() +
		       QString("%1.db").arg(objectName()));

    if(db.open())
      {
	QSqlQuery query(db);

	if(query.exec(QString("SELECT host, originating_url, "
			      "datetime FROM %1").
		      arg(objectName())))
	  {
	    while(query.next())
	      {
		QString url
		  (QString::fromUtf8
		   (dmisc::decodedString
		    (QByteArray::fromBase64(query.value(1).toByteArray()))));
		QString host
		  (QString::fromUtf8
		   (dmisc::decodedString
		    (QByteArray::fromBase64(query.value(0).toByteArray()))));
		QString dateTime
		  (QString::fromUtf8
		   (dmisc::decodedString
		    (QByteArray::fromBase64(query.value(2).toByteArray()))));

		if(!host.trimmed().isEmpty())
		  {
		    disconnect(this,
			       SIGNAL(itemChanged(QStandardItem *)),
			       this,
			       SLOT(slotCheckBoxItemChanged(QStandardItem *)));

		    QList<QStandardItem *> list;

		    QStandardItem *item = 0;

		    item = new QStandardItem(host.trimmed());
		    item->setData(item->text(), Qt::UserRole);
		    item->setEditable(false);
		    list << item;
		    item = new QStandardItem(url);
		    item->setData(item->text(), Qt::UserRole);
		    item->setEditable(false);
		    list << item;
		    item = new QStandardItem(dateTime);
		    item->setData
		      (QDateTime::fromString(dateTime,
					     "MM/dd/yyyy hh:mm:ss AP"),
		       Qt::UserRole);
		    item->setEditable(false);
		    list << item;
		    item = new QStandardItem();
		    item->setData(true, Qt::UserRole);
		    item->setEditable(false);
		    item->setCheckable(true);
		    item->setCheckState(Qt::Checked);
		    list << item;
		    appendRow(list);
		    connect(this,
			    SIGNAL(itemChanged(QStandardItem *)),
			    this,
			    SLOT(slotCheckBoxItemChanged(QStandardItem *)));
		  }
	      }
	  }

	db.close();
      }
  }

  QSqlDatabase::removeDatabase(objectName());
}

void dexceptionsmodel::deleteList(const QModelIndexList &list)
{
  if(dmisc::passphraseWasAuthenticated())
    {
      int removedRows = 0;
      QStringList items;
      QModelIndexList l_list(list);

      /*
      ** Sort the list by row number. If the list is not sorted,
      ** the following while-loop misbehaves.
      */

      qSort(l_list);

      while(!l_list.isEmpty())
	{
	  QStandardItem *item = this->item
	    (l_list.takeFirst().row() - removedRows, 0);

	  if(item)
	    {
	      items.append(item->text());

	      if(removeRow(item->row()))
		removedRows += 1;
	    }
	}

      if(!items.isEmpty())
	{
	  {
	    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",
							objectName());

	    db.setDatabaseName(dooble::s_homePath +
			       QDir::separator() +
			       QString("%1.db").arg(objectName()));

	    if(db.open())
	      {
		QSqlQuery query(db);

		if(query.exec(QString("SELECT host FROM %1").
			      arg(objectName())))
		  while(query.next())
		    {
		      QString host
			(QString::fromUtf8
			 (dmisc::decodedString
			  (QByteArray::fromBase64(query.value(0).
						  toByteArray()))));

		      if(!items.contains(host))
			continue;
		      else
			items.removeOne(host);

		      QSqlQuery deleteQuery(db);

		      deleteQuery.exec("PRAGMA synchronous = OFF");
		      deleteQuery.prepare(QString("DELETE FROM %1 "
						  "WHERE host = ?").
					  arg(objectName()));
		      deleteQuery.bindValue(0, query.value(0));
		      deleteQuery.exec();

		      if(items.isEmpty())
			break;
		    }
	      }

	    db.close();
	  }

	  QSqlDatabase::removeDatabase(objectName());
	}
    }
  else
    {
      int removedRows = 0;
      QModelIndexList l_list(list);

      /*
      ** Sort the list by row number. If the list is not sorted,
      ** the following while-loop misbehaves.
      */

      qSort(l_list);

      while(!l_list.isEmpty())
	{
	  QStandardItem *item = this->item
	    (l_list.takeFirst().row() - removedRows, 0);

	  if(item)
	    if(removeRow(item->row()))
	      removedRows += 1;
	}
    }
}

void dexceptionsmodel::slotCheckBoxItemChanged(QStandardItem *item)
{
  if(dmisc::passphraseWasAuthenticated() &&
     item && item->isCheckable())
    {
      createExceptionsDatabase();

      {
	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", objectName());

	db.setDatabaseName(dooble::s_homePath +
			   QDir::separator() +
			   QString("%1.db").arg(objectName()));

	if(db.open())
	  {
	    QSqlQuery query(db);

	    if(query.exec(QString("SELECT host FROM %1").
			  arg(objectName())))
	      while(query.next())
		{
		  QString host
		    (QString::fromUtf8
		     (dmisc::decodedString
		      (QByteArray::fromBase64(query.value(0).
					      toByteArray()))));

		  if(host != this->item(item->row(), 0)->text())
		    continue;

		  QSqlQuery deleteQuery(db);

		  deleteQuery.exec("PRAGMA synchronous = OFF");
		  deleteQuery.prepare(QString("DELETE FROM %1 "
					      "WHERE host = ?").
				      arg(objectName()));
		  deleteQuery.bindValue(0, query.value(0));
		  deleteQuery.exec();
		  break;
		}

	    if(item->checkState() == Qt::Checked)
	      {
		query.exec("PRAGMA synchronous = OFF");
		query.prepare
		  (QString("INSERT OR REPLACE INTO %1 "
			   "(host, originating_url, datetime)"
			   "VALUES (?, ?, ?)").arg(objectName()));
		query.bindValue
		  (0, dmisc::encodedString(this->item(item->row(), 0)->text().
					   toUtf8(),
					   true).toBase64());
		query.bindValue
		  (1, dmisc::encodedString
		   (this->item(item->row(), 1)->text().toUtf8(),
		    true).toBase64());
		query.bindValue
		  (2, dmisc::encodedString
		   (this->item(item->row(), 2)->text().toUtf8(),
		    true).toBase64());
		query.exec();
	      }
	  }

	db.close();
      }

      QSqlDatabase::removeDatabase(objectName());
    }
}

void dexceptionsmodel::createExceptionsDatabase(void)
{
  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", objectName());

    db.setDatabaseName(dooble::s_homePath +
		       QDir::separator() + QString("%1.db").arg(objectName()));

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec(QString("CREATE TABLE IF NOT EXISTS %1 ("
			   "host TEXT PRIMARY KEY NOT NULL, "
			   "originating_url TEXT, "
			   "datetime TEXT NOT NULL)").
		   arg(objectName()));
      }

    db.close();
  }

  QSqlDatabase::removeDatabase(objectName());
}

void dexceptionsmodel::reencode(QProgressBar *progress)
{
  if(!dmisc::passphraseWasAuthenticated())
    return;

  /*
  ** This method assumes that the respective container
  ** is as current as possible. It will use the container's
  ** contents to create new database entries using the newly-encoded
  ** data. Obsolete data will be deleted via the purge method.
  */

  purge();

  if(progress)
    {
      progress->setMaximum(-1);
      progress->setVisible(true);
    }

  QList<int> rows;

  for(int i = 0; i < rowCount(); i++)
    {
      QStandardItem *item = this->item(i, 3);

      if(item && item->checkState() == Qt::Checked)
	rows.append(i);
    }

  if(progress)
    {
      progress->setMaximum(rows.size());
      progress->update();
    }

  for(int i = 0; i < rows.size(); i++)
    {
      QStandardItem *item = this->item(rows.at(i), 0);

      if(item)
	allow(item->text());

      if(progress)
	progress->setValue(i + 1);
    }

  if(progress)
    progress->setVisible(false);
}

void dexceptionsmodel::purge(void)
{
  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",
						objectName());

    db.setDatabaseName(dooble::s_homePath +
		       QDir::separator() +
		       QString("%1.db").arg(objectName()));

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("PRAGMA synchronous = OFF");
	query.exec(QString("DELETE FROM %1").arg(objectName()));
	query.exec("VACUUM");
      }

    db.close();
  }

  QSqlDatabase::removeDatabase(objectName());
}

bool dexceptionsmodel::contains(const QString &host) const
{
  return !findItems(host.trimmed()).isEmpty();
}
