/* This file is part of Ingen.  Copyright (C) 2006 Dave Robillard.
 * 
 * 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 "OSCClientReceiver.h"
#include "raul/AtomLiblo.h"
#include <list>
#include <cassert>
#include <cstring>
#include <iostream>
using std::cerr; using std::cout; using std::endl;

namespace Ingen {
namespace Client {

	
OSCClientReceiver::OSCClientReceiver(int listen_port)
: _listen_port(listen_port),
  _st(NULL)//,
//  _receiving_node(false),
//  _receiving_node_model(NULL),
//  _receiving_node_num_ports(0),
//  _num_received_ports(0)
{
	start();
}


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


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

	if (_listen_port == 0) { 
		_st = lo_server_thread_new(NULL, error_cb);
		_listen_port = lo_server_thread_get_port(_st);
	} else {
		char port_str[8];
		snprintf(port_str, 8, "%d", _listen_port);
		_st = lo_server_thread_new(port_str, error_cb);
	}

	if (_st == NULL) {
		cerr << "[OSCClientReceiver] Could not start OSC listener.  Aborting." << endl;
		exit(EXIT_FAILURE);
	} else {
		cout << "[OSCClientReceiver] Started OSC listener on port " << lo_server_thread_get_port(_st) << endl;
	}

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

	//lo_server_thread_add_method(_st, "/om/response/ok", "i", om_response_ok_cb, this);
	//lo_server_thread_add_method(_st, "/om/response/error", "is", om_responseerror_cb, this);
	
	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("[OSCMsg] %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");
    
	/*for (int i=0; i < argc; ++i) {
		printf("         '%c'  ", types[i]);
		lo_arg_pp(lo_type(types[i]), argv[i]);
		printf("\n");
    }
    printf("\n");*/

	return 1;  // not handled
}


void
OSCClientReceiver::error_cb(int num, const char* msg, const char* path)
{
	cerr << "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)
{
	string msg = "Received unknown OSC message: ";
	msg += path;

	cerr << msg << endl;

	return 0;
}


void
OSCClientReceiver::setup_callbacks()
{
	lo_server_thread_add_method(_st, "/om/response", "iis", response_cb, this);
	lo_server_thread_add_method(_st, "/om/num_plugins", "i", num_plugins_cb, this);
	lo_server_thread_add_method(_st, "/om/plugin", "ss", plugin_cb, this);
	lo_server_thread_add_method(_st, "/om/new_patch", "si", new_patch_cb, this);
	lo_server_thread_add_method(_st, "/om/destroyed", "s", destroyed_cb, this);
	lo_server_thread_add_method(_st, "/om/patch_enabled", "s", patch_enabled_cb, this);
	lo_server_thread_add_method(_st, "/om/patch_disabled", "s", patch_disabled_cb, this);
	lo_server_thread_add_method(_st, "/om/patch_cleared", "s", patch_cleared_cb, this);
	lo_server_thread_add_method(_st, "/om/object_renamed", "ss", object_renamed_cb, this);
	lo_server_thread_add_method(_st, "/om/new_connection", "ss", connection_cb, this);
	lo_server_thread_add_method(_st, "/om/disconnection", "ss", disconnection_cb, this);
	lo_server_thread_add_method(_st, "/om/new_node", "ssii", new_node_cb, this);
	lo_server_thread_add_method(_st, "/om/new_port", "ssi", new_port_cb, this);
	lo_server_thread_add_method(_st, "/om/metadata/update", NULL, metadata_update_cb, this);
	lo_server_thread_add_method(_st, "/om/control_change", "sf", control_change_cb, this);
	lo_server_thread_add_method(_st, "/om/program_add", "siis", program_add_cb, this);
	lo_server_thread_add_method(_st, "/om/program_remove", "sii", program_remove_cb, this);
}


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

int
OSCClientReceiver::m_new_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	new_patch(&argv[0]->s, argv[1]->i); // path, poly
	return 0;
}


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


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


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


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


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


int
OSCClientReceiver::m_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;
	
	connection(src_port_path, dst_port_path);

	return 0;
}


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

	disconnection(src_port_path, dst_port_path);

	return 0;
}


/** Notification of a new node creation.
 */
int
OSCClientReceiver::m_new_node_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char*   uri        = &argv[0]->s;
	const char*   node_path  = &argv[1]->s;
	const int32_t poly       =  argv[2]->i;
	const int32_t num_ports  =  argv[3]->i;

	new_node(uri, node_path, poly, num_ports);

	/*_receiving_node_model = new NodeModel(node_path);
	_receiving_node_model->polyphonic((poly == 1));
	_receiving_node_num_ports = num_ports;

	PluginModel* pi = new PluginModel(type, uri);
	_receiving_node_model->plugin(pi);
	
	_receiving_node = true;
	_num_received_ports = 0;
	*/
	return 0;
}


