/* This file is part of Ingen.
 * Copyright (C) 2007 Dave Robillard <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 "PatchLibrarian.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include "PatchModel.h"
#include "NodeModel.h"
#include "ConnectionModel.h"
#include "PortModel.h"
#include "PresetModel.h"
#include "OSCController.h"
#include "PluginModel.h"
#include "Path.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <utility> // for pair, make_pair
#include <cassert>
#include <cstring>
#include <string>
#include <unistd.h> // for usleep
#include <cstdlib>  // for atof
#include <cmath>

using std::string; using std::vector; using std::pair;
using std::cerr; using std::cout; using std::endl;

namespace Ingen {
namespace Client {

	
/** Searches for the filename passed in the path, returning the full
 * path of the file, or the empty string if not found.
 *
 * This function tries to be as friendly a black box as possible - if the path
 * passed is an absolute path and the file is found there, it will return
 * that path, etc.
 *
 * additional_path is a list (colon delimeted as usual) of additional
 * directories to look in.  ie the directory the parent patch resides in would
 * be a good idea to pass as additional_path, in the case of a subpatch.
 */
string
PatchLibrarian::find_file(const string& filename, const string& additional_path)
{
	string search_path = additional_path + ":" + m_patch_path;
	
	// Try to open the raw filename first
	std::ifstream is(filename.c_str(), std::ios::in);
	if (is.good()) {
		is.close();
		return filename;
	}
	
	string directory;
	string full_patch_path = "";
	
	while (search_path != "") {
		directory = search_path.substr(0, search_path.find(':'));
		if (search_path.find(':') != string::npos)
			search_path = search_path.substr(search_path.find(':')+1);
		else
			search_path = "";

		full_patch_path = directory +"/"+ filename;
		
		std::ifstream is;
		is.open(full_patch_path.c_str(), std::ios::in);
	
		if (is.good()) {
			is.close();
			return full_patch_path;
		} else {
			cerr << "[PatchLibrarian] Could not find patch file " << full_patch_path << endl;
		}
	}

	return "";
}


/** Save a patch from a PatchModel to a filename.
 *
 * The filename passed is the true filename the patch will be saved to (with no prefixing or anything
 * like that), and the patch_model's filename member will be set accordingly.
 *
 * This will break if:
 * - The filename does not have an extension (ie contain a ".")
 * - The patch_model has no (Ingen) path
 */
