/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            instrumentwidget.cc
 *
 *  Sat May 12 15:38:38 CEST 2018
 *  Copyright 2018 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 "instrumentwidget.h"


#include <iostream>

#include <QHBoxLayout>
#include <QVBoxLayout>

#include <QPushButton>
#include <QLabel>
#include <QStatusBar>
#include <QApplication>
#include <QDockWidget>
#include <QToolBar>
#include <QAction>
#include <QMenuBar>
#include <QFileDialog>
#include <QIntValidator>
#include <QTabWidget>
#include <QProgressBar>
#include <QTime>
#include <QSpinBox>

#include "sleep.h"
#include "canvastool.h"
#include "canvastoolthreshold.h"
#include "volumefader.h"
#include "selectioneditor.h"
#include "zoomslider.h"
#include "settings.h"
#include "project.h"

#define MAXVAL 10000000L

static void addTool(QToolBar* toolbar, Canvas* canvas, CanvasTool* tool)
{
	QAction* action = new QAction(tool->name(), toolbar);
	action->setCheckable(true);
	toolbar->addAction(action);
	tool->connect(action, SIGNAL(toggled(bool)), tool, SLOT(setActive(bool)));
	tool->setActive(false);
	canvas->tools.push_back(tool);
}

InstrumentWidget::InstrumentWidget(Settings& settings, Instrument& instrument)
	: settings(settings)
	, instrument(instrument)
{
	setProperty("id", instrument.getId());

	{
		int start = 44100 * 60;
		Selection p(start, start + 44100 * 60, 0, 0); // one minute selection
		selections_preview.add(p);
	}

	QWidget* central = new QWidget();
	QHBoxLayout* lh = new QHBoxLayout();
	QVBoxLayout* lv = new QVBoxLayout();
	central->setLayout(lv);
	setCentralWidget(central);

	extractor = new AudioExtractor(instrument, this);
	canvaswidget = new CanvasWidget(this);

	QToolBar* toolbar = addToolBar(tr("Tools"));
	listen = new CanvasToolListen(canvaswidget->canvas, player);
	addTool(toolbar, canvaswidget->canvas, listen);
	threshold = new CanvasToolThreshold(canvaswidget->canvas, instrument);
	canvaswidget->canvas->tools.push_back(threshold);
	tool_selections = new CanvasToolSelections(canvaswidget->canvas, selections,
	                                           selections_preview);
	connect(threshold, SIGNAL(thresholdChanged(double)),
	        tool_selections, SLOT(thresholdChanged(double)));
	connect(threshold, SIGNAL(thresholdChanging(double)),
	        tool_selections, SLOT(thresholdChanged(double)));
	connect(&selections, SIGNAL(activeChanged(sel_id_t)),
	        canvaswidget->canvas, SLOT(update()));
	connect(&selections, SIGNAL(updated(sel_id_t)),
	        canvaswidget->canvas, SLOT(update()));
	addTool(toolbar, canvaswidget->canvas, tool_selections);

	sorter = new SampleSorter(selections, selections_preview, instrument);
	connect(&selections, SIGNAL(added(sel_id_t)),
	        sorter, SLOT(addSelection(sel_id_t)));
	connect(&selections_preview, SIGNAL(added(sel_id_t)),
	        sorter, SLOT(addSelectionPreview(sel_id_t)));
	connect(&selections, SIGNAL(updated(sel_id_t)), sorter, SLOT(relayout()));
	connect(&selections_preview, SIGNAL(updated(sel_id_t)),
	        sorter, SLOT(relayout()));
	connect(&selections, SIGNAL(removed(sel_id_t)), sorter, SLOT(relayout()));
	connect(&selections_preview, SIGNAL(removed(sel_id_t)),
	        sorter, SLOT(relayout()));
	connect(&selections, SIGNAL(activeChanged(sel_id_t)),
	        sorter, SLOT(relayout()));

	QPushButton* btn_playsamples = new QPushButton(tr("Play samples"));
	connect(btn_playsamples, SIGNAL(clicked()), this, SLOT(playSamples()));

	sb_playsamples = new QScrollBar(Qt::Horizontal);
	sb_playsamples->setRange(100, 4000); // ms


	lh->addWidget(canvaswidget);
	lv->addLayout(lh, 100);
	lv->addWidget(sorter, 15);
	lv->addWidget(btn_playsamples);
	lv->addWidget(sb_playsamples);


	QDockWidget* dockWidget = new QDockWidget(tr("Process"), this);
	dockWidget->setAllowedAreas(Qt::RightDockWidgetArea);
	dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);

	dockWidget->setWidget(new QWidget());
	dockWidget->widget()->setLayout(new QVBoxLayout());

	tabs = new QTabWidget(this);
	tabs->setMinimumWidth(350);
	tabs->addTab(createFilesTab(), tr("Files"));
	generateTabId = tabs->addTab(createGenerateTab(), tr("Generate"));
	tabs->addTab(createEditTab(), tr("Edit"));
	//tabs->addTab(createExportTab(), tr("Export"));
	connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
	tabChanged(tabs->currentIndex());

	dockWidget->widget()->layout()->addWidget(tabs);

	VolumeFader* vol = new VolumeFader();
	connect(vol, SIGNAL(volumeChangedDb(double)),
	        &player, SLOT(setGainDB(double)));
	connect(&player, SIGNAL(peakUpdate(double)),
	        vol, SLOT(updatePeakPower(double)));

	vol->setVolumeDb(0);
	dockWidget->widget()->layout()->addWidget(vol);

	addDockWidget(Qt::RightDockWidgetArea, dockWidget);

	canvaswidget->yscale->setValue(0.9);
	canvaswidget->yoffset->setValue(MAXVAL/2);
	canvaswidget->xscale->setValue(0);
	canvaswidget->xoffset->setValue(0);

	// Update selections according to threshold
	tool_selections->thresholdChanged(instrument.getThreshold());
}

