/* This file is part of Patchage.
 * Copyright 2007-2011 David Robillard <http://drobilla.net>
 *
 * Patchage 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.
 *
 * Patchage 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 details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include <stdlib.h>

#include <stdexcept>
#include <fstream>

#include "raul/log.hpp"

#include "StateManager.hpp"
#include "Patchage.hpp"

using std::endl;
using std::map;
using std::string;

StateManager::StateManager()
	: _window_location(0, 0)
	, _window_size(640, 480)
	, _zoom(1.0)
{
}

bool
StateManager::get_module_location(const string& name, ModuleType type, Coord& loc)
{
	map<string, ModuleSettings>::const_iterator i = _module_settings.find(name);
	if (i == _module_settings.end())
		return false;

	const ModuleSettings& settings = (*i).second;

	switch (type) {
	case Input:
		if (settings.input_location) {
			loc = *settings.input_location;
			return true;
		}
		break;
	case Output:
		if (settings.output_location) {
			loc = *settings.output_location;
			return true;
		}
		break;
	case InputOutput:
		if (settings.inout_location) {
			loc = *settings.inout_location;
			return true;
		}
		break;
	default:
		throw std::runtime_error("Invalid module type");
	}

	return false;
}

void
StateManager::set_module_location(const string& name, ModuleType type, Coord loc)
{
retry:
	map<string, ModuleSettings>::iterator i = _module_settings.find(name);
	if (i == _module_settings.end()) {
		// no mapping exists, insert new element and set its split type, then retry to retrieve reference to it
		_module_settings[name].split = type != InputOutput;
		goto retry;
	}

	ModuleSettings& settings_ref = (*i).second;

	switch (type) {
	case Input:
		settings_ref.input_location = loc;
		break;
	case Output:
		settings_ref.output_location = loc;
		break;
	case InputOutput:
		settings_ref.inout_location = loc;
		break;
	default:
		throw std::runtime_error("Invalid module type");
	}
}

/** Returns whether or not this module should be split.
 *
 * If nothing is known about the given module, @a default_val is returned (this is
 * to allow driver's to request terminal ports get split by default).
 */
bool
StateManager::get_module_split(const string& name, bool default_val) const
{
	map<string, ModuleSettings>::const_iterator i = _module_settings.find(name);
	if (i == _module_settings.end())
		return default_val;

	return (*i).second.split;
}

void
StateManager::set_module_split(const string& name, bool split)
{
	_module_settings[name].split = split;
}

void
StateManager::load(const string& filename)
{
	_module_settings.clear();

	std::ifstream is;
	is.open(filename.c_str(), std::ios::in);

	if ( ! is.good())
		return;

	Raul::info << "Loading configuration file " << filename << endl;

	string s;

	is >> s;
	if (s == "window_location") {
		is >> s;
		_window_location.x = atoi(s.c_str());
		is >> s;
		_window_location.y = atoi(s.c_str());
	}

	is >> s;
	if (s == "window_size") {
		is >> s;
		_window_size.x = atoi(s.c_str());
		is >> s;
		_window_size.y = atoi(s.c_str());
	}

	is >> s;
	if (s != "zoom_level") {
		std::string msg = "Corrupt settings file: expected \"zoom_level\", found \"";
		msg.append(s).append("\"");
		throw std::runtime_error(msg);
	}

	is >> s;
	_zoom = atof(s.c_str());

	Coord loc;
	ModuleType type;
	string name;

	while (1) {
		is >> s;
		if (is.eof()) break;

		// Old versions didn't quote, so need to support both :/
		if (s[0] == '\"') {
			if (s.length() > 1 && s[s.length()-1] == '\"') {
				name = s.substr(1, s.length()-2);
			} else {
				name = s.substr(1);
				is >> s;
				while (s[s.length()-1] != '\"') {
					name.append(" ").append(s);
					is >> s;
				}
				name.append(" ").append(s.substr(0, s.length()-1));
			}
		} else {
			name = s;
		}

		is >> s;
		if (s == "input") type = Input;
		else if (s == "output") type = Output;
		else if (s == "inputoutput") type = InputOutput;
		else throw std::runtime_error("Corrupt settings file.");

		is >> s;
		loc.x = atoi(s.c_str());
		is >> s;
		loc.y = atoi(s.c_str());

		set_module_location(name, type, loc);
	}

	is.close();
}

static inline void
write_module_settings_entry(
	std::ofstream& os,
	const string& name,
	const char * type,
	const Coord& loc)
{
	os << "\"" << name << "\"" << " " << type << " " << loc.x << " " << loc.y << std::endl;
}

void
StateManager::save(const string& filename)
{
	std::ofstream os;
	os.open(filename.c_str(), std::ios::out);

	os << "window_location " << _window_location.x << " " << _window_location.y << std::endl;
	os << "window_size " << _window_size.x << " " << _window_size.y << std::endl;
	os << "zoom_level " << _zoom << std::endl;

	for (map<string, ModuleSettings>::iterator i = _module_settings.begin();
			i != _module_settings.end(); ++i) {
		const ModuleSettings& settings = (*i).second;
		const string& name = (*i).first;

		if (settings.split) {
			if (settings.input_location && settings.output_location) {
				write_module_settings_entry(os, name, "input", *settings.input_location);
				write_module_settings_entry(os, name, "output", *settings.output_location);
			}
		} else {
			if (settings.input_location && settings.inout_location) {
				write_module_settings_entry(os, name, "inputoutput", *settings.inout_location);
			}
		}
	}

	os.close();
}

float
StateManager::get_zoom()
{
	return _zoom;
}

void
StateManager::set_zoom(float zoom)
{
	_zoom = zoom;
}

int
StateManager::get_port_color(PortType type)
{
	// Darkest tango palette colour, with S -= 6, V -= 6, w/ transparency

	if (type == JACK_AUDIO)
		return 0x244678C0;
	else if (type == JACK_MIDI)
		return 0x960909C0;
	else if (type == ALSA_MIDI)
		return 0x4A8A0EC0;
	else
		return 0xFF0000FF;
}