void
PatchLibrarian::save_patch(PatchModel* patch_model, const string& filename, bool recursive)
{
	assert(filename != "");
	assert(patch_model->path() != "");
	
	cout << "Saving patch " << patch_model->path() << " to " << filename << endl;

	patch_model->filename(filename);
	
	string dir = filename.substr(0, filename.find_last_of("/"));
	
	NodeModel* nm = NULL;
	PatchModel* spm = NULL; // subpatch model
	
	xmlDocPtr  xml_doc = NULL;
    xmlNodePtr xml_root_node = NULL;
	xmlNodePtr xml_node = NULL;
	xmlNodePtr xml_child_node = NULL;
	xmlNodePtr xml_grandchild_node = NULL;
	
    xml_doc = xmlNewDoc((xmlChar*)"1.0");
    xml_root_node = xmlNewNode(NULL, (xmlChar*)"patch");
    xmlDocSetRootElement(xml_doc, xml_root_node);

	const size_t temp_buf_length = 255;
	char temp_buf[temp_buf_length];
	
	string patch_name;
	if (patch_model->path() != "/") {
	  patch_name = patch_model->name();
	} else {
	  patch_name = filename;
	  if (patch_name.find("/") != string::npos)
	    patch_name = patch_name.substr(patch_name.find_last_of("/") + 1);
	  if (patch_name.find(".") != string::npos)
	    patch_name = patch_name.substr(0, patch_name.find_last_of("."));
	}

	assert(patch_name.length() > 0);
	xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"name",
			       (xmlChar*)patch_name.c_str());
	
	snprintf(temp_buf, temp_buf_length, "%zd", patch_model->poly());
	xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"polyphony", (xmlChar*)temp_buf);
	
	// Write metadata
	for (map<string, string>::const_iterator i = patch_model->metadata().begin();
			i != patch_model->metadata().end(); ++i) {
		// Dirty hack, don't save coordinates in patch file
		if ((*i).first != "module-x" && (*i).first != "module-y"
				&& (*i).first != "filename")
			xml_node = xmlNewChild(xml_root_node, NULL,
				(xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
		assert((*i).first != "connection");
		assert((*i).first != "node");
		assert((*i).first != "subpatch");
		assert((*i).first != "name");
		assert((*i).first != "polyphony");
		assert((*i).first != "preset");
	}
	
	// Save nodes and subpatches
	for (NodeModelMap::const_iterator i = patch_model->nodes().begin(); i != patch_model->nodes().end(); ++i) {
		nm = i->second;
		
		if (nm->plugin()->type() == PluginModel::Patch) {  // Subpatch
			spm = (PatchModel*)i->second;
			xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"subpatch", NULL);
			
			xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)spm->name().c_str());
			
			string ref_filename;
			// No path
			if (spm->filename() == "") {
				ref_filename = spm->name() + ".om";
				spm->filename(dir +"/"+ ref_filename);
			// Absolute path
			} else if (spm->filename().substr(0, 1) == "/") {
				// Attempt to make it a relative path, if it's undernath this patch's dir
				if (dir.substr(0, 1) == "/" && spm->filename().substr(0, dir.length()) == dir) {
					ref_filename = spm->filename().substr(dir.length()+1);
				} else { // FIXME: not good
					ref_filename = spm->filename().substr(spm->filename().find_last_of("/")+1);
					spm->filename(dir +"/"+ ref_filename);
				}
			} else {
				ref_filename = spm->filename();
			}
			
			xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"filename", (xmlChar*)ref_filename.c_str());
			
			snprintf(temp_buf, temp_buf_length, "%zd", spm->poly());
			xml_child_node = xmlNewChild(xml_node, NULL,  (xmlChar*)"polyphony", (xmlChar*)temp_buf);
			
			// Write metadata
			for (map<string, string>::const_iterator i = nm->metadata().begin();
					i != nm->metadata().end(); ++i) {	
				// Dirty hack, don't save metadata that would be in patch file
				if ((*i).first != "polyphony" && (*i).first != "filename"
					&& (*i).first != "author" && (*i).first != "description")
					xml_child_node = xmlNewChild(xml_node, NULL,
						(xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
			}
	
			if (recursive)
				save_patch(spm, spm->filename(), true);

		} else {  // Normal node
			xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"node", NULL);
			
			xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"name", (xmlChar*)nm->name().c_str());
			
			if (nm->plugin() == NULL) break;
	
			xml_child_node = xmlNewChild(xml_node, NULL,  (xmlChar*)"polyphonic",
				(xmlChar*)((nm->polyphonic()) ? "true" : "false"));
				
			xml_child_node = xmlNewChild(xml_node, NULL,  (xmlChar*)"type",
				(xmlChar*)nm->plugin()->type_string());
			/*
			xml_child_node = xmlNewChild(xml_node, NULL,  (xmlChar*)"plugin-label",
				(xmlChar*)(nm->plugin()->plug_label().c_str()));
	
			if (nm->plugin()->type() != PluginModel::Internal) {
				xml_child_node = xmlNewChild(xml_node, NULL,  (xmlChar*)"library-name",
					(xmlChar*)(nm->plugin()->lib_name().c_str()));
			}*/
			xml_child_node = xmlNewChild(xml_node, NULL,  (xmlChar*)"plugin-uri",
				(xmlChar*)(nm->plugin()->uri().c_str()));
		
			// Write metadata
			for (map<string, string>::const_iterator i = nm->metadata().begin(); i != nm->metadata().end(); ++i) {
				// DSSI _hack_ (FIXME: fix OSC to be more like this and not smash DSSI into metadata?)
				if ((*i).first.substr(0, 16) == "dssi-configure--") {
					xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"dssi-configure", NULL);
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
						(xmlChar*)"key", (xmlChar*)(*i).first.substr(16).c_str());
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
							(xmlChar*)"value", (xmlChar*)(*i).second.c_str());
				} else if ((*i).first == "dssi-program") {
					xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"dssi-program", NULL);
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
						(xmlChar*)"bank", (xmlChar*)(*i).second.substr(0, (*i).second.find("/")).c_str());
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL,
						(xmlChar*)"program", (xmlChar*)(*i).second.substr((*i).second.find("/")+1).c_str());
				} else {
					xml_child_node = xmlNewChild(xml_node, NULL,
						(xmlChar*)(*i).first.c_str(), (xmlChar*)(*i).second.c_str());
				}
			}
	
			PortModel* pm = NULL;
			// Write port metadata, if necessary
			for (list<PortModel*>::const_iterator i = nm->ports().begin(); i != nm->ports().end(); ++i) {
				pm = (*i);
				if (pm->is_input() && pm->user_min() != pm->min_val() || pm->user_max() != pm->max_val()) {
					xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port", NULL);
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"name",
						(xmlChar*)pm->path().name().c_str());
					snprintf(temp_buf, temp_buf_length, "%f", pm->user_min());
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"user-min", (xmlChar*)temp_buf);
					snprintf(temp_buf, temp_buf_length, "%f", pm->user_max());
					xml_grandchild_node = xmlNewChild(xml_child_node, NULL, (xmlChar*)"user-max", (xmlChar*)temp_buf);
				}	
			}
		}
	}

	// Save connections
	
	const list<ConnectionModel*>& cl = patch_model->connections();
	const ConnectionModel* c = NULL;
	
	for (list<ConnectionModel*>::const_iterator i = cl.begin(); i != cl.end(); ++i) {
		c = (*i);
		xml_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"connection", NULL);
		xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"source-node",
			(xmlChar*)c->src_port_path().parent().name().c_str());
		xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"source-port",
			(xmlChar*)c->src_port_path().name().c_str());
		xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"destination-node",
			(xmlChar*)c->dst_port_path().parent().name().c_str());
		xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"destination-port",
			(xmlChar*)c->dst_port_path().name().c_str());
	}
	
    // Save control values (ie presets eventually, right now just current control vals)
	
	xmlNodePtr xml_preset_node = xmlNewChild(xml_root_node, NULL, (xmlChar*)"preset", NULL);
	xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"name", (xmlChar*)"default");

	PortModel* pm = NULL;

	// Save node port controls
	for (NodeModelMap::const_iterator n = patch_model->nodes().begin(); n != patch_model->nodes().end(); ++n) {
		nm = n->second;
		for (PortModelList::const_iterator p = nm->ports().begin(); p != nm->ports().end(); ++p) {
			pm = *p;
			if (pm->is_input() && pm->is_control()) {
				float val = pm->value();
				xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"control",  NULL);
				xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"node-name",
					(xmlChar*)nm->name().c_str());
				xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port-name",
					(xmlChar*)pm->path().name().c_str());
				snprintf(temp_buf, temp_buf_length, "%f", val);
				xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"value",
					(xmlChar*)temp_buf);
			}
		}
	}
	
	// Save patch port controls
	for (PortModelList::const_iterator p = patch_model->ports().begin();
			p != patch_model->ports().end(); ++p) {
		pm = *p;
		if (pm->is_input() && pm->is_control()) {
			float val = pm->value();
			xml_node = xmlNewChild(xml_preset_node, NULL, (xmlChar*)"control",  NULL);
			xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"port-name",
				(xmlChar*)pm->path().name().c_str());
			snprintf(temp_buf, temp_buf_length, "%f", val);
			xml_child_node = xmlNewChild(xml_node, NULL, (xmlChar*)"value",
				(xmlChar*)temp_buf);
		}
	}
	
	xmlSaveFormatFile(filename.c_str(), xml_doc, 1); // 1 == pretty print

    xmlFreeDoc(xml_doc);
    xmlCleanupParser();
}


