/* This file is part of Ingen.
 * Copyright 2007-2011 David Robillard <http://drobilla.net>
 *
 * Ingen 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.
 *
 * Ingen 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 <cassert>
#include "raul/log.hpp"
#include "flowcanvas/Module.hpp"
#include "ingen/ServerInterface.hpp"
#include "shared/LV2URIMap.hpp"
#include "ingen/client/PatchModel.hpp"
#include "ingen/client/PortModel.hpp"
#include "App.hpp"
#include "Configuration.hpp"
#include "WidgetFactory.hpp"
#include "PatchWindow.hpp"
#include "Port.hpp"
#include "PortMenu.hpp"
#include "WindowFactory.hpp"

using namespace Ingen::Client;
using namespace std;
using namespace Raul;

namespace Ingen {
using namespace Shared;
namespace GUI {

ArtVpathDash* Port::_dash;

Port*
Port::create(FlowCanvas::Module&        module,
             SharedPtr<const PortModel> pm,
             bool                       human_name,
             bool                       flip)
{
	Glib::ustring label(human_name ? "" : pm->path().symbol());
	if (human_name) {
		const Raul::Atom& name = pm->get_property(App::instance().uris().lv2_name);
		if (name.type() == Raul::Atom::STRING) {
			label = name.get_string();
		} else {
			const SharedPtr<const NodeModel> parent(PtrCast<const NodeModel>(pm->parent()));
			if (parent && parent->plugin_model())
				label = parent->plugin_model()->port_human_name(pm->index());
		}
	}
	return new Port(module, pm, label, flip);
}

/** @a flip Make an input port appear as an output port, and vice versa.
 */
Port::Port(FlowCanvas::Module&        module,
           SharedPtr<const PortModel> pm,
           const string&              name,
           bool                       flip)
	: FlowCanvas::Port(module, name,
			flip ? (!pm->is_input()) : pm->is_input(),
			App::instance().configuration()->get_port_color(pm.get()))
	, _port_model(pm)
	, _pressed(false)
	, _flipped(flip)
{
	assert(pm);

	ArtVpathDash* dash = this->dash();
	_rect.property_dash() = dash;
	set_border_width(dash ? 2.0 : 0.0);

	pm->signal_moved().connect(sigc::mem_fun(this, &Port::moved));

	if (App::instance().can_control(pm.get())) {
		set_toggled(pm->is_toggle());
		show_control();
		pm->signal_property().connect(
			sigc::mem_fun(this, &Port::property_changed));
		pm->signal_value_changed().connect(
			sigc::mem_fun(this, &Port::value_changed));
	}

	pm->signal_activity().connect(sigc::mem_fun(this, &Port::activity));

	update_metadata();

	value_changed(pm->value());
}

Port::~Port()
{
	App::instance().activity_port_destroyed(this);
}

void
Port::update_metadata()
{
	SharedPtr<const PortModel> pm = _port_model.lock();
	if (App::instance().can_control(pm.get()) && pm->is_numeric()) {
		boost::shared_ptr<const NodeModel> parent = PtrCast<const NodeModel>(pm->parent());
		if (parent) {
			float min = 0.0f;
			float max = 1.0f;
			parent->port_value_range(pm, min, max, App::instance().sample_rate());
			set_control_min(min);
			set_control_max(max);
		}
	}
}

bool
Port::show_menu(GdkEventButton* ev)
{
	PortMenu* menu = NULL;
	WidgetFactory::get_widget_derived("object_menu", menu);
	menu->init(model(), _flipped);
	menu->popup(ev->button, ev->time);
	return true;
}

void
Port::moved()
{
	if (App::instance().configuration()->name_style() == Configuration::PATH)
		set_name(model()->symbol().c_str());
}

void
Port::value_changed(const Atom& value)
{
	if (_pressed)
		return;
	else if (value.type() == Atom::FLOAT)
		FlowCanvas::Port::set_control(value.get_float());
}

bool
Port::on_event(GdkEvent* ev)
{
	switch (ev->type) {
	case GDK_BUTTON_PRESS:
		if (ev->button.button == 1)
			_pressed = true;
		break;
	case GDK_BUTTON_RELEASE:
		if (ev->button.button == 1)
			_pressed = false;
	default:
		break;
	}

	return false;
}

void
Port::activity()
{
	App::instance().port_activity(this);
}

void
Port::set_control(float value, bool signal)
{
	if (signal) {
		App&                        app   = App::instance();
		Ingen::Shared::World* const world = app.world();
		app.engine()->set_property(model()->path(),
				world->uris()->ingen_value, Atom(value));
		PatchWindow* pw = app.window_factory()->patch_window(
				PtrCast<const PatchModel>(model()->parent()));
		if (!pw)
			pw = app.window_factory()->patch_window(
					PtrCast<const PatchModel>(model()->parent()->parent()));
		if (pw)
			pw->show_port_status(model().get(), value);
	}

	FlowCanvas::Port::set_control(value);
}

void
Port::property_changed(const URI& key, const Atom& value)
{
	const LV2URIMap& uris = App::instance().uris();
	if (value.type() == Atom::FLOAT) {
		float val = value.get_float();
		if (key == uris.ingen_value && !_pressed) {
			set_control(val, false);
		} else if (key == uris.lv2_minimum) {
			if (model()->port_property(uris.lv2_sampleRate)) {
				val *= App::instance().sample_rate();
			}
			set_control_min(val);
		} else if (key == uris.lv2_maximum) {
			if (model()->port_property(uris.lv2_sampleRate)) {
				val *= App::instance().sample_rate();
			}
			set_control_max(val);
		}
	} else if (key == uris.lv2_portProperty) {
		if (value == uris.lv2_toggled)
			set_toggled(true);
	} else if (key == uris.ctx_context) {
		ArtVpathDash* dash = this->dash();
		_rect.property_dash() = dash;
		set_border_width(dash ? 2.0 : 0.0);
	} else if (key == uris.lv2_name) {
		if (value.type() == Atom::STRING
				&& App::instance().configuration()->name_style() == Configuration::HUMAN) {
			set_name(value.get_string());
		}
	}
}

ArtVpathDash*
Port::dash()
{
	const LV2URIMap& uris = App::instance().uris();
	SharedPtr<const PortModel> pm = _port_model.lock();
	if (!pm)
		return NULL;

	if (pm->has_context(uris.ctx_AudioContext))
		return NULL;

	if (!_dash) {
		_dash = new ArtVpathDash();
		_dash->n_dash = 2;
		_dash->dash = art_new(double, 2);
		_dash->dash[0] = 4;
		_dash->dash[1] = 4;
	}

	return _dash;
}

void
Port::set_selected(bool b)
{
	if (b != selected()) {
		FlowCanvas::Port::set_selected(b);
		SharedPtr<const PortModel> pm = _port_model.lock();
		if (pm && b) {
			const App&                 app  = App::instance();
			SharedPtr<const NodeModel> node = PtrCast<NodeModel>(pm->parent());
			PatchWindow*               win  = app.window_factory()->parent_patch_window(node);
			if (win) {
				const std::string& doc = node->plugin_model()->port_documentation(
					pm->index());
				if (!doc.empty()) {
					win->doc_textview()->get_buffer()->set_text(doc);
					win->doc_textview()->show();
				} else {
					win->doc_textview()->hide();
				}
			}
		}
	}
}

} // namespace GUI
} // namespace Ingen