/*
 *  Kchmviewer - a CHM and EPUB file viewer with broad language support
 *  Copyright (C) 2004-2014 George Yunaev, gyunaev@ulduzsoft.com
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <QAbstractItemModel>
#include <QAction>
#include <QByteArray>
#include <QComboBox>
#include <QGroupBox>
#include <QIcon>
#include <QList>
#include <QListView>
#include <QMap>
#include <QMimeData>
#include <QModelIndex>
#include <QModelIndexList>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QToolBar>
#include <QVariant>
#include <Qt>
#include <QtGlobal>

#include "toolbarmanager.h"

#include "toolbareditor.h"


static const char* ACTION_MIME_FORMAT = "application/vnd.action.list";


//
// A subclassed list model which supports toolbars
//
class ActionListModel : public QAbstractListModel
{
	public:
		ActionListModel( ToolbarEditor* editor, const QStringList& actions, bool actionSource )
			: QAbstractListModel( editor )
		{
			m_editor = editor;
			m_actions = actions;
			m_actionSource = actionSource;
		}

		int rowCount( const QModelIndex& ) const
		{
			return m_actions.size();
		}

		QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const
		{
			if ( !index.isValid() || index.row() < 0 || index.row() >= m_actions.size() )
				return QVariant();

			if ( m_actions[ index.row() ] == ToolbarManager::separatorName() )
			{
				if ( role == Qt::DisplayRole )
					return "--- separator ---";

				return QVariant();
			}

			QAction* action = m_editor->findAction( m_actions[ index.row() ] );

			if ( !action )
				return QVariant();

			switch ( role )
			{
			case Qt::DisplayRole:
			case Qt::ToolTipRole:
				return action->toolTip();

			case Qt::DecorationRole:
				return action->icon();
			}

			return QVariant();
		}

		Qt::ItemFlags flags( const QModelIndex& index ) const
		{
			if ( index.isValid() )
				return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled;
			else
				return Qt::ItemIsDropEnabled;
		}

		Qt::DropActions supportedDropActions() const
		{
			return Qt::MoveAction;
		}

		// Required for drag and drop
		bool insertRows( int row, int count, const QModelIndex& parent = QModelIndex() )
		{
			int start = row;
			int end = row + count - 1;
			emit beginInsertRows( parent, start, end );

			for ( ; count > 0; count-- )
				m_actions.insert( row, 0 );

			emit endInsertRows();
			return true;
		}

		// Required for drag and drop
		bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() )
		{
			// Do not remove a row with separator
			if ( m_actionSource && m_actions[row ] == ToolbarManager::separatorName() )
				return true;

			int start = row;
			int end = row + count - 1;
			emit beginRemoveRows( parent, start, end );

			for ( ; count > 0; count-- )
				m_actions.removeAt( row );

			emit endRemoveRows();
			return true;
		}

		// Set drag type for drag/drop
		QStringList mimeTypes() const
		{
			QStringList types;
			types << ACTION_MIME_FORMAT;
			return types;
		}

		QMimeData* mimeData( const QModelIndexList& indexes ) const
		{
			if ( indexes.size() != 1 || !indexes[0].isValid() )
				return 0;

			QMimeData* mimeData = new QMimeData();
			mimeData->setData( ACTION_MIME_FORMAT, m_actions[ indexes[0].row() ].toUtf8() );
			return mimeData;
		}

		bool dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent )
		{
			if ( action == Qt::IgnoreAction )
				return true;

			if ( !data->hasFormat( ACTION_MIME_FORMAT ) )
				return false;

			// Our list has only one column
			if ( column > 0 )
				return false;

			// We initially examine the row number supplied to see if we can use it to insert items into the model,
			// regardless of whether the parent index is valid or not.
			// If the parent model index is valid, the drop occurred on an item. In this simple list model, we find
			// out the row number of the item and use that value to insert dropped items into the top level of the model.
			int beginRow;

			if ( row != -1 )
				beginRow = row;
			else if ( parent.isValid() )
				beginRow = parent.row();
			else
				beginRow = rowCount( QModelIndex() );

			QByteArray actionName = data->data( ACTION_MIME_FORMAT );

			// Do not add this_is_separator to the source actionlist again
			if ( m_actionSource && actionName == ToolbarManager::separatorName() )
				return true;

			// The strings can then be inserted into the underlying data store. For consistency, this can be done
			// through the model's own interface.
			insertRows( beginRow, 1, QModelIndex() );
			m_actions[ beginRow ] = actionName;

			emit dataChanged( index( beginRow, 0, QModelIndex() ), index( beginRow, 0, QModelIndex() ) );
			return true;
		}

		QStringList actions() const
		{
			return m_actions;
		}

	private:
		ToolbarEditor*          m_editor;
		QStringList             m_actions;
		bool                    m_actionSource;
};


ToolbarEditor::ToolbarEditor( QWidget* parent )
	: QDialog( parent ), Ui::ToolbarEditor()
{
	setupUi( this );

	m_allowActionsWithoutIcons = true;
	m_activeToolbar = 0;

	connect( boxToolbars, SIGNAL( activated( int ) ), this, SLOT( toolbarSelected( int ) ) );
}

ToolbarEditor::~ToolbarEditor()
{
}

void ToolbarEditor::addToolbar( QToolBar* toolbar )
{
	m_toolbars.push_back( toolbar );
}

void ToolbarEditor::addToolbars( QList<QToolBar*> toolbars )
{
	foreach ( QToolBar* t, toolbars )
		m_toolbars.push_back( t );
}

void ToolbarEditor::setAvailableActions( QList<QAction*> availableActions )
{
	m_availableActions = availableActions;
}

int ToolbarEditor::exec()
{
	if ( m_toolbars.size() == 0 )
		qFatal( "ToolbarEditor::exec: no toolbars selected, and editing is disabled. Aborting." );

	// Hide the group allowing to edit toolbar list if we do not allow editing, and there
	// is only one toolbar
	if ( m_toolbars.size() == 1 )
		groupEditToolbars->hide();

	// Init the toolbar(s) actions
	foreach ( QToolBar* toolbar, m_toolbars )
	{
		initToolbarActions( toolbar );

		// Add a toolbar into the combobox
		boxToolbars->addItem( toolbar->windowTitle() );
	}

	m_activeToolbar = m_toolbars.front();
	setupViews( m_activeToolbar );

	return QDialog::exec();
}

QAction* ToolbarEditor::findAction( const QString& objectname ) const
{
	foreach ( QAction* action, m_availableActions )
		if ( ToolbarManager::actionName( action ) == objectname )
			return action;

	return 0;
}

void ToolbarEditor::initToolbarActions( QToolBar* toolbar )
{
	QStringList selected;

	foreach ( QAction* action, toolbar->actions() )
	{
		if ( action->isSeparator() )
			selected.push_back( ToolbarManager::separatorName() );
		else if ( ToolbarManager::hasAction( m_availableActions, action ) )
			selected.push_back( ToolbarManager::actionName( action ) );
	}

	m_selected[ toolbar ] = selected;
}

void ToolbarEditor::setupViews( QToolBar* toolbar )
{
	if ( !m_selected.contains( toolbar ) )
		qFatal( "ToolbarEditor::setupViews: invalid toolbar" );

	QStringList actions = m_selected[ toolbar ];

	// Create the list of available actions
	QStringList available;

	foreach ( QAction* action, m_availableActions )
	{
		if ( !actions.contains( ToolbarManager::actionName( action ) ) )
		{
			if ( m_allowActionsWithoutIcons || !action->icon().isNull() )
				available.push_back( ToolbarManager::actionName( action ) );
		}
	}

	if ( !available.contains( ToolbarManager::separatorName() ) )
		available.push_back( ToolbarManager::separatorName() );

	// Init models for available and selected actions
	ActionListModel* newModelAvailable = new ActionListModel( this, available, true );
	ActionListModel* newModelSelected = new ActionListModel( this, actions, false );

	// Set them, and get the old models
	ActionListModel* oldModelAvailable = ( ActionListModel* ) listAvailable->model();
	ActionListModel* oldModelSelected = ( ActionListModel* ) listActions->model();

	listActions->setModel( newModelSelected );
	listAvailable->setModel( newModelAvailable );

	// Remove old models
	delete oldModelAvailable;
	delete oldModelSelected;

	m_activeToolbar = toolbar;
}

void ToolbarEditor::toolbarSelected( int index )
{
	if ( index == -1 )
		return;

	QToolBar* selected = m_toolbars[ index ];

	if ( selected == m_activeToolbar )
		return;

	// Copy the settings from active toolbar, and switch to new toolbar
	updateToolbarActions( m_activeToolbar );
	setupViews( selected );
}

void ToolbarEditor::updateToolbarActions( QToolBar* toolbar )
{
	if ( !m_selected.contains( toolbar ) )
		qFatal( "ToolbarEditor::updateToolbarActions: invalid toolbar" );

	ActionListModel* model = ( ActionListModel* ) listActions->model();
	m_selected[ toolbar ] = model->actions();
}

QStringList ToolbarEditor::actionsForToolbar( QToolBar* toolbar )
{
	if ( !m_selected.contains( toolbar ) )
		return QStringList();
	else
		return m_selected[ toolbar ];
}

void ToolbarEditor::accept()
{
	// Copy the last settings
	updateToolbarActions( m_activeToolbar );

	// Reset models
	delete listAvailable->model();
	delete listActions->model();
	listActions->setModel( 0 );
	listAvailable->setModel( 0 );

	QDialog::accept();
}