/** Load a patch in to the engine (and client) from a patch file.
 *
 * The name and poly from the passed PatchModel are used.  If the name is
 * the empty string, the name will be loaded from the file.  If the poly
 * is 0, it will be loaded from file.  Otherwise the given values will
 * be used.
 *
 * If @a wait is set, the patch will be checked for existence before
 * loading everything in to it (to prevent messing up existing patches
 * that exist at the path this one should load as).
 *
 * If the @a existing parameter is true, the patch will be loaded into a
 * currently existing patch (ie a merging will take place).  Errors will
 * result if Nodes of conflicting names exist.
 *
 * Returns the path of the newly created patch.
 */
string
PatchLibrarian::load_patch(PatchModel* pm, bool wait, bool existing)
{
	string filename = pm->filename();

	string additional_path = (pm->parent() == NULL)
		? "" : ((PatchModel*)pm->parent())->filename();
	additional_path = additional_path.substr(0, additional_path.find_last_of("/"));

	filename = find_file(pm->filename(), additional_path);

	size_t poly = pm->poly();

	//cerr << "[PatchLibrarian] Loading patch " << filename << "" << endl;

	const size_t temp_buf_length = 255;
	char temp_buf[temp_buf_length];
	
	bool load_name = (pm->path() == "");
	bool load_poly = (poly == 0);
	
	xmlDocPtr doc = xmlParseFile(filename.c_str());

	if (doc == NULL ) {
		cerr << "Unable to parse patch file." << endl;
		return "";
	}

	xmlNodePtr cur = xmlDocGetRootElement(doc);

	if (cur == NULL) {
		cerr << "Empty document." << endl;
		xmlFreeDoc(doc);
		return "";
	}

	if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) {
		cerr << "File is not an Ingen patch file (root node != <patch>)" << endl;
		xmlFreeDoc(doc);
		return "";
	}

	xmlChar* key = NULL;
	cur = cur->xmlChildrenNode;
	string path;

	pm->filename(filename);

	// Load Patch attributes
	while (cur != NULL) {
		key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
		
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
			if (load_name) {
				assert(key != NULL);
				if (pm->parent() != NULL) {
					path = pm->parent()->base_path() + string((char*)key);
				} else {
					path = string("/") + string((char*)key);
				}
				assert(path.find("//") == string::npos);
				assert(path.length() > 0);
				pm->set_path(path);
			}
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
			if (load_poly) {
				poly = atoi((char*)key);
				pm->poly(poly);
			}
		} else if (xmlStrcmp(cur->name, (const xmlChar*)"connection")
				&& xmlStrcmp(cur->name, (const xmlChar*)"node")
				&& xmlStrcmp(cur->name, (const xmlChar*)"subpatch")
				&& xmlStrcmp(cur->name, (const xmlChar*)"preset")) {
			// Don't know what this tag is, add it as metadata without overwriting
			// (so caller can set arbitrary parameters which will be preserved)
			if (key != NULL)
				if (pm->get_metadata((const char*)cur->name) == "")
					pm->set_metadata((const char*)cur->name, (const char*)key);
		}
		
		xmlFree(key);
		key = NULL; // Avoid a (possible?) double free

		cur = cur->next;
	}
	
	if (poly == 0) poly = 1;

	if (!existing) {
		// Wait until the patch is created or the node creations may fail
		if (wait) {
			int id = m_osc_controller->get_next_request_id();
			m_osc_controller->set_wait_response_id(id);
			m_osc_controller->create_patch(pm, id);
			bool succeeded = m_osc_controller->wait_for_response();
	
			// If creating the patch failed, bail out so we don't load all these nodes
			// into an already existing patch
			if (!succeeded) {
				cerr << "[PatchLibrarian] Patch load failed (patch already exists)" << endl;
				return "";
			}
		} else {
			m_osc_controller->create_patch(pm);
		}
	}
	

	// Set the filename metadata.  (FIXME)
	// This isn't so good, considering multiple clients on multiple machines, and
	// absolute filesystem paths obviously aren't going to be correct.  But for now
	// this is all I can figure out to have Save/Save As work properly for subpatches
	m_osc_controller->set_metadata(pm->path(), "filename", pm->filename());

	// Load nodes
	NodeModel* nm = NULL;
	cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
	
	while (cur != NULL) {
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) {
			nm = parse_node(pm, doc, cur);
			if (nm != NULL) {
				m_osc_controller->add_node(nm);
				m_osc_controller->set_all_metadata(nm);
				for (list<PortModel*>::const_iterator j = nm->ports().begin();
						j != nm->ports().end(); ++j) {
					// FIXME: ew
					snprintf(temp_buf, temp_buf_length, "%f", (*j)->user_min());
					m_osc_controller->set_metadata((*j)->path(), "user-min", temp_buf);
					snprintf(temp_buf, temp_buf_length, "%f", (*j)->user_max());
					m_osc_controller->set_metadata((*j)->path(), "user-max", temp_buf);
				}
				nm = NULL;
				usleep(10000);
			}
		}
		cur = cur->next;
	}

	// Load subpatches
	cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
	while (cur != NULL) {
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) {
			load_subpatch(pm, doc, cur);
		}
		cur = cur->next;
	}
	
	// Load connections
	ConnectionModel* cm = NULL;
	cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
	while (cur != NULL) {
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) {
			cm = parse_connection(pm, doc, cur);
			if (cm != NULL) {
				m_osc_controller->connect(cm->src_port_path(), cm->dst_port_path());
				usleep(1000);
			}
		}
		cur = cur->next;
	}
	
	
	// Load presets (control values)
	PresetModel* preset_model = NULL;
	cur = xmlDocGetRootElement(doc)->xmlChildrenNode;
	while (cur != NULL) {
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) {
			preset_model = parse_preset(pm, doc, cur);
			assert(preset_model != NULL);
			if (preset_model->name() == "default")
				m_osc_controller->set_preset(pm->path(), preset_model);
		}
		cur = cur->next;
	}
	
	xmlFreeDoc(doc);
	xmlCleanupParser();

	m_osc_controller->set_all_metadata(pm);

	if (!existing)
		m_osc_controller->enable_patch(pm->path());

	string ret = pm->path();
	return ret;
}


