/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            projectserialiser.cc
 *
 *  Sat May 12 10:11:22 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 "projectserialiser.h"

#include <vector>
#include <iostream>

#include <QDomDocument>
#include <QHash>

/*
<?xml version="1.0" encoding="UTF-8"?>
<dgedit version="1.0">
	<project_name>MyProject</project_name>
	<raw_file_root>/some/path</raw_file_root>
</dgedit>
*/

class DomHelper
{
public:
	DomHelper(const QDomDocument& doc) : elem(doc.documentElement()) {}
	DomHelper(const QDomElement& elem) : elem(elem) {}

	DomHelper operator()(const QString& name)
	{
		auto node = elem.namedItem(name);
		if(!node.isElement())
		{
			std::cout << "No such child-element: '" << name.toStdString() << "'\n";
			QDomElement e;
			return DomHelper(e);
		}

		return DomHelper(node.toElement());
	}

	QString operator[](const QString& name)
	{
		auto attrs = elem.attributes();
		auto node = attrs.namedItem(name);
		if(!node.isAttr())
		{
			std::cout << "No such attribute: '" << name.toStdString() << "'\n";
			return "";
		}
		return node.toAttr().value();
	}

	QString text()
	{
		return elem.text();
	}

	// Get child nodes with tag_name or all child nodes if tag_name == "".
	std::vector<DomHelper> children(const QString& tag_name = "")
	{
		std::vector<DomHelper> children;
		auto child_nodes = elem.childNodes();
		for(int i = 0; i < child_nodes.count(); ++i)
		{
			auto node = child_nodes.at(i);
			if(!node.isElement())
			{
				continue;
			}

			auto elem = node.toElement();
			if(elem.tagName() == tag_name || tag_name == "")
			{
				children.emplace_back(DomHelper(elem));
			}
		}

		return children;
	}

private:
	QDomElement elem;
};

QString ProjectSerialiser::serialise(const Project& project)
{
	// Ugly hack to ensure the xml attribute order is the same each time a save
	// or export is performed.
	qSetGlobalQHashSeed(0);

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

	auto dgedit = doc.createElement("dgedit");
	doc.appendChild(dgedit);

	dgedit.setAttribute("version", "1.0");

	auto project_name = doc.createElement("project_name");
	project_name.appendChild(doc.createTextNode(project.project_name));
	dgedit.appendChild(project_name);

	auto raw_file_root = doc.createElement("raw_file_root");
	raw_file_root.appendChild(doc.createTextNode(project.raw_file_root));
	dgedit.appendChild(raw_file_root);

	auto description = doc.createElement("description");
	description.appendChild(doc.createTextNode(project.description));
	dgedit.appendChild(description);

	auto export_path = doc.createElement("export_path");
	export_path.appendChild(doc.createTextNode(project.export_path));
	dgedit.appendChild(export_path);

	dgedit.setAttribute("next_id", project.next_id);
	dgedit.setAttribute("samplerate", project.samplerate);

	auto channels = doc.createElement("channels");
	dgedit.appendChild(channels);

	for(const auto& c : project.channels)
	{
		auto channel = doc.createElement("channel");
		channel.setAttribute("id", c.getId());
		channel.setAttribute("name", c.getChannelName());
		channels.appendChild(channel);
	}

	auto instruments = doc.createElement("instruments");
	dgedit.appendChild(instruments);

	for(const auto& i : project.instruments)
	{
		auto instrument = doc.createElement("instrument");

		instrument.setAttribute("id", (int)i.getId());

		instrument.setAttribute("attack_length", (int)i.getAttackLength());
		instrument.setAttribute("power_spread", (int)i.getPowerSpread());
		instrument.setAttribute("minimum_size", (int)i.getMinimumSize());
		instrument.setAttribute("falloff", (int)i.getFalloff());
		instrument.setAttribute("fade_length", (int)i.getFadeLength());

		instrument.setAttribute("threshold", i.getThreshold());

		auto prefix = doc.createElement("prefix");
		prefix.appendChild(doc.createTextNode(i.getPrefix()));
		instrument.appendChild(prefix);

		instruments.appendChild(instrument);

		auto instrument_name = doc.createElement("instrument_name");
		instrument_name.appendChild(doc.createTextNode(i.instrument_name));
		instrument.appendChild(instrument_name);

		auto file_list = doc.createElement("file_list");
		instrument.appendChild(file_list);

		for(const auto& audiofile : i.audio_files)
		{
			auto file = doc.createElement("file");
			file.appendChild(doc.createTextNode(audiofile.getFile()));
			file.setAttribute("id", audiofile.getId());
			file.setAttribute("name", audiofile.getName());
			file.setAttribute("channel_map_id", audiofile.getChannelMapId());
			file.setAttribute("master", i.master_file == audiofile.getAbsoluteFile());
			file.setAttribute("main", audiofile.getMainChannel() ? "true" : "false");
			file_list.appendChild(file);
		}

		auto regions = doc.createElement("regions");
		regions.setAttribute("nextid", i.selections.nextid);
		regions.setAttribute("act", i.selections.act);
		instrument.appendChild(regions);

		for(auto r = i.selections.sels.begin(); r != i.selections.sels.end(); ++r)
		{
			auto region = doc.createElement("region");
			region.setAttribute("id", (int)r.key());
			region.setAttribute("from", (int)r.value().from);
			region.setAttribute("to", (int)r.value().to);
			region.setAttribute("fadein", (int)r.value().fadein);
			region.setAttribute("fadeout", (int)r.value().fadeout);
			region.setAttribute("energy", r.value().energy);
			region.setAttribute("name", r.value().name);
			regions.appendChild(region);
		}
	}

	return doc.toString();
}

