/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010 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 <QDir>
#include <QMenu>
#include <QStack>
#include <QSettings>
#include <QTreeView>
#include <QScrollBar>
#include <QMessageBox>
#include <QFileInfoList>
#include <QContextMenuEvent>

#include "dooble.h"
#include "dfilemanager.h"

dfilesystemmodel *dfilemanager::tableModel = 0;
QFileSystemModel *dfilemanager::treeModel = 0;

dfilemanager::dfilemanager(QWidget *parent):QWidget(parent)
{
  ui.setupUi(this);
  connect(ui.treeView,
	  SIGNAL(clicked(const QModelIndex &)),
	  this,
	  SLOT(slotClicked(const QModelIndex &)));
  connect(ui.treeView,
	  SIGNAL(activated(const QModelIndex &)),
	  this,
	  SLOT(slotClicked(const QModelIndex &)));
  connect(ui.tableView,
	  SIGNAL(activated(const QModelIndex &)),
	  this,
	  SLOT(slotDoubleClicked(const QModelIndex &)));
  connect(ui.tableView,
	  SIGNAL(doubleClicked(const QModelIndex &)),
	  this,
	  SLOT(slotDoubleClicked(const QModelIndex &)));
  connect(ui.pathLabel,
	  SIGNAL(linkActivated(const QString &)),
	  this,
	  SLOT(slotLabelClicked(const QString &)));
  m_history = new dwebhistory();
  ui.splitter->setStretchFactor(0, 0);
  ui.splitter->setStretchFactor(1, 1);
  connect(treeModel,
	  SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
	  this,
	  SLOT(slotDirectoryRemoved(const QModelIndex &, int, int)));
  ui.treeView->setModel(treeModel);
  ui.treeView->setSortingEnabled(true);
  ui.treeView->sortByColumn(0, Qt::AscendingOrder);
  ui.treeView->hideColumn(1);
  ui.treeView->hideColumn(2);
  ui.treeView->hideColumn(3);
  ui.treeView->setExpandsOnDoubleClick(false);
  ui.tableView->setModel(tableModel);
#ifndef Q_OS_UNIX
  ui.tableView->hideColumn(5);
  ui.tableView->hideColumn(6);
#endif
  ui.tableView->setSortingEnabled(true);
  ui.tableView->sortByColumn(0, Qt::AscendingOrder);
  ui.tableView->setShowGrid(false);
  ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(ui.treeView,
	  SIGNAL(customContextMenuRequested(const QPoint &)),
	  this,
	  SLOT(slotCustomContextMenuRequest(const QPoint &)));
  ui.tableView->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(ui.tableView,
	  SIGNAL(customContextMenuRequested(const QPoint &)),
	  this,
	  SLOT(slotCustomContextMenuRequest(const QPoint &)));
  connect(ui.tableView->horizontalHeader(),
	  SIGNAL(sectionMoved(int, int, int)),
	  this,
	  SLOT(slotSaveTableHeaderState(void)));
  connect(ui.tableView->horizontalHeader(),
	  SIGNAL(sectionClicked(int)),
	  this,
	  SLOT(slotSaveTableHeaderState(void)));
  connect(ui.tableView->horizontalHeader(),
	  SIGNAL(sectionResized(int, int, int)),
	  this,
	  SLOT(slotSaveTableHeaderState(void)));
  connect
    (ui.treeView->itemDelegate(),
     SIGNAL(closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)),
     this,
     SLOT(slotCloseEditor(QWidget *, QAbstractItemDelegate::EndEditHint)));
  connect
    (ui.tableView->itemDelegate(),
     SIGNAL(closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)),
     this,
     SLOT(slotCloseEditor(QWidget *, QAbstractItemDelegate::EndEditHint)));

  if(dooble::s_settings.contains("mainWindow/fileManagerColumnsState"))
    if(!ui.tableView->horizontalHeader()->restoreState
       (dooble::s_settings.value
	("mainWindow/fileManagerColumnsState", "").toByteArray()))
      {
	ui.tableView->sortByColumn(0, Qt::AscendingOrder);

	for(int i = 0; i < ui.tableView->horizontalHeader()->count(); i++)
	  ui.tableView->resizeColumnToContents(i);
      }
}

void dfilemanager::slotSaveTableHeaderState(void)
{
  dooble::s_settings["mainWindow/fileManagerColumnsState"] =
    ui.tableView->horizontalHeader()->saveState();
}

dfilemanager::~dfilemanager()
{
  /*
  ** Do not attempt to save the table header's state here.
  */

  if(m_history)
    {
      delete m_history;
      m_history = 0;
    }
}