/** Build a NodeModel given a pointer to a Node in a patch file.
 */
NodeModel*
PatchLibrarian::parse_node(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr node)
{
	NodeModel* nm = new NodeModel("/UNINITIALIZED"); // FIXME: ew
	PluginModel* plugin = new PluginModel();

	xmlChar* key;
	xmlNodePtr cur = node->xmlChildrenNode;
	
	bool found_name = false;
	
	while (cur != NULL) {
		key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
		
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
			nm->set_path(parent->base_path() + (char*)key);
			found_name = true;
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphonic"))) {
			nm->polyphonic(!strcmp((char*)key, "true"));
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"type"))) {
			plugin->set_type((const char*)key);
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"library-name"))) {
			plugin->lib_name((char*)key);
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-label"))) {
			plugin->plug_label((char*)key);
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"plugin-uri"))) {
			plugin->uri((char*)key);
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"port"))) {
			xmlNodePtr child = cur->xmlChildrenNode;
			
			string path;
			float user_min = 0.0;
			float user_max = 0.0;
			
			while (child != NULL) {
				key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
				
				if ((!xmlStrcmp(child->name, (const xmlChar*)"name"))) {
					path = nm->base_path() + (char*)key;
				} else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-min"))) {
					user_min = atof((char*)key);
				} else if ((!xmlStrcmp(child->name, (const xmlChar*)"user-max"))) {
					user_max = atof((char*)key);
				}
				
				xmlFree(key);
				key = NULL; // Avoid a (possible?) double free
		
				child = child->next;
			}

			// FIXME: nasty assumptions
			PortModel* pm = new PortModel(path,
					PortModel::CONTROL, PortModel::INPUT, PortModel::NONE,
					0.0, user_min, user_max);
			nm->add_port(pm);

		// DSSI hacks.  Stored in the patch files as special elements, but sent to
		// the engine as normal metadata with specially formatted key/values.  Not
		// sure if this is the best way to go about this, but it's the least damaging
		// right now
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-program"))) {
			xmlNodePtr child = cur->xmlChildrenNode;
			
			string bank;
			string program;
			
			while (child != NULL) {
				key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
				
				if ((!xmlStrcmp(child->name, (const xmlChar*)"bank"))) {
					bank = (char*)key;
				} else if ((!xmlStrcmp(child->name, (const xmlChar*)"program"))) {
					program = (char*)key;
				}
				
				xmlFree(key);
				key = NULL; // Avoid a (possible?) double free
				child = child->next;
			}
			nm->set_metadata("dssi-program", bank +"/"+ program);
			
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"dssi-configure"))) {
			xmlNodePtr child = cur->xmlChildrenNode;
			
			string dssi_key;
			string dssi_value;
			
			while (child != NULL) {
				key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
				
				if ((!xmlStrcmp(child->name, (const xmlChar*)"key"))) {
					dssi_key = (char*)key;
				} else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
					dssi_value = (char*)key;
				}
				
				xmlFree(key);
				key = NULL; // Avoid a (possible?) double free
		
				child = child->next;
			}
			nm->set_metadata(string("dssi-configure--").append(dssi_key), dssi_value);
			
		} else {  // Don't know what this tag is, add it as metadata
			if (key != NULL)
				nm->set_metadata((const char*)cur->name, (const char*)key);
		}
		xmlFree(key);
		key = NULL;

		cur = cur->next;
	}
	
	if (nm->path() == "") {
		cerr << "[PatchLibrarian] Malformed patch file (node tag has empty children)" << endl;
		cerr << "[PatchLibrarian] Node ignored." << endl;
		delete nm;
		return NULL;
	} else {
		nm->plugin(plugin);
		return nm;
	}
}


