/*
  This file is part of Ingen.
  Copyright 2007-2012 David Robillard <http://drobilla.net/>

  Ingen is free software: you can redistribute it and/or modify it under the
  terms of the GNU Affero General Public License as published by the Free
  Software Foundation, either version 3 of the License, or 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 Affero General Public License for details.

  You should have received a copy of the GNU Affero General Public License
  along with Ingen.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <assert.h>
#include <stdio.h>
#include <string.h>

#include "raul/log.hpp"
#include "raul/AtomLiblo.hpp"
#include "raul/Path.hpp"

#include "ingen_config.h"
#include "OSCClientReceiver.hpp"

#define LOG(s) s << "[OSCClientReceiver] "

using namespace std;
using namespace Raul;

namespace Ingen {
namespace Client {

OSCClientReceiver::OSCClientReceiver(int                        listen_port,
                                     SharedPtr<ClientInterface> target)
	: _target(target)
	, _st(NULL)
	, _listen_port(listen_port)
{
#ifdef RAUL_LOG_DEBUG
	start(true);
#else
	start(false); // true = dump, false = shutup
#endif
}

OSCClientReceiver::~OSCClientReceiver()
{
	stop();
}

void
OSCClientReceiver::start(bool dump_osc)
{
	if (_st != NULL)
		return;

	// Attempt preferred port
	if (_listen_port != 0) {
		char port_str[8];
		snprintf(port_str, 8, "%d", _listen_port);
		_st = lo_server_thread_new(port_str, lo_error_cb);
	}

	// Find a free port
	if (!_st) {
		_st = lo_server_thread_new(NULL, lo_error_cb);
		_listen_port = lo_server_thread_get_port(_st);
	}

	if (_st == NULL) {
		LOG(error) << "Could not start OSC listener.  Aborting." << endl;
		exit(EXIT_FAILURE);
	} else {
		LOG(info) << "Started OSC listener on port " << lo_server_thread_get_port(_st) << endl;
	}

	// Print all incoming messages
	if (dump_osc)
		lo_server_thread_add_method(_st, NULL, NULL, generic_cb, NULL);

	setup_callbacks();

	// Display any uncaught messages to the console
	//lo_server_thread_add_method(_st, NULL, NULL, unknown_cb, NULL);

	lo_server_thread_start(_st);
}

void
OSCClientReceiver::stop()
{
	if (_st != NULL) {
		//unregister_client();
		lo_server_thread_free(_st);
		_st = NULL;
	}
}

int
OSCClientReceiver::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data)
{
	printf("[OSCClientReceiver] %s (%s)\t", path, types);

	for (int i=0; i < argc; ++i) {
		lo_arg_pp(lo_type(types[i]), argv[i]);
		printf("\t");
	}
	printf("\n");

	return 1;  // not handled
}

void
OSCClientReceiver::lo_error_cb(int num, const char* msg, const char* path)
{
	LOG(error) << "Got error from server: " << msg << endl;
}

int
OSCClientReceiver::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data)
{
	std::string msg = "Received unknown OSC message: ";
	msg += path;

	LOG(error) << msg << endl;

	return 0;
}

void
OSCClientReceiver::setup_callbacks()
{
	if (!_target)
		return;

	lo_server_thread_add_method(_st, "/response", "ii", response_cb, this);
	lo_server_thread_add_method(_st, "/plugin", "sss", plugin_cb, this);
	lo_server_thread_add_method(_st, "/put", NULL, put_cb, this);
	lo_server_thread_add_method(_st, "/delta_begin", NULL, delta_begin_cb, this);
	lo_server_thread_add_method(_st, "/delta_remove", NULL, delta_remove_cb, this);
	lo_server_thread_add_method(_st, "/delta_add", NULL, delta_add_cb, this);
	lo_server_thread_add_method(_st, "/delta_end", NULL, delta_end_cb, this);
	lo_server_thread_add_method(_st, "/move", "ss", move_cb, this);
	lo_server_thread_add_method(_st, "/delete", "s", del_cb, this);
	lo_server_thread_add_method(_st, "/connect", "ss", connection_cb, this);
	lo_server_thread_add_method(_st, "/disconnect", "ss", disconnection_cb, this);
	lo_server_thread_add_method(_st, "/set_property", NULL, set_property_cb, this);
}

/** Catches errors that aren't a direct result of a client request.
 */
int
OSCClientReceiver::_error_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	_target->error((char*)argv[0]);
	return 0;
}

int
OSCClientReceiver::_del_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	_target->del((const char*)&argv[0]->s);
	return 0;
}

int
OSCClientReceiver::_put_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* obj_path = &argv[0]->s;
	const char* ctx      = &argv[1]->s;
	Resource::Properties prop;
	for (int i = 2; i < argc-1; i += 2)
		prop.insert(make_pair(&argv[i]->s,
		                      AtomLiblo::lo_arg_to_atom(types[i+1], argv[i+1])));
	_target->put(obj_path, prop, Resource::uri_to_graph(ctx));
	return 0;
}

int
OSCClientReceiver::_delta_begin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* obj_path = &argv[0]->s;
	assert(_delta_remove.empty());
	assert(_delta_add.empty());
	_delta_uri = obj_path;
	return 0;
}

int
OSCClientReceiver::_delta_remove_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	_delta_remove.insert(make_pair(&argv[0]->s,
	                               AtomLiblo::lo_arg_to_atom(types[1], argv[1])));
	return 0;
}

int
OSCClientReceiver::_delta_add_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	_delta_add.insert(make_pair(&argv[0]->s,
	                            AtomLiblo::lo_arg_to_atom(types[1], argv[1])));
	return 0;
}

int
OSCClientReceiver::_delta_end_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	_target->delta(_delta_uri, _delta_remove, _delta_add);
	_delta_uri = Raul::URI();
	_delta_remove.clear();
	_delta_add.clear();
	return 0;
}

int
OSCClientReceiver::_move_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* old_path = &argv[1]->s;
	const char* new_path = &argv[2]->s;

	_target->move(old_path, new_path);
	return 0;
}

int
OSCClientReceiver::_connection_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* const src_port_path = &argv[0]->s;
	const char* const dst_port_path = &argv[1]->s;

	_target->connect(src_port_path, dst_port_path);

	return 0;
}

int
OSCClientReceiver::_disconnection_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* src_uri = &argv[0]->s;
	const char* dst_uri = &argv[1]->s;

	_target->disconnect(src_uri, dst_uri);

	return 0;
}

/** Notification of a new or updated property.
 */
int
OSCClientReceiver::_set_property_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	if (argc != 3 || types[0] != 's' || types[1] != 's')
		return 1;

	const char* obj_uri  = &argv[0]->s;
	const char* key      = &argv[1]->s;

	Atom value = AtomLiblo::lo_arg_to_atom(types[2], argv[2]);

	_target->set_property(obj_uri, key, value);

	return 0;
}

int
OSCClientReceiver::_response_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	assert(!strcmp(types, "ii"));
	_target->response(argv[0]->i, (Status)argv[1]->i);
	return 0;
}

} // namespace Client
} // namespace Ingen