void dfilemanager::load(const QUrl &url)
{
  if(url.isEmpty() || !url.isValid())
    return;

  m_url = url;
  emit urlChanged(m_url);
  emit titleChanged(m_url.toString(QUrl::StripTrailingSlash));
  emit loadStarted();
  emit loadProgress(-1);
  ui.treeView->selectionModel()->clear();

  QModelIndex index = qobject_cast<QFileSystemModel *>
    (ui.treeView->model())->index(url.toLocalFile());
  QModelIndex tmpIndex(index);

  ui.treeView->selectionModel()->select
    (index, QItemSelectionModel::Select);

  while(tmpIndex.isValid())
    {
      ui.treeView->expand(tmpIndex);
      tmpIndex = tmpIndex.parent();
    }

  ui.treeView->scrollTo(index,
			QAbstractItemView::PositionAtCenter);
  ui.tableView->setRootIndex
    (qobject_cast<QFileSystemModel *> (ui.tableView->model())->index
     (url.toLocalFile()));
  ui.pathLabel->clear();
  prepareLabel(m_url);
  m_history->add(m_url);
  emit iconChanged();
  emit loadFinished(true);
}

QUrl dfilemanager::url(void) const
{
  return m_url;
}

QString dfilemanager::title(void) const
{
  return m_url.toString(QUrl::StripTrailingSlash);
}

void dfilemanager::reload(void)
{
  load(m_url);
}

void dfilemanager::back(void)
{
  load(m_history->back().url());
}

void dfilemanager::forward(void)
{
  load(m_history->forward().url());
}

QString dfilemanager::html(void) const
{
  QString str("");
  QModelIndex index(ui.tableView->rootIndex());
  QFileSystemModel *model = qobject_cast<QFileSystemModel *>
    (ui.tableView->model());

  str += "<html>\n";
  str += ui.pathLabel->text() + "\n";
  str += "<table border=\"0\">\n";
  str += "<tr>\n";

  for(int i = 0; i < ui.tableView->horizontalHeader()->count(); i++)
    str += QString("<th>%1</th>\n").arg
      (model->headerData(i, Qt::Horizontal).toString());

  str += "</tr>\n";

  for(int i = 0; index.child(i, 0).isValid(); i++)
    {
      str += "<tr>\n";

      for(int j = 0; j < ui.tableView->horizontalHeader()->count(); j++)
	str += QString("<td>%1</td>").arg
	  (model->data(index.child(i, j)).toString());

      str += "</tr>\n";
    }

  str += "</table>\n";
  str += "</html>";
  return str;
}

void dfilemanager::slotDoubleClicked(const QModelIndex &index)
{
  if(qobject_cast<QFileSystemModel *> (ui.tableView->model())->isDir(index))
    {
      m_url = QUrl::fromLocalFile
	(qobject_cast<QFileSystemModel *> (ui.tableView->model())->
	 filePath(index));
      emit urlChanged(m_url);
      emit titleChanged(m_url.toString(QUrl::StripTrailingSlash));
      emit loadStarted();
      emit loadProgress(-1);
      ui.tableView->setRootIndex(index);
      ui.treeView->selectionModel()->clear();

      QModelIndex tmpIndex;
      QModelIndex currentIndex = ui.treeView->currentIndex();
      QModelIndex destinationIndex = qobject_cast<QFileSystemModel *>
	(ui.treeView->model())->index
	(qobject_cast<QFileSystemModel *> (ui.tableView->model())->
	 filePath(index));

      ui.treeView->selectionModel()->select
	(destinationIndex, QItemSelectionModel::Select);
      tmpIndex = destinationIndex;

      while(currentIndex != tmpIndex)
	{
	  ui.treeView->expand(tmpIndex);
	  tmpIndex = tmpIndex.parent();
	}

      ui.treeView->scrollTo(destinationIndex,
			    QAbstractItemView::PositionAtCenter);
      prepareLabel(m_url);
      m_history->add(m_url);
      emit loadFinished(true);
    }
}

void dfilemanager::slotClicked(const QModelIndex &index)
{
  m_url = QUrl::fromLocalFile
    (qobject_cast<QFileSystemModel *> (ui.treeView->model())->filePath(index));
  emit urlChanged(m_url);
  emit titleChanged(m_url.toString(QUrl::StripTrailingSlash));
  emit loadStarted();
  emit loadProgress(-1);
  ui.treeView->selectionModel()->select
    (index, QItemSelectionModel::ClearAndSelect);
  ui.tableView->selectionModel()->clear();
  ui.tableView->setRootIndex
    (qobject_cast<QFileSystemModel *> (ui.tableView->model())->index
     (qobject_cast<QFileSystemModel *>
      (ui.treeView->model())->filePath(index)));
  prepareLabel(m_url);
  m_history->add(m_url);
  emit loadFinished(true);
}