void
PatchLibrarian::load_subpatch(PatchModel* parent, xmlDocPtr doc, const xmlNodePtr subpatch)
{
	xmlChar *key;
	xmlNodePtr cur = subpatch->xmlChildrenNode;
	
	PatchModel* pm = new PatchModel("", 1); // FIXME: ew
	
	while (cur != NULL) {
		key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
		
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
			if (parent == NULL)
				pm->set_path(string("/") + (const char*)key);
			else
				pm->set_path(parent->base_path() + (const char*)key);
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) {
			pm->poly(atoi((const char*)key));
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"filename"))) {
			pm->filename((const char*)key);
		} else {  // Don't know what this tag is, add it as metadata
			if (key != NULL && strlen((const char*)key) > 0)
				pm->set_metadata((const char*)cur->name, (const char*)key);
		}
		xmlFree(key);
		key = NULL;

		cur = cur->next;
	}

	// This needs to be done after setting the path above, to prevent
	// NodeModel::set_path from calling it's parent's rename_node with
	// an invalid (nonexistant) name
	pm->set_parent(parent);
	
	load_patch(pm, false);
}


/** Build a ConnectionModel given a pointer to a connection in a patch file.
 */
ConnectionModel*
PatchLibrarian::parse_connection(const PatchModel* parent, xmlDocPtr doc, const xmlNodePtr node)
{
	//cerr << "[PatchLibrarian] Parsing connection..." << endl;

	xmlChar *key;
	xmlNodePtr cur = node->xmlChildrenNode;
	
	string source_node, source_port, dest_node, dest_port;
	
	while (cur != NULL) {
		key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
		
		if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-node"))) {
			source_node = (char*)key;
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"source-port"))) {
			source_port = (char*)key;
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-node"))) {
			dest_node = (char*)key;
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"destination-port"))) {
			dest_port = (char*)key;
		}
		
		xmlFree(key);
		key = NULL; // Avoid a (possible?) double free

		cur = cur->next;
	}

	if (source_node == "" || source_port == "" || dest_node == "" || dest_port == "") {
		cerr << "[PatchLibrarian] Malformed patch file (connection tag has empty children)" << endl;
		cerr << "[PatchLibrarian] Connection ignored." << endl;
		return NULL;
	}

	// FIXME: temporary compatibility, remove any slashes from port names
	// remove this soon once patches have migrated
	string::size_type slash_index;
	while ((slash_index = source_port.find("/")) != string::npos)
		source_port[slash_index] = '-';

	while ((slash_index = dest_port.find("/")) != string::npos)
		dest_port[slash_index] = '-';
	
	ConnectionModel* cm = new ConnectionModel(parent->base_path() + source_node +"/"+ source_port,
		parent->base_path() + dest_node +"/"+ dest_port);
	
	return cm;
}


