/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            filelist.cc
 *
 *  Mon Nov 30 15:35:52 CET 2009
 *  Copyright 2009 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of DrumGizmo.
 *
 *  DrumGizmo 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.
 *
 *  DrumGizmo 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 DrumGizmo; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "filelist.h"

#include <QFileDialog>
#include <QFileInfo>
#include <QMenu>

#include <iostream>

#include "itemeditor.h"
#include "project.h"

#include <QAbstractItemModel>
#include <QTreeView>

#include <iostream>

#include <QComboBox>
#include <QLineEdit>
#include <QCheckBox>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QHeaderView>
#include <QStyleOptionButton>
#include <QApplication>

#include <sndfile.h>

class ChannelMapDeligate
	: public QStyledItemDelegate
{
public:
	ChannelMapDeligate(Instrument& instrument)
		: instrument(instrument)
	{
	}

	QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
	                      const QModelIndex &index) const override
	{
		// Main channel:
		if(index.column() == 2)
		{
			auto w = new QCheckBox(parent);
			auto audiofile_ids = instrument.getAudioFileList();
			auto audiofile_id = audiofile_ids.begin() + index.row();
			auto& audiofile = instrument.getAudioFile(*audiofile_id);

			connect(w, &QCheckBox::stateChanged,
			        [&audiofile](int state)
			        {
				        audiofile.setMainChannel(state == Qt::Checked);
			        });

			return w;
		}

		// Name:
		if(index.column() == 3)
		{
			auto w = new QLineEdit(parent);

			auto audiofile_ids = instrument.getAudioFileList();
			auto audiofile_id = audiofile_ids.begin() + index.row();
			auto& audiofile = instrument.getAudioFile(*audiofile_id);

			connect(w, &QLineEdit::textEdited,
			        [&audiofile](const QString& name)
			        {
				        audiofile.setName(name);
			        });
			return w;
		}

		// Channel Map ID:
		if(index.column() == 4)
		{
			auto w = new QComboBox(parent);
			w->addItem(tr("<None>"), -1);
			auto channel_ids = instrument.getProject().getChannelList();
			for(auto channel_id : channel_ids)
			{
				const auto& channel = instrument.getProject().getChannel(channel_id);
				w->addItem(channel.getChannelName(), channel.getId());
			}

			auto audiofile_ids = instrument.getAudioFileList();
			auto audiofile_id = audiofile_ids.begin() + index.row();
			auto& audiofile = instrument.getAudioFile(*audiofile_id);

			connect(w,
			        // This wierd line points the compiler to the correct overloaded
			        // version of QComboBox::currentIndexChanged
			        // ie. chooses the (int) version over the (const QString&) version
			        QOverload<int>::of(&QComboBox::currentIndexChanged),
			        [w, &audiofile](int idx)
			        {
				        audiofile.setChannelMapId(w->itemData(idx).toInt());
			        });
			return w;
		}

		return QStyledItemDelegate::createEditor(parent, option, index);
	}

	void setEditorData(QWidget *editor, const QModelIndex &index) const override
	{
		// Main channel:
		if(index.column() == 2)
		{
			auto w = static_cast<QCheckBox*>(editor);
			auto b = index.data(Qt::EditRole).toBool();
			w->setCheckState(b ? Qt::Checked : Qt::Unchecked);
		}

		// Name:
		if(index.column() == 3)
		{
			auto w = static_cast<QLineEdit*>(editor);
			auto s = index.data(Qt::EditRole).toString();
			w->setText(s);
		}

		// Channel Map ID:
		if(index.column() == 4)
		{
			auto w = static_cast<QComboBox*>(editor);
			auto i = w->findData(index.data(Qt::EditRole).toInt());
			w->setCurrentIndex(i);
		}
}

	void setModelData(QWidget *editor, QAbstractItemModel *model,
	                  const QModelIndex &index) const override
	{
		// Main channel:
		if(index.column() == 2)
		{
			model->setData(index,
			               static_cast<QCheckBox*>(editor)->checkState() == Qt::Checked,
			               Qt::EditRole);
		}

		// Name:
		if(index.column() == 3)
		{
			model->setData(index, static_cast<QLineEdit*>(editor)->text(),
			               Qt::EditRole);
		}

		// Channel Map ID:
		if(index.column() == 4)
		{
			model->setData(index, static_cast<QComboBox*>(editor)->currentData(),
			               Qt::EditRole);
		}
	}

	void paint(QPainter* painter, const QStyleOptionViewItem& option,
	           const QModelIndex& index) const override
	{
		if(index.column() == 2) // Main Channel
		{
			auto audiofile_ids = instrument.getAudioFileList();
			auto audiofile_id = audiofile_ids.begin() + index.row();
			const auto& audiofile = instrument.getAudioFile(*audiofile_id);

			bool checked = audiofile.getMainChannel();
			QStyleOptionButton butOpt;
			butOpt.state = QStyle::State_Enabled;
			butOpt.state |= checked ? QStyle::State_On : QStyle::State_Off;
			butOpt.rect = option.rect;
			QApplication::style()->drawControl( QStyle::CE_CheckBox,
			                                    &butOpt, painter );
		}
		else
		{
			QStyledItemDelegate::paint(painter, option, index);
		}
	}

private:
	Instrument& instrument;
};

class FileDataModel
	: public QAbstractItemModel
{
public:
	FileDataModel(Instrument& instrument)
		: instrument(instrument)
	{
	}

	QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
	{
		if(!hasIndex(row, column, parent))
		{
			return QModelIndex();
		}

		auto audiofile_ids = instrument.getAudioFileList();
		if(!parent.isValid())
		{
			if(row < audiofile_ids.size())
			{
				return createIndex(row, column, &instrument);
			}
			else
			{
				return QModelIndex(); // row is out of bounds.
			}
		}

		return QModelIndex();
	}

	QModelIndex parent(const QModelIndex &index) const override
	{
		return QModelIndex(); // no parent
	}

	int rowCount(const QModelIndex &parent = QModelIndex()) const override
	{
		if(parent.column() > 0) // only return row count on first column
		{
			return 0;
		}

		if(!parent.isValid())
		{
			auto audiofile_ids = instrument.getAudioFileList();
			return audiofile_ids.size(); // root level
		}

		return 0; // no children
	}

	int columnCount(const QModelIndex &parent = QModelIndex()) const override
	{
		return 5;
	}

	QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
	{
		if(!index.isValid())
		{
			return QVariant();
		}

		auto audiofile_ids = instrument.getAudioFileList();

		auto audiofile_id = audiofile_ids.begin() + index.row();
		const auto& audiofile = instrument.getAudioFile(*audiofile_id);

		if(role == Qt::DecorationRole )
		{
			if(index.column() == 0)
			{
				if(audiofile.getAbsoluteFile() == instrument.getMasterFile())
				{
					return QPixmap(":/icons/master.png");
				}
				else
				{
					return QPixmap(":/icons/file.png");
				}
			}
		}

		if(role == Qt::DisplayRole)
		{
			switch(index.column())
			{
			case 0: return QVariant(); // Master
			case 1: return audiofile.getFile();
			case 2: return audiofile.getMainChannel() ? "x" : " ";
			case 3: return audiofile.getName();
			case 4:
				{
					auto channelMapId = audiofile.getChannelMapId();
					if(channelMapId == -1)
					{
						return tr("<None>");
					}
					return instrument.getProject().getChannel(channelMapId).getChannelName();
				}
			default: return QVariant();
			}
		}
		else if(role == Qt::EditRole)
		{
			switch(index.column())
			{
			case 0: return QVariant(); // Master
			case 1: return audiofile.getFile();
			case 2: return audiofile.getMainChannel();
			case 3: return audiofile.getName();
			case 4: return audiofile.getChannelMapId();
			default: return QVariant();
			}
		}
		else

		{
			return QVariant();
		}
	}

	QVariant headerData(int section, Qt::Orientation orientation, int role) const override
	{
		if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
		{
			switch(section)
			{
			case 0: return tr("M");
			case 1: return tr("Filename");
			case 2: return tr("m");
			case 3: return tr("Name");
			case 4: return tr("Kit Channel");
			default: return QVariant();
			}
		}

		return QVariant();
	}

	Qt::ItemFlags flags(const QModelIndex &index) const override
	{
		if(!index.isValid())
		{
			return 0;
		}

		switch(index.column())
		{
		case 0: // Master
			return QAbstractItemModel::flags(index);
		case 1: // File
			return QAbstractItemModel::flags(index);
		case 2: // Main channel
			return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
		case 3: // Name
			return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
		case 4: // Channel map id
			return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
		}
	}

	bool setData(const QModelIndex &index, const QVariant &value,
	             int role = Qt::EditRole) override
	{
		auto audiofile_ids = instrument.getAudioFileList();

		if(index.row() > audiofile_ids.size() ||
		   index.column() > (columnCount() - 1))
		{
			return false;
		}

		auto audiofile_id = audiofile_ids.begin() + index.row();
		auto& audiofile = instrument.getAudioFile(*audiofile_id);

		switch(index.column())
		{
		case 0: // Master
			break;
		case 1: // File
			break;
		case 2: // Main Channel
			audiofile.setMainChannel(value.toBool());
			break;
		case 3: // Name
			audiofile.setName(value.toString());
			break;
		case 4: // Channel map id
			audiofile.setChannelMapId(value.toInt());
			break;
		default: break;
		}

		return true;
	}

	void refresh()
	{
		beginResetModel();
		endResetModel();
	}

private:
	Instrument& instrument;
};