void dfilemanager::prepareLabel(const QUrl &url)
{
  QString path(url.toString(QUrl::StripTrailingSlash));
  QString richText("");
  QString compositePath("");

#ifdef Q_OS_WIN
  path.remove("file:///");
#else
  if(path.startsWith("file:///"))
    path.remove("file://");
  else if(path == "file:")
    path = "/";
#endif

  QStringList list(path.split("/"));

  while(!list.isEmpty())
    {
      compositePath += list.first() + "/";
      richText += QString("<a href = \"%1\">%2</a>")
	.arg(compositePath).arg(list.takeFirst());

      if(!list.isEmpty())
	richText += "/";
    }

  pathLabelPlainText = path;
  ui.pathLabel->setText(richText);
}

void dfilemanager::slotLabelClicked(const QString &link)
{
  load(QUrl::fromLocalFile(link));
}

void dfilemanager::slotDirectoryRemoved(const QModelIndex &index,
					int start, int end)
{
  if(start < 0) // How about validating end?
    return;

  QFileSystemModel *model = qobject_cast<QFileSystemModel *>
    (ui.treeView->model());

  for(int i = start; i <= end; i++)
    if(model->filePath(index.child(i, 0)) == pathLabelPlainText)
      /*
      ** Proceed to the parent directory.
      */

      slotClicked(index);
}

void dfilemanager::slotCustomContextMenuRequest(const QPoint &pos)
{
  QModelIndex index;
  QFileSystemModel *model = 0;

  if(qobject_cast<QTreeView *> (sender()))
    {
      QMenu menu(this);
      QAction *action = 0;

      index = ui.treeView->indexAt(pos);
      model = qobject_cast<QFileSystemModel *> (ui.treeView->model());

      QFile::Permissions permissions = model->permissions(index);

      if(!model->fileName(index).isEmpty() && model->isDir(index))
	{
	  action = menu.addAction(tr("Create &Sub-Directory"),
				  this,
				  SLOT(slotTreeMenuAction(void)));
	  action->setEnabled
	    (permissions & QFile::WriteUser);
	  action->setData("create_subdirectory");
	  menu.addSeparator();
	}

      if(index.isValid())
	{
	  action = menu.addAction
	    (tr("&Delete"),
	     this,
	     SLOT(slotTreeMenuAction(void)));
	  action->setEnabled
	    (permissions & QFile::WriteUser);
	  action->setData("delete");
	  menu.addSeparator();
	  action = menu.addAction
	    (tr("&Rename"),
	     this,
	     SLOT(slotTreeMenuAction(void)));
	  action->setEnabled
	    (permissions & QFile::WriteUser);
	  action->setData("rename");
	  menu.exec(ui.treeView->mapToGlobal(pos));
	}
    }
  else if(qobject_cast<QTableView *> (sender()))
    {
      QMenu menu(this);
      QAction *action = 0;

      model = qobject_cast<QFileSystemModel *> (ui.tableView->model());
      index = model->index(pathLabelPlainText);

      QFile::Permissions permissions = model->permissions(index);

      action = menu.addAction(tr("&Create Directory"),
			      this,
			      SLOT(slotTableMenuAction(void)));
      action->setEnabled
	(permissions & QFile::WriteUser);
      action->setData("create_directory");
      index = ui.tableView->indexAt(pos);

      if(!model->fileName(index).isEmpty() && model->isDir(index))
	{
	  action = menu.addAction(tr("Create &Sub-directory"),
				  this,
				  SLOT(slotTableMenuAction(void)));
	  action->setEnabled
	    (permissions & QFile::WriteUser);
	  action->setData("create_subdirectory");
	}

      if(index.isValid())
	{
	  QFile::Permissions permissions = model->permissions(index);

	  menu.addSeparator();
	  action = menu.addAction
	    (tr("&Delete"),
	     this,
	     SLOT(slotTableMenuAction(void)));
	  action->setEnabled
	    (permissions & QFile::WriteUser);
	  action->setData("delete");
	  menu.addSeparator();
	  action = menu.addAction
	    (tr("&Rename"),
	     this,
	     SLOT(slotTableMenuAction(void)));
	  action->setEnabled
	    (permissions & QFile::WriteUser);
	  action->setData("rename");
	}

      menu.exec(ui.tableView->mapToGlobal(pos));
    }
}

void dfilemanager::slotTreeMenuAction(void)
{
  QAction *action = qobject_cast<QAction *> (sender());

  if(action)
    menuAction(action->data().toString(), ui.treeView);
}

void dfilemanager::slotTableMenuAction(void)
{
  QAction *action = qobject_cast<QAction *> (sender());

  if(action)
    menuAction(action->data().toString(), ui.tableView);
}