/** Build a PresetModel given a pointer to a preset in a patch file.
 */
PresetModel*
PatchLibrarian::parse_preset(const PatchModel* patch, xmlDocPtr doc, const xmlNodePtr node)
{
	xmlNodePtr cur = node->xmlChildrenNode;
	xmlChar* key;

	PresetModel* pm = new PresetModel(patch->base_path());
	
	while (cur != NULL) {
		key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);

		if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) {
			assert(key != NULL);
			pm->name((char*)key);
		} else if ((!xmlStrcmp(cur->name, (const xmlChar*)"control"))) {
			xmlNodePtr child = cur->xmlChildrenNode;
	
			string node_name = "", port_name = "";
			float val = 0.0;
			
			while (child != NULL) {
				key = xmlNodeListGetString(doc, child->xmlChildrenNode, 1);
				
				if ((!xmlStrcmp(child->name, (const xmlChar*)"node-name"))) {
					node_name = (char*)key;
				} else if ((!xmlStrcmp(child->name, (const xmlChar*)"port-name"))) {
					port_name = (char*)key;
				} else if ((!xmlStrcmp(child->name, (const xmlChar*)"value"))) {
					val = atof((char*)key);
				}
				
				xmlFree(key);
				key = NULL; // Avoid a (possible?) double free
		
				child = child->next;
			}
			
			if (port_name == "") {
				string msg = "Unable to parse control in patch file ( node = ";
				msg.append(node_name).append(", port = ").append(port_name).append(")");
				m_client_hooks->error(msg);
			} else {
				// FIXME: temporary compatibility, remove any slashes from port name
				// remove this soon once patches have migrated
				string::size_type slash_index;
				while ((slash_index = port_name.find("/")) != string::npos)
					port_name[slash_index] = '-';
				pm->add_control(node_name, port_name, val);
			}
		}
		xmlFree(key);
		key = NULL;
		cur = cur->next;
	}
	if (pm->name() == "") {
		m_client_hooks->error("Preset in patch file has no name.");
		pm->name("Unnamed");
	}

	return pm;
}

} // namespace Client
} // namespace Ingen