/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            audioextractor.cc
 *
 *  Sat Nov 21 13:09:35 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 "audioextractor.h"

#include <QDomDocument>
#include <QFile>
#include <QDir>
#include <QApplication>
#include <QHash>

#include <sndfile.h>

#include "project.h"

#define INSTRUMENT_VERSION "2.0"

typedef struct
{
	SNDFILE *fh;
	float *data;
} audiodata_t;

AudioExtractor::AudioExtractor(Instrument& instrument, QObject *parent)
	: QObject(parent)
	, instrument(instrument)
{
}

bool AudioExtractor::exportSelections()
{
	auto selections = instrument.getSelections();
	auto exportpath = instrument.getProject().getExportPath();
	auto prefix = instrument.getPrefix();

	int samplerate = -1;
	emit setMaximumProgress(selections.ids().size() + 1/* for xml writing*/);
	int progress = 0;
	emit progressUpdate(progress++);
	qApp->processEvents();

	// Open all input audio files:
	auto audiofile_ids = instrument.getAudioFileList();
	audiodata_t audiodata[audiofile_ids.size()];

	int idx = 0;
	for(auto audiofile_id : audiofile_ids)
	{
		const auto& audiofile = instrument.getAudioFile(audiofile_id);
		QString file = audiofile.getAbsoluteFile();

		SF_INFO sf_info;
		audiodata[idx].fh = sf_open(file.toStdString().c_str(), SFM_READ, &sf_info);
		if(!audiodata[idx].fh)
		{
			printf("AudioExtractor load error '%s'\n", file.toStdString().c_str());
			return false;
		}

		if(samplerate == -1)
		{
			// Store the first samplerate we meet
			samplerate = sf_info.samplerate;
		}

		audiodata[idx].data = NULL;

		idx++;
	}

	idx = 1;
	QVector<sel_id_t> sels = selections.ids();

	// Sort selections by velocity
	for(int v1 = 0; v1 < sels.size(); v1++)
	{
		for(int v2 = 0; v2 < sels.size(); v2++)
		{
			Selection sel1 = selections.get(sels[v1]);
			Selection sel2 = selections.get(sels[v2]);

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

	if(samplerate == -1)
	{
		// For some reason we never got a samplerate. Set it to 44k1Hz
		samplerate = 44100;
	}

	// Iterate and write audio files
	QVector<sel_id_t>::iterator si = sels.begin();
	while(si != sels.end())
	{
		Selection sel = selections.get(*si);
		size_t offset = sel.from;
		size_t size = sel.to - sel.from;
		size_t fadein = sel.fadein;
		size_t fadeout = sel.fadeout;


		// Read all input audio file chunks:
		for(int i = 0; i < audiofile_ids.size(); i++)
		{
			// Clear out old buffer (if one exists)
			if(audiodata[i].data)
			{
				delete audiodata[i].data;
				audiodata[i].data = NULL;
			}

			SNDFILE *fh = audiodata[i].fh;

			sf_seek(fh, offset, SEEK_SET);

			float *data = new float[size];
			sf_read_float(fh, data, size);

			// Apply linear fadein
			for(size_t fi = 0; fi < fadein; fi++)
			{
				float val = ((float)fi / (float)fadein);
				if(fi < size) data[fi] *= val;
			}

			// Apply fadeout
			for(size_t fo = 0; fo < fadeout; fo++)
			{
				float val = 1.0 - ((float)fo / (float)fadeout);
				if( (((size - fadeout) + fo) < size) &&
				    (((size - fadeout) + fo) >= 0) )
				{
					data[(size - fadeout) + fo] *= val;
				}
			}

			audiodata[i].data = data;
		}

		// Create output path:
		QString path = exportpath + QDir::separator() + prefix + QDir::separator() + "samples";
		QDir d;
		d.mkpath(path);

		// Write all sample chunks to single output file:
		QString file = path + QDir::separator() + QString::number(idx) + "-" + prefix + ".wav";

		SF_INFO sf_info;
		sf_info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
		sf_info.samplerate = samplerate;
		sf_info.channels = audiofile_ids.size();

		SNDFILE *ofh = sf_open(file.toStdString().c_str(), SFM_WRITE, &sf_info);
		if(!ofh)
		{
			printf("Open for write error. %s\n", file.toStdString().c_str());
			return false;
		}

		for(size_t ob = 0; ob < size; ob++)
		{
			float obuf[audiofile_ids.size()];
			for(int ai = 0; ai < audiofile_ids.size(); ai++)
			{
				obuf[ai] = audiodata[ai].data[ob];
			}
			sf_write_float(ofh, obuf, audiofile_ids.size());
		}
		sf_close(ofh);

		idx++;
		si++;

		emit progressUpdate(progress++);
		qApp->processEvents();
	}

	// Close all input audio files:
	for(int i = 0; i < audiofile_ids.size(); i++)
	{
		if(audiodata[i].data)
		{
			delete audiodata[i].data;
			audiodata[i].data = NULL;
		}

		sf_close(audiodata[i].fh);
	}

	// Ugly hack to ensure the xml attribute order is the same each time a save
	// or export is performed.
	qSetGlobalQHashSeed(0);

	QDomDocument doc;
	QDomProcessingInstruction header =
		doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
	doc.appendChild(header);

	QDomElement instrument_node = doc.createElement("instrument");
	instrument_node.setAttribute("version", INSTRUMENT_VERSION);
	instrument_node.setAttribute("name", prefix);
	doc.appendChild(instrument_node);

	QDomElement samples = doc.createElement("samples");
	instrument_node.appendChild(samples);

	{
		// Do the adding to the xml file one sample at the time.
		int index = 0;
		QVector<sel_id_t>::iterator si = sels.begin();
		while(si != sels.end())
		{
			index++;

			Selection i = selections.get(*si);
			i.name = prefix + "-" + QString::number(index);

			QDomElement sample = doc.createElement("sample");
			sample.setAttribute("name", i.name);
			sample.setAttribute("power", QString::number(i.energy));
			samples.appendChild(sample);

			selections.update(*si, i);

			int channelnum = 1; // Filechannel numbers are 1-based.

			for(auto audiofile_id : audiofile_ids)
			{
				const auto& audiofile = instrument.getAudioFile(audiofile_id);

				QString file = audiofile.getFile();
				QString name = audiofile.getName();

				QDomElement audiofile_node = doc.createElement("audiofile");
				audiofile_node.setAttribute("file", "samples/" +
				                            QString::number(index) + "-" + prefix + ".wav");
				audiofile_node.setAttribute("channel", name);
				audiofile_node.setAttribute("filechannel", QString::number(channelnum));
				sample.appendChild(audiofile_node);
				channelnum++;
			}

			si++;
		}
	}

	QFile xmlfile(exportpath + QDir::separator() + prefix + QDir::separator() + prefix + ".xml");
	if(!xmlfile.open(QIODevice::WriteOnly))
	{
		return false;
	}
	xmlfile.write(doc.toByteArray());
	xmlfile.close();

	emit progressUpdate(progress++);
	qApp->processEvents();
	return true;
}