FileList::FileList(Instrument& instrument)
	: instrument(instrument)
{
	model = new FileDataModel(instrument);
	setModel(model);
	setItemDelegate(new ChannelMapDeligate(instrument));
	setEditTriggers(QAbstractItemView::AllEditTriggers); // show list on click

	setContextMenuPolicy(Qt::CustomContextMenu); // enable context menu
	connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
	        this, SLOT(onCustomContextMenu(const QPoint &)));
	setRootIsDecorated(false);
	header()->resizeSection(0, 24);
	header()->resizeSection(2, 24);

	connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
	        this, SLOT(selectionChanged(const QModelIndex&)));

	createMenus();
}

static double getSamplerate(const QString& file)
{
	SF_INFO sf_info;
	SNDFILE *fh = sf_open(file.toStdString().c_str(), SFM_READ, &sf_info);
	if(!fh)
	{
		return -1.0;
	}

	return sf_info.samplerate;
}

void FileList::addFiles()
{
	auto root = instrument.getProject().getRawFileRoot();
	if(path.isEmpty())
	{
		path = root;
	}

	QStringList files =
		QFileDialog::getOpenFileNames(this, tr("Open file"),
		                              path, tr("Audio Files (*.wav)"));
	QStringList::Iterator i = files.begin();
	while(i != files.end())
	{
		QString file = *i;
		QFileInfo fi(file);
		QString name = fi.baseName();
		path = fi.absolutePath();

		double samplerate = getSamplerate(file);
		if(samplerate == -1.0)
		{
			// Error reading file. Skip it.
			std::cout << "Error reading file. Skip it.\n";
			++i;
			continue;
		}

		if(instrument.getProject().getProjectSamplerate() == -1.0)
		{
			// Samplerate not yet set
			instrument.getProject().setProjectSamplerate(samplerate);
		}

		if(instrument.getProject().getProjectSamplerate() != samplerate)
		{
			// Samplerate of file differs from the projeft samplerate
			std::cout << "Samplerate of file differs from the project samplerate:\n";
			std::cout << " project: " << instrument.getProject().getProjectSamplerate() << std::endl;
			std::cout << " file: " << samplerate << std::endl;
		}

		if(root == file.left(root.length()))
		{
			file = file.mid(root.length() + 1);
		}

		auto id = instrument.createAudioFile();
		auto& audiofile = instrument.getAudioFile(id);
		audiofile.setName(name);
		audiofile.setFile(file);
		audiofile.setChannelMapId(-1);
		audiofile.setMainChannel(false);

		i++;
	}

	model->refresh();
}

void FileList::selectionChanged(const QModelIndex &index)
{
	auto audiofile_ids = instrument.getAudioFileList();
	auto audiofile_id = audiofile_ids.begin() + index.row();
	const auto& audiofile = instrument.getAudioFile(*audiofile_id);

	instrument.setMasterFile(audiofile.getFile());

	emit masterFileChanged(audiofile.getAbsoluteFile());
	//setMasterFile(i);
	model->refresh();
}

void FileList::createMenus()
{
	menu = new QMenu();

	setMasterAction = new QAction(tr("Set as Master (dbl-click)"), this);
	connect(setMasterAction, SIGNAL(triggered()), this, SLOT(setMaster()));

	removeAction = new QAction(tr("Remove"), this);
	connect(removeAction, SIGNAL(triggered()), this, SLOT(removeFile()));

	removeAllAction = new QAction(tr("Remove all"), this);
	connect(removeAllAction, SIGNAL(triggered()), this, SLOT(removeAllFiles()));

	menu->addAction(setMasterAction);
	menu->addAction(removeAction);
	menu->addSeparator();
	menu->addAction(removeAllAction);
}

void FileList::onCustomContextMenu(const QPoint &point)
{
	QModelIndex index = indexAt(point);
	if(index.isValid())
	{
		menu->popup(viewport()->mapToGlobal(point));
	}
}

void FileList::setMaster()
{
//	setMasterFile(activeItem);
}

void FileList::removeFile()
{
	auto audiofile_ids = instrument.getAudioFileList();
	auto audiofile_id = audiofile_ids.begin() + currentIndex().row();
	auto& audiofile = instrument.getAudioFile(*audiofile_id);
	if(instrument.getMasterFile() == audiofile.getAbsoluteFile())
	{
		emit masterFileChanged(""); // Clear canvas
	}

	instrument.deleteAudioFile(*audiofile_id);

	//emit fileRemoved(file, name);
}

void FileList::removeAllFiles()
{
	instrument.setMasterFile("");
	auto audiofile_ids = instrument.getAudioFileList();
	for(auto audiofile_id : audiofile_ids)
	{
		instrument.deleteAudioFile(audiofile_id);
	}
	reset();

	emit masterFileChanged(""); // Clear canvas
	emit allFilesRemoved();
}