/** Notification of a new port creation.
 */
int
OSCClientReceiver::m_new_port_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* port_path   = &argv[0]->s;
	const char* type        = &argv[1]->s;
	bool        is_output   = (argv[2]->i == 1);
	/*const char* direction   = &argv[2]->s;
	const char* hint        = &argv[3]->s;
	float       default_val =  argv[4]->f;
	float       min_val     =  argv[5]->f;
	float       max_val     =  argv[6]->f;*/

	new_port(port_path, type, is_output);
#if 0
	PortModel::Type ptype = PortModel::CONTROL;
	if (!strcmp(type, "AUDIO")) ptype = PortModel::AUDIO;
	else if (!strcmp(type, "CONTROL")) ptype = PortModel::CONTROL;
	else if (!strcmp(type, "MIDI")) ptype = PortModel::MIDI;
	else cerr << "[OSCClientReceiver] WARNING:  Unknown port type received (" << type << ")" << endl;

#if 0
	PortModel::Direction pdir = PortModel::INPUT;
	if (!strcmp(direction, "INPUT")) pdir = PortModel::INPUT;
	else if (!strcmp(direction, "OUTPUT")) pdir = PortModel::OUTPUT;
	else cerr << "[OSCClientReceiver] WARNING:  Unknown port direction received (" << direction << ")" << endl;
#endif
	PortModel::Direction pdir = is_output ? PortModel::OUTPUT : PortModel::INPUT;

/*
	PortModel::Hint phint = PortModel::NONE;
	if (!strcmp(hint, "LOGARITHMIC")) phint = PortModel::LOGARITHMIC;
	else if (!strcmp(hint, "INTEGER")) phint = PortModel::INTEGER;
	else if (!strcmp(hint, "TOGGLE")) phint = PortModel::TOGGLE;
	
	PortModel* port_model = new PortModel(port_path, ptype, pdir, phint, default_val, min_val, max_val);
*/	
	PortModel* port_model = new PortModel(port_path, ptype, pdir);
	if (m_receiving_node) {
		assert(m_receiving_node_model);
		m_receiving_node_model->add_port(port_model);
		++m_num_received_ports;
		
		// If transmission is done, send new node to client
		if (m_num_received_ports == m_receiving_node_num_ports) {
			new_node_model(m_receiving_node_model);
			m_receiving_node = false;
			m_receiving_node_model = NULL;
			m_num_received_ports = 0;
		}
	} else {
		new_port_model(port_model);
	}

#endif
	return 0;	
}


/** Notification of a new or updated piece of metadata.
 */
int
OSCClientReceiver::m_metadata_update_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_path  = &argv[0]->s;
	const char* key       = &argv[1]->s;

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

	metadata_update(obj_path, key, value);

	return 0;	
}


int
OSCClientReceiver::m_control_change_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* const port_path  = &argv[0]->s;
	const float       value      =  argv[1]->f;

	control_change(port_path, value);

	return 0;	
}


int
OSCClientReceiver::m_response_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	assert(!strcmp(types, "iis"));
	response(argv[0]->i, argv[1]->i, &argv[2]->s);

	return 0;
}


/** Number of plugins in engine, should precede /om/plugin messages in response
 * to a /om/send_plugins
 */
int
OSCClientReceiver::m_num_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	num_plugins(argv[0]->i);

	return 0;
}


/** A plugin info response from the server, in response to a /send_plugins
 */
int
OSCClientReceiver::m_plugin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	assert(argc == 2 && !strcmp(types, "ss"));
	new_plugin(&argv[0]->s, &argv[1]->s); // type, uri

	return 0;	
}


int
OSCClientReceiver::m_program_add_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* node_path  = &argv[0]->s;
	int32_t     bank       =  argv[1]->i;
	int32_t     program    =  argv[2]->i;
	const char* name       = &argv[3]->s;

	program_add(node_path, bank, program, name);

	return 0;	
}


int
OSCClientReceiver::m_program_remove_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg)
{
	const char* node_path  = &argv[0]->s;
	int32_t     bank       =  argv[1]->i;
	int32_t     program    =  argv[2]->i;

	program_remove(node_path, bank, program);

	return 0;	
}


} // namespace Client
} // namespace Ingen