/* This file is part of Ingen.
 * Copyright (C) 2007 Dave 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 "interface/ClientInterface.h"
#include "interface/ClientKey.h"
#include "OSCModelEngineInterface.h"
#include "OSCClientReceiver.h"
#include "SigClientInterface.h"
#include "Store.h"
#include "PatchLibrarian.h"
#include "PluginModel.h"
#include "raul/SharedPtr.h"
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include "cmdline.h"  // generated by gengetopt

using std::cout;
using std::endl;

using namespace Ingen::Client;

void do_something();

string random_name();

void create_patch(); 
void destroy(); 
void add_node(); 
void connect(); 
void disconnect(); 
void disconnect_all(); 
void set_port_value(); 
void set_port_value_queued(); 
void rename_object();


// Yay globals!
SharedPtr<OSCModelEngineInterface> engine;
SharedPtr<Store> store;


// OSC listening non-threaded signal emitter
struct OSCSigEmitter : public OSCClientReceiver, public SigClientInterface {
public:
	OSCSigEmitter(int listen_port)
	: Ingen::Shared::ClientInterface()
	, OSCClientReceiver(listen_port)
	, SigClientInterface()
	{
	}
};


int
main(int argc, char** argv)
{
	const char* engine_url  = NULL;
	int         client_port = 0;

	/* Parse command line options */
	gengetopt_args_info args_info;
	if (cmdline_parser (argc, argv, &args_info) != 0)
		return 1;

	if (args_info.engine_url_given) {
		engine_url = args_info.engine_url_arg;
	} else {
		cout << "[Main] No engine URL specified.  Attempting to use osc.udp://localhost:16180" << endl;
		engine_url = "osc.udp://localhost:16180";
	}	

	if (args_info.client_port_given)
		client_port = args_info.client_port_arg;
	else
		client_port = 0; // will choose a free port automatically
	
	engine = SharedPtr<ModelEngineInterface>(new OSCModelEngineInterface(engine_url));
	SharedPtr<SigClientInterface> emitter(new OSCSigEmitter(16182));
	
	store = SharedPtr<Store>(new Store(engine, emitter));

	engine->activate();
	
	/* Connect to engine */
	//engine->attach(engine_url);
	engine->register_client(ClientKey(), emitter);
	
	engine->load_plugins();
	engine->request_plugins();

	//int id = engine->get_next_request_id();
	engine->request_all_objects(/*id*/);
	//engine->set_wait_response_id(id);
	//engine->wait_for_response();

	// Disable DSP for stress testing
	engine->disable_patch("/");
	
	while (true) {
		do_something();
		usleep(10000);
	}
	
	sleep(2);
	engine->unregister_client(ClientKey());
	//engine->detach();

	return 0;
}

/** Does some random action
 */
void
do_something()
{
	int action = rand() % 10;

	switch(action) {
	case 0:
		create_patch(); break;
	case 1:
		destroy(); break;
	case 2:
		add_node(); break;
	case 3:
		connect(); break;
	case 4:
		disconnect(); break;
	case 5:
		disconnect_all(); break;
	case 6:
		set_port_value(); break;
	case 7:
		set_port_value_queued(); break;
	case 8:
		rename_object(); break;
	default:
		break;
	}
}


string
random_name()
{
	int length = (rand()%10)+1;
	string name(length, '-');

	for (int i=0; i < length; ++i)
		name[i] = 'a' + rand()%26;

	return name;
}


Path
random_object()
{
	typedef map<Path, SharedPtr<ObjectModel> > ObjectMap;
	
	const ObjectMap& objects = store->objects();

	// Return a crap path every once in a while
	if (rand() % 30)
		return "/DEAD/BEEF";

	// "random" is a bit of a generous label...
	for (ObjectMap::const_iterator i = objects.begin(); i != objects.end(); ++i)
		if (rand() % objects.size())
			return i->second->path();

	return "/";
}


string
random_plugin()
{
	typedef map<string, SharedPtr<PluginModel> > PluginMap;
	
	const PluginMap& plugins = store->plugins();

	// Return a crap path every once in a while
	if (rand() % 30)
		return "DEAD:BEEF";

	// "random" is a bit of a generous label...
	for (PluginMap::const_iterator i = plugins.begin(); i != plugins.end(); ++i)
		if (rand() % plugins.size())
			return i->second->uri();

	return "DEAD:BEEF2";
}