InstrumentWidget::~InstrumentWidget()
{
}

void InstrumentWidget::tabChanged(int tabid)
{
	tool_selections->setShowPreview(tabid == generateTabId);
	sorter->setShowPreview(tabid == generateTabId);
	tool_selections->autoCreateSelectionsPreview();
	threshold->setActive(tabid == generateTabId);
}

QWidget* InstrumentWidget::createFilesTab()
{
	QWidget* w = new QWidget();
	QVBoxLayout* l = new QVBoxLayout();
	w->setLayout(l);

	l->addWidget(new QLabel(tr("Files: (double-click to set as master)")));
	QPushButton* loadbtn = new QPushButton();
	loadbtn->setText(tr("Add files..."));
	l->addWidget(loadbtn);

	filelist = new FileList(instrument);
	connect(filelist, SIGNAL(masterFileChanged(QString)),
	        this, SLOT(loadFile(QString)));
	connect(loadbtn, SIGNAL(clicked()), filelist, SLOT(addFiles()));
	l->addWidget(filelist);

	if(!instrument.getMasterFile().isEmpty())
	{
		loadFile(instrument.getMasterFile());
	}

	return w;
}

QWidget* InstrumentWidget::createEditTab()
{
	selections = instrument.getSelections();
	SelectionEditor* se = new SelectionEditor(selections, instrument);

	connect(&selections, SIGNAL(added(sel_id_t)), se, SLOT(added(sel_id_t)));
	connect(&selections, SIGNAL(updated(sel_id_t)), se, SLOT(updated(sel_id_t)));
	connect(&selections, SIGNAL(removed(sel_id_t)), se, SLOT(removed(sel_id_t)));
	connect(&selections, SIGNAL(activeChanged(sel_id_t)),
	        se, SLOT(activeChanged(sel_id_t)));

	connect(&selections, SIGNAL(added(sel_id_t)), this, SLOT(selectionChanged()));
	connect(&selections, SIGNAL(updated(sel_id_t)), this, SLOT(selectionChanged()));
	connect(&selections, SIGNAL(removed(sel_id_t)), this, SLOT(selectionChanged()));
	connect(&selections, SIGNAL(activeChanged(sel_id_t)), this, SLOT(selectionChanged()));

	return se;
}

void InstrumentWidget::selectionChanged()
{
	instrument.setSelections(selections);
}

static QSlider* createAttribute(QWidget* parent, QString name,
                                int range_from, int range_to)
{
	QSlider* slider;

	QGridLayout* l = new QGridLayout();

	l->addWidget(new QLabel(name), 0, 0, 1, 2);

	QSpinBox* spin = new QSpinBox();
	spin->setRange(range_from, range_to);
	l->addWidget(spin, 1, 0, 1, 1);

	slider = new QSlider(Qt::Horizontal);
	slider->setRange(range_from, range_to);
	l->addWidget(slider, 1, 1, 1,1);

	QObject::connect(spin, SIGNAL(valueChanged(int)),
	                 slider, SLOT(setValue(int)));
	QObject::connect(slider, SIGNAL(valueChanged(int)),
	                 spin, SLOT(setValue(int)));

	((QBoxLayout*)parent->layout())->addLayout(l);

	return slider;
}

QWidget* InstrumentWidget::createGenerateTab()
{
	QWidget* w = new QWidget();
	QVBoxLayout* l = new QVBoxLayout();
	w->setLayout(l);

	QHBoxLayout* btns = new QHBoxLayout();

	QPushButton* autosel = new QPushButton();
	autosel->setText(tr("Generate"));
	connect(autosel, SIGNAL(clicked()),
	        tool_selections, SLOT(clearSelections()));
	connect(autosel, SIGNAL(clicked()),
	        tool_selections, SLOT(autoCreateSelections()));

	connect(threshold, SIGNAL(thresholdChanged(double)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));
	connect(threshold, SIGNAL(thresholdChanging(double)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));

	QPushButton* clearsel = new QPushButton();
	clearsel->setText(tr("Clear"));
	connect(clearsel, SIGNAL(clicked()),
	        tool_selections, SLOT(clearSelections()));

	btns->addWidget(autosel);
	btns->addWidget(clearsel);

	l->addLayout(btns);

	slider_attacklength = createAttribute(w, tr("Attack length:"), 10, 1000);
	connect(slider_attacklength, SIGNAL(valueChanged(int)),
	        sorter, SLOT(setAttackLength(int)));
	connect(slider_attacklength, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));
	slider_attacklength->setValue(instrument.getAttackLength());
	connect(slider_attacklength, SIGNAL(valueChanged(int)),
	        this, SLOT(generateSlidersChanged()));

	slider_spread = createAttribute(w, tr("Power spread:"), 1, 2000);
	connect(slider_spread, SIGNAL(valueChanged(int)),
	        sorter, SLOT(setSpreadFactor(int)));
	connect(slider_spread, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));
	slider_spread->setValue(instrument.getPowerSpread());
	connect(slider_spread, SIGNAL(valueChanged(int)),
	        this, SLOT(generateSlidersChanged()));

	slider_hold = createAttribute(w, tr("Minimum size (samples):"), 0, 200000);
	connect(slider_hold, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(holdChanged(int)));
	connect(slider_hold, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));
	slider_hold->setValue(instrument.getMinimumSize());
	connect(slider_hold, SIGNAL(valueChanged(int)),
	        this, SLOT(generateSlidersChanged()));

	slider_falloff = createAttribute(w, tr("Falloff:"), 10, 5000);
	connect(slider_falloff, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(noiseFloorChanged(int)));
	connect(slider_falloff, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));
	slider_falloff->setValue(instrument.getFalloff());
	connect(slider_falloff, SIGNAL(valueChanged(int)),
	        this, SLOT(generateSlidersChanged()));

	slider_fadelength = createAttribute(w, tr("Fadelength:"), 0, 2000);
	connect(slider_fadelength, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(fadeoutChanged(int)));
	connect(slider_fadelength, SIGNAL(valueChanged(int)),
	        tool_selections, SLOT(autoCreateSelectionsPreview()));
	slider_fadelength->setValue(instrument.getFadeLength());
	connect(slider_fadelength, SIGNAL(valueChanged(int)),
	        this, SLOT(generateSlidersChanged()));

	l->addStretch();

	return w;
}

void InstrumentWidget::generateSlidersChanged()
{
	Project::RAIIBulkUpdate bulkUpdate(instrument.getProject());
	instrument.setAttackLength(slider_attacklength->value());
	instrument.setPowerSpread(slider_spread->value());
	instrument.setMinimumSize(slider_hold->value());
	instrument.setFalloff(slider_falloff->value());
	instrument.setFadeLength(slider_fadelength->value());
}

QWidget* InstrumentWidget::createExportTab()
{
	QWidget* w = new QWidget();
	QVBoxLayout* l = new QVBoxLayout();
	w->setLayout(l);

	l->addWidget(new QLabel(tr("Prefix:")));
	prefix = new QLineEdit();
	prefix->setText(instrument.getPrefix());
	connect(prefix, SIGNAL(textChanged(const QString &)),
	        this, SLOT(prefixChanged()));
	l->addWidget(prefix);

	l->addWidget(new QLabel(tr("Export path:")));
	QHBoxLayout* lo_exportp = new QHBoxLayout();
	lineed_exportp = new QLineEdit();
	lineed_exportp->setText(instrument.getProject().getExportPath());
	connect(lineed_exportp, SIGNAL(textChanged(const QString &)),
	        this, SLOT(exportPathChanged()));
	lo_exportp->addWidget(lineed_exportp);
	QPushButton* btn_browse = new QPushButton(tr("..."));
	connect(btn_browse, SIGNAL(clicked()), this, SLOT(browse()));
	lo_exportp->addWidget(btn_browse);

	l->addLayout(lo_exportp);

	QPushButton* exportsel = new QPushButton();
	exportsel->setText(tr("Export"));
	connect(exportsel, SIGNAL(clicked()), this, SLOT(doExport()));
	l->addWidget(exportsel);

	QProgressBar* bar = new QProgressBar();
	connect(extractor, SIGNAL(progressUpdate(int)), bar, SLOT(setValue(int)));
	connect(extractor, SIGNAL(setMaximumProgress(int)),
	        bar, SLOT(setMaximum(int)));
	l->addWidget(bar);

	l->addStretch();

	return w;
}

void InstrumentWidget::prefixChanged()
{
	instrument.setPrefix(prefix->text());
}

void InstrumentWidget::exportPathChanged()
{
	instrument.getProject().setExportPath(lineed_exportp->text());
}

void InstrumentWidget::playSamples()
{
	Selections* sels = &selections;
	if(tabs->currentIndex() == generateTabId)
	{
		sels = &selections_preview;
	}

	QVector<sel_id_t> ids = sels->ids();
	for(int v1 = 0; v1 < ids.size(); v1++)
	{
		for(int v2 = 0; v2 < ids.size(); v2++)
		{
			Selection sel1 = sels->get(ids[v1]);
			Selection sel2 = sels->get(ids[v2]);

			if(sel1.energy < sel2.energy)
			{
				sel_id_t vtmp = ids[v1];
				ids[v1] = ids[v2];
				ids[v2] = vtmp;
			}
		}
	}

	QVector<sel_id_t>::iterator i = ids.begin();
	while(i != ids.end())
	{
		Selection sel = sels->get(*i);

		unsigned int length = sb_playsamples->value() * 44100 / 1000;

		unsigned int sample_length = sel.to - sel.from;

		unsigned int to = sel.to;

		if(sample_length > length)
		{
			to = sel.from + length;
		}

		sels->setActive(*i);

		connect(&player, SIGNAL(positionUpdate(size_t)),
		        listen, SLOT(update(size_t)));

		player.playSelection(sel, to - sel.from);
		QTime t;
		t.start();
		while(t.elapsed() < sb_playsamples->value())
		{
			qApp->processEvents();
			q_usleep(25 * 1000);
		}
		player.stop();

		disconnect(&player, SIGNAL(positionUpdate(size_t)),
		           listen, SLOT(update(size_t)));
		i++;
	}
}

void InstrumentWidget::doExport()
{
	extractor->exportSelections();
}

void InstrumentWidget::loadFile(QString filename)
{
	setCursor(Qt::WaitCursor);
	statusBar()->showMessage(tr("Loading..."));
	qApp->processEvents();

	sorter->setWavData(NULL, 0);
	player.setPcmData(NULL, 0, 0);

	canvaswidget->canvas->load(filename);

	sorter->setWavData(canvaswidget->canvas->data, canvaswidget->canvas->size);
	player.setPcmData(canvaswidget->canvas->data, canvaswidget->canvas->size, canvaswidget->canvas->samplerate);

	statusBar()->showMessage(tr("Ready"));
	setCursor(Qt::ArrowCursor);

	instrument.setMasterFile(filename);
}

void InstrumentWidget::browse()
{
	QString path =
		QFileDialog::getExistingDirectory(this, tr("Select export path"),
		                                  lineed_exportp->text());
	lineed_exportp->setText(path);
}