bool ProjectSerialiser::deserialise(const QString& data, Project& project)
{
	// Ugly hack to ensure the xml attribute order is the same each time a save
	// or export is performed.
	qSetGlobalQHashSeed(0);

	QDomDocument doc;
	if(!doc.setContent(data))
	{
		std::cout << "Could not parse XML\n";
		return false;
	}

	project.reset();
	DomHelper dom(doc);

	QString version = dom["version"];
	if(version != "1.0")
	{
		std::cout << "Bad version: '" << version.toStdString() << "'\n";
		return false;
	}

	project.next_id = dom["next_id"].toInt();
	project.samplerate = dom["samplerate"].toDouble();
	project.project_name = dom("project_name").text();
	project.raw_file_root = dom("raw_file_root").text();
	project.description = dom("description").text();
	project.export_path = dom("export_path").text();

	auto channels = dom("channels").children("channel");
	for(auto& channel : channels)
	{
		auto id = channel["id"].toInt();
		project.channels.emplace_back(Channel(project, id));

		auto& ch = project.channels.back();
		ch.channel_name = channel["name"];
	}

	auto instruments = dom("instruments").children("instrument");
	for(auto& instrument : instruments)
	{
		auto id = instrument["id"].toInt();
		project.instruments.emplace_back(Instrument(project, id));

		auto& instr = project.instruments.back();
		instr.instrument_name = instrument("instrument_name").text();

		QString master_file;
		auto files = instrument("file_list").children("file");
		for(auto& file : files)
		{
			auto id = file["id"].toInt();
			instr.audio_files.emplace_back(AudioFile(instr, id));
			auto& audiofile = instr.audio_files.back();
			audiofile.file = file.text();
			audiofile.name = file["name"];
			audiofile.channel_map_id = file["channel_map_id"].toInt();
			audiofile.main_channel = file["main"] == "true";

			if(file["master"] == "1")
			{
				master_file = audiofile.getAbsoluteFile();
			}
		}

		instr.master_file = master_file;

		instr.attack_length = instrument["attack_length"].toInt();
		instr.power_spread = instrument["power_spread"].toInt();;
		instr.minimum_size = instrument["minimum_size"].toInt();
		instr.falloff = instrument["falloff"].toInt();
		instr.fade_length = instrument["fade_length"].toInt();

		instr.threshold = instrument["threshold"].toFloat();

		instr.prefix = instrument("prefix").text();

		auto selections = instrument("regions");
		instr.selections.nextid = selections["nextid"].toInt();
		instr.selections.act = selections["act"].toInt();

		for(auto& selection : selections.children())
		{
			Selection s;
			s.from = selection["from"].toInt();
			s.to = selection["to"].toInt();
			s.fadein = selection["fadein"].toInt();
			s.fadeout = selection["fadeout"].toInt();
			s.energy = selection["energy"].toFloat();
			s.name = selection["name"];
			instr.selections.sels[selection["id"].toInt()] = s;
		}
	}

	return true;
}