void dfilemanager::menuAction(const QString &action, QAbstractItemView *view)
{
  if(!view)
    return;

  /*
  ** Care should be taken when calling this method programatically.
  */

  if(action == "delete")
    deleteSelections(view);
  else if(action == "rename")
    {
      view->openPersistentEditor(view->currentIndex());
      view->setCurrentIndex(view->currentIndex());
      view->edit(view->currentIndex());
    }
  else if(action == "create_directory")
    {
      QFileSystemModel *model = qobject_cast<QFileSystemModel *>
	(ui.tableView->model());

      if(!model)
	return;

      QModelIndex index;

      for(int i = 1;; i++)
	{
	  QString filePath(pathLabelPlainText);

	  if(QFile(QString("%1/%2_%3").arg(filePath).arg("untitled").
		   arg(i)).exists())
	    continue;

	  index = model->mkdir(model->index(filePath),
			       QString("%1_%2").arg("untitled").arg(i));
	  ui.tableView->openPersistentEditor(index);
	  ui.tableView->setCurrentIndex(index);
	  ui.tableView->edit(index);
	  break;
	}
    }
  else if(action == "create_subdirectory")
    {
      QModelIndex index(view->currentIndex());
      QFileSystemModel *model = qobject_cast<QFileSystemModel *>
	(view->model());

      if(!model)
	return;

      for(int i = 1;; i++)
	{
	  QString filePath(model->filePath(index));

	  if(QFile(QString("%1/%2_%3").arg(filePath).arg("untitled").
		   arg(i)).exists())
	    continue;

	  index = model->mkdir(model->index(filePath),
			       QString("%1_%2").arg("untitled").arg(i));

	  if(qobject_cast<QTreeView *> (view) == ui.treeView)
	    {
	      view->openPersistentEditor(index);
	      view->setCurrentIndex(index);
	      view->edit(index);
	    }

	  break;
	}
    }
}

void dfilemanager::slotCloseEditor(QWidget *editor,
				   QAbstractItemDelegate::EndEditHint hint)
{
  Q_UNUSED(editor);
  Q_UNUSED(hint);

  if(sender() == ui.treeView->itemDelegate())
    {
      ui.treeView->closePersistentEditor(ui.treeView->currentIndex());
      ui.treeView->sortByColumn(0);
    }
  else if(sender() == ui.tableView->itemDelegate())
    {
      ui.tableView->closePersistentEditor(ui.tableView->currentIndex());
      ui.tableView->sortByColumn(0);
    }
}

void dfilemanager::keyPressEvent(QKeyEvent *event)
{
  if(event && event->key() == Qt::Key_Delete)
    deleteSelections(ui.tableView);

  QWidget::keyPressEvent(event);
}

void dfilemanager::deleteSelections(QAbstractItemView *view)
{
  const QFileSystemModel *model = qobject_cast<const QFileSystemModel *>
    (view->currentIndex().model());

  if(!model)
    return;

  QModelIndexList list(view->selectionModel()->selectedRows());

  if(list.size() == 1)
    {
      QSettings settings(dooble::s_settings.value("iconSet").toString(),
			 QSettings::IniFormat);
      QMessageBox mb(QMessageBox::Warning,
		     tr("Dooble Web Browser: Warning"),
		     QString(tr("Are you sure that you wish to delete "
				"%1?")).arg(model->fileName(list[0])),
		     QMessageBox::No | QMessageBox::Yes,
		     this);

      mb.setWindowIcon
	(QIcon(settings.value("mainWindow/windowIcon").toString()));

      for(int i = 0; i < mb.buttons().size(); i++)
	if(mb.buttonRole(mb.buttons().at(i)) == QMessageBox::YesRole)
	  mb.buttons().at(i)->setIcon
	    (QIcon(settings.value("okButtonIcon").toString()));
	else
	  mb.buttons().at(i)->setIcon
	    (QIcon(settings.value("cancelButtonIcon").toString()));

      if(mb.exec() == QMessageBox::Yes)
	if(model && list[0].isValid())
	  model->remove(list[0]);
    }
  else
    {
      QSettings settings(dooble::s_settings.value("iconSet").toString(),
			 QSettings::IniFormat);
      QMessageBox mb(QMessageBox::Warning,
		     tr("Dooble Web Browser: Warning"),
		     tr("Are you sure that you wish to delete the selected "
			"entries?"),
		     QMessageBox::No | QMessageBox::Yes,
		     this);

      mb.setWindowIcon
	(QIcon(settings.value("mainWindow/windowIcon").toString()));

      for(int i = 0; i < mb.buttons().size(); i++)
	if(mb.buttonRole(mb.buttons().at(i)) == QMessageBox::YesRole)
	  mb.buttons().at(i)->setIcon
	    (QIcon(settings.value("okButtonIcon").toString()));
	else
	  mb.buttons().at(i)->setIcon
	    (QIcon(settings.value("cancelButtonIcon").toString()));

      if(mb.exec() == QMessageBox::Yes)
	while(!list.isEmpty())
	  if(model && list.first().isValid())
	    model->remove(list.takeFirst());
	  else
	    break;
    }

  list.clear();
}