void
create_patch()
{
	// Make the probability of this happening inversely proportionate to the number
	// of objects to keep the # in a sane range
	if (store->objects().size() > 0 && (rand() % store->objects().size()))
		return;

	bool subpatch = rand()%2;
	Path parent = "/";
	string name = random_name();
	
	if (subpatch)
		parent = random_object();  // very likely invalid parent error

	const Path path = parent.base() + name;

	cout << "Creating patch " << path << endl;

	engine->create_patch(path, (rand()%8)+1);
	
	// Spread them out a bit for easier monitoring with ingenuity
	engine->set_metadata(path, "ingenuity:module-x", 1600 + rand()%800 - 400);
	engine->set_metadata(path, "ingenuity:module-y", 1200 + rand()%700 - 350);
}

 
void
destroy()
{
	// Make the probability of this happening proportionate to the number
	// of patches to keep the # in a sane range
	if (store->objects().size() > 0 && !(rand() % store->objects().size()))
		return;

	engine->destroy(random_object());
}

 
void
add_node()
{
	const Path path = random_object().base() + random_name();

	cout << "Adding node " << path << endl;
	engine->create_node(path, random_plugin(), rand()%2);

	// Spread them out a bit for easier monitoring with ingenuity
	engine->set_metadata(path, "ingenuity:module-x", 1600 + rand()%800 - 400);
	engine->set_metadata(path, "ingenuity:module-y", 1200 + rand()%700 - 350);
}

 
void
connect()
{
	if (!(rand() % 10)) {  // Attempt a connection between two nodes in the same patch
		PatchModel* parent = store->random_patch();
		NodeModel* n1 = store->random_node_in_patch(parent);
		NodeModel* n2 = store->random_node_in_patch(parent);
		PortModel* p1 = store->random_port_in_node(n1);
		PortModel* p2 = store->random_port_in_node(n2);
		
		if (p1 != NULL && p2 != NULL) {
			cout << "Connecting " << p1->path() << " -> " << p2->path() << endl;
			engine->connect(p1->path(), p2->path());
		}

	} else {  // Attempt a connection between two truly random nodes
		PortModel* p1 = store->random_port();
		PortModel* p2 = store->random_port();
	
		if (p1 != NULL && p2 != NULL) {
			cout << "Connecting " << p1->path() << " -> " << p2->path() << endl;
			engine->connect(p1->path(), p2->path());
		}
	}	
}

 
void
disconnect()
{
	PortModel* p1 = store->random_port();
	PortModel* p2 = store->random_port();

	if (p1 != NULL && p2 != NULL) {
		cout << "Disconnecting " << p1->path() << " -> " << p2->path() << endl;
		engine->disconnect(p1->path(), p2->path());
	}
}

 
void
disconnect_all()
{
	PortModel* p = store->random_port();

	if (p != NULL) {
		cout << "Disconnecting all from" << p->path() << endl;
		engine->disconnect_all(p->path());
	}
}

 
void
set_port_value()
{
	PortModel* pm = store->random_port();
	float val = (float)rand() / (float)RAND_MAX;
	
	if (pm != NULL) {
		cout << "Setting control for port " << pm->path() << " to " << val << endl;
		engine->set_port_value(pm->path(), val);
	}
}

 
void
set_port_value_queued()
{
	PortModel* pm = store->random_port();
	float val = (float)rand() / (float)RAND_MAX;
	
	if (pm != NULL) {
		cout << "Setting control (slow) for port " << pm->path() << " to " << val << endl;
		engine->set_port_value_queued(pm->path(), val);
	}
}


void
rename_object()
{
	// 1/6th chance of it being a patch
	/*int type = rand()%6;

	if (type == 0) {
		NodeModel* n = store->random_node();
		if (n != NULL)
			engine->rename(n->path(), random_name());
	} else {
		PatchModel* p = store->random_patch();
		if (p != NULL)
			engine->rename(p->path(), random_name());
	}*/
}