var fs = require('fs');

// Constants

var con =
{
	allowable_connections:
	[
		['dialogue.Text', 'dialogue.Text'],
		['dialogue.Text', 'dialogue.Node'],
		['dialogue.Text', 'dialogue.Choice'],
		['dialogue.Text', 'dialogue.Set'],
		['dialogue.Text', 'dialogue.Branch'],
		['dialogue.Text', 'dialogue.Function'],
		['dialogue.Text', 'dialogue.Tree'],
		['dialogue.Text', 'dialogue.GoToLabel'],
		['dialogue.Node', 'dialogue.Text'],
		['dialogue.Node', 'dialogue.Node'],
		['dialogue.Node', 'dialogue.Choice'],
		['dialogue.Node', 'dialogue.Set'],
		['dialogue.Node', 'dialogue.Branch'],
		['dialogue.Node', 'dialogue.Function'],
		['dialogue.Node', 'dialogue.Tree'],
		['dialogue.Node', 'dialogue.GoToLabel'],
		['dialogue.Tree', 'dialogue.Node'],
		['dialogue.Tree', 'dialogue.Text'],
		['dialogue.Tree', 'dialogue.Choice'],
		['dialogue.Tree', 'dialogue.Set'],
		['dialogue.Tree', 'dialogue.Branch'],
		['dialogue.Tree', 'dialogue.Function'],
		['dialogue.Tree', 'dialogue.Tree'],
		['dialogue.Tree', 'dialogue.GoToLabel'],
		['dialogue.StartNode', 'dialogue.Text'],
		['dialogue.StartNode', 'dialogue.Node'],
		['dialogue.StartNode', 'dialogue.Choice'],
		['dialogue.StartNode', 'dialogue.Set'],
		['dialogue.StartNode', 'dialogue.Branch'],
		['dialogue.StartNode', 'dialogue.Function'],
		['dialogue.StartNode', 'dialogue.Tree'],
		['dialogue.StartNode', 'dialogue.GoToLabel'],
		['dialogue.Choice', 'dialogue.Text'],
		['dialogue.Choice', 'dialogue.Node'],
		['dialogue.Choice', 'dialogue.Set'],
		['dialogue.Choice', 'dialogue.Branch'],
		['dialogue.Choice', 'dialogue.Function'],
		['dialogue.Choice', 'dialogue.Tree'],
		['dialogue.Choice', 'dialogue.GoToLabel'],
		['dialogue.Set', 'dialogue.Text'],
		['dialogue.Set', 'dialogue.Node'],
		['dialogue.Set', 'dialogue.Set'],
		['dialogue.Set', 'dialogue.Branch'],
		['dialogue.Set', 'dialogue.Function'],
		['dialogue.Set', 'dialogue.Tree'],
		['dialogue.Set', 'dialogue.GoToLabel'],
		['dialogue.Function', 'dialogue.Text'],
		['dialogue.Function', 'dialogue.Node'],
		['dialogue.Function', 'dialogue.Set'],
		['dialogue.Function', 'dialogue.Branch'],
		['dialogue.Function', 'dialogue.Function'],
		['dialogue.Function', 'dialogue.Tree'],
		['dialogue.Function', 'dialogue.GoToLabel'],
		['dialogue.Branch', 'dialogue.Text'],
		['dialogue.Branch', 'dialogue.Node'],
		['dialogue.Branch', 'dialogue.Set'],
		['dialogue.Branch', 'dialogue.Branch'],
		['dialogue.Branch', 'dialogue.Function'],
		['dialogue.Branch', 'dialogue.Tree'],
		['dialogue.Branch', 'dialogue.GoToLabel'],
	],

	default_link: new joint.dia.Link(
	{
		attrs:
		{
			'.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', },
			'.link-tools .tool-remove circle, .marker-vertex': { r: 8 },
		},
	}),
};
con.default_link.set('smooth', true);

// State

var state =
{
	graph: new joint.dia.Graph(),
	paper: null,
	filepath: null,
	panning: false,
	mouse_position: { x: 0, y: 0 },
	context_position: { x: 0, y: 0 },
	menu: null,
};

// Models

joint.shapes.dialogue = {};
joint.shapes.dialogue.Base = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Base',
			size: { width: 100, height: 64 },
			name: '',
			attrs:
			{
				rect: { stroke: 'none', 'fill-opacity': 0 },
				text: { display: 'none' },
			},
		},
		joint.shapes.devs.Model.prototype.defaults
	),
});

joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
{
	template:
	[
		'<div class="node">',
		'<span class="label"></span>',
		'<button class="delete">x</button>',
		'<input type="text" class="name" placeholder="Text" />',
		'</div>',
	].join(''),

	initialize: function()
	{
		_.bindAll(this, 'updateBox');
		joint.shapes.devs.ModelView.prototype.initialize.apply(this, arguments);

		this.$box = $(_.template(this.template)());
		// Prevent paper from handling pointerdown.
		this.$box.find('input').on('mousedown click', function(evt) { evt.stopPropagation(); });

		// This is an example of reacting on the input change and storing the input data in the cell model.
		this.$box.find('input.name').on('change', _.bind(function(evt)
		{
			this.model.set('name', $(evt.target).val());
		}, this));

		this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model));
		// Update the box position whenever the underlying model changes.
		this.model.on('change', this.updateBox, this);
		// Remove the box when the model gets removed from the graph.
		this.model.on('remove', this.removeBox, this);

		this.updateBox();
	},

	render: function()
	{
		joint.shapes.devs.ModelView.prototype.render.apply(this, arguments);
		this.paper.$el.prepend(this.$box);
		this.updateBox();
		return this;
	},

	updateBox: function()
	{
		// Set the position and dimension of the box so that it covers the JointJS element.
		var bbox = this.model.getBBox();
		// Example of updating the HTML with a data stored in the cell model.
		var nameField = this.$box.find('input.name');
		if (!nameField.is(':focus'))
			nameField.val(this.model.get('name'));
		var label = this.$box.find('.label');
		var type = this.model.get('type').slice('dialogue.'.length);
		label.text(type);
		label.attr('class', 'label ' + type);
		this.$box[0].classList.add(type);
		this.$box.css({ width: bbox.width, height: bbox.height, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' });
	},

	removeBox: function(evt)
	{
		this.$box.remove();
	},
});

joint.shapes.dialogue.Node = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Node',
            size: { width: 100, height: 65, },
			inPorts: ['input'],
			outPorts: ['output'],
			attrs:
			{
				'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
			},
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});
joint.shapes.dialogue.NodeView = joint.shapes.dialogue.BaseView;

joint.shapes.dialogue.Tree = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Tree',
            size: { width: 150, height: 65, },
			inPorts: ['input'],
			outPorts: ['output'],
			attrs:
			{
				'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
			},
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});
joint.shapes.dialogue.TreeView = joint.shapes.dialogue.BaseView;

joint.shapes.dialogue.Text = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Text',
			inPorts: ['input'],
			outPorts: ['output'],
			attrs:
			{
				'.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
			},
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});
joint.shapes.dialogue.TextView = joint.shapes.dialogue.BaseView;

joint.shapes.dialogue.Function = joint.shapes.devs.Model.extend(
    {
        defaults: joint.util.deepSupplement
        (
            {
                type: 'dialogue.Function',
                size: { width: 200, height: 65, },
                inPorts: ['input'],
                outPorts: ['output'],
                attrs:
                    {
                        '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
                    },
            },
            joint.shapes.dialogue.Base.prototype.defaults
        ),
    });
joint.shapes.dialogue.FunctionView = joint.shapes.dialogue.BaseView;

joint.shapes.dialogue.GoToLabel = joint.shapes.devs.Model.extend(
    {
        defaults: joint.util.deepSupplement
        (
            {
                type: 'dialogue.GoToLabel',
                size: { width: 125, height: 65, },
                inPorts: ['input'],
                outPorts: ['output'],
                attrs:
                    {
                        '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
                    },
            },
            joint.shapes.dialogue.Base.prototype.defaults
        ),
    });
joint.shapes.dialogue.GoToLabelView = joint.shapes.dialogue.BaseView;


joint.shapes.dialogue.Branch = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Branch',
			size: { width: 150, height: 95, },
			inPorts: ['input'],
			outPorts: ['output0'],
			values: [],
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});
joint.shapes.dialogue.BranchView = joint.shapes.dialogue.BaseView.extend(
{
	template:
	[
		'<div class="node">',
		'<span class="label"></span>',
		'<button class="delete">x</button>',
		'<button class="add">+</button>',
		'<button class="remove">-</button>',
		'<input type="text" class="name" placeholder="Variable" />',
		'<input type="text" value="Default" readonly/>',
		'</div>',
	].join(''),

	initialize: function()
	{
		joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
		this.$box.find('.add').on('click', _.bind(this.addPort, this));
		this.$box.find('.remove').on('click', _.bind(this.removePort, this));
	},

	removePort: function()
	{
		if (this.model.get('outPorts').length > 1)
		{
			var outPorts = this.model.get('outPorts').slice(0);
			outPorts.pop();
			this.model.set('outPorts', outPorts);
			var values = this.model.get('values').slice(0);
			values.pop();
			this.model.set('values', values);
			this.updateSize();
		}
	},

	addPort: function()
	{
		var outPorts = this.model.get('outPorts').slice(0);
		outPorts.push('output' + outPorts.length.toString());
		this.model.set('outPorts', outPorts);
		var values = this.model.get('values').slice(0);
		values.push(null);
		this.model.set('values', values);
		this.updateSize();
	},

	updateBox: function()
	{
		joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
		var values = this.model.get('values');
		var valueFields = this.$box.find('input.value');

		// Add value fields if necessary
		for (var i = valueFields.length; i < values.length; i++)
		{
			// Prevent paper from handling pointerdown.
			var $field = $('<input type="text" class="value" />');
			$field.attr('placeholder', 'Value ' + (i + 1).toString());
			$field.attr('index', i);
			this.$box.append($field);
			$field.on('mousedown click', function(evt) { evt.stopPropagation(); });

			// This is an example of reacting on the input change and storing the input data in the cell model.
			$field.on('change', _.bind(function(evt)
			{
				var values = this.model.get('values').slice(0);
				values[$(evt.target).attr('index')] = $(evt.target).val();
				this.model.set('values', values);
			}, this));
		}

		// Remove value fields if necessary
		for (var i = values.length; i < valueFields.length; i++)
			$(valueFields[i]).remove();

		// Update value fields
		valueFields = this.$box.find('input.value');
		for (var i = 0; i < valueFields.length; i++)
		{
			var field = $(valueFields[i]);
			if (!field.is(':focus'))
				field.val(values[i]);
		}
	},

	updateSize: function()
	{
		var textField = this.$box.find('input.name');
		var height = textField.outerHeight(true);
		this.model.set('size', { width: 150, height: 95 + Math.max(0, (this.model.get('outPorts').length - 1) * height) });
	},
});


joint.shapes.dialogue.StartNode = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.StartNode',
			inPorts: ['input'],
			outPorts: ['output'],
			size: { width: 100, height: 30, },
			value: 'DialogueNode.START',
            attrs:
                {
                    '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
                },
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});
joint.shapes.dialogue.StartNodeView = joint.shapes.dialogue.BaseView.extend(
{
	template:
	[
		'<div class="node">',
		'<span class="label"></span>',
		'<button class="delete">x</button>',
		'</div>',
	].join(''),

	initialize: function()
	{
		joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
		//this.$box.find('input.value').on('change', _.bind(function(evt)
		//{
		//	this.model.set('value', $(evt.target).val());
		//}, this));
	},

	updateBox: function()
	{
		joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
		//var field = this.$box.find('input.value');
		//if (!field.is(':focus'))
		//	field.val(this.model.get('value'));
	},
});


joint.shapes.dialogue.Choice = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Choice',
			inPorts: ['input'],
			outPorts: ['output'],
			size: { width: 175, height: 95, },
			value: '',
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});
joint.shapes.dialogue.ChoiceView = joint.shapes.dialogue.BaseView.extend(
{
	template:
	[
		'<div class="node">',
		'<span class="label"></span>',
		'<button class="delete">x</button>',
		'<input type="text" class="name" placeholder="Text" />',
		'<input type="text" class="value" placeholder="Conditions" />',
		'</div>',
	].join(''),

	initialize: function()
	{
		joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
		this.$box.find('input.value').on('change', _.bind(function(evt)
		{
			this.model.set('value', $(evt.target).val());
		}, this));
	},

	updateBox: function()
	{
		joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
		var field = this.$box.find('input.value');
		if (!field.is(':focus'))
			field.val(this.model.get('value'));
	},
});

joint.shapes.dialogue.Set = joint.shapes.devs.Model.extend(
{
	defaults: joint.util.deepSupplement
	(
		{
			type: 'dialogue.Set',
			inPorts: ['input'],
			outPorts: ['output'],
			size: { width: 125, height: 90, },
			value: '',
		},
		joint.shapes.dialogue.Base.prototype.defaults
	),
});

joint.shapes.dialogue.SetView = joint.shapes.dialogue.BaseView.extend(
{
	template:
	[
		'<div class="node">',
		'<span class="label"></span>',
		'<button class="delete">x</button>',
		'<input type="text" class="name" placeholder="Variable" />',
		'<input type="text" class="value" placeholder="Value" />',
		'</div>',
	].join(''),

	initialize: function()
	{
		joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
		this.$box.find('input.value').on('change', _.bind(function(evt)
		{
			this.model.set('value', $(evt.target).val());
		}, this));
	},

	updateBox: function()
	{
		joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
		var field = this.$box.find('input.value');
		if (!field.is(':focus'))
			field.val(this.model.get('value'));
	},
});

// Functions

var func = {};

func.validate_connection = function(cellViewS, magnetS, cellViewT, magnetT, end, linkView)
{
	// Prevent loop linking
	if (magnetS === magnetT)
		return false;

	if (cellViewS === cellViewT)
		return false;

	if ($(magnetT).parents('.outPorts').length > 0) // Can't connect to an output port
		return false;

	var sourceType = cellViewS.model.attributes.type;
	var targetType = cellViewT.model.attributes.type;
	var valid = false;
	for (var i = 0; i < con.allowable_connections.length; i++)
	{
		var rule = con.allowable_connections[i];
		if (sourceType === rule[0] && targetType === rule[1])
		{
			valid = true;
			break;
		}
	}
	if (!valid)
		return false;

	var links = state.graph.getConnectedLinks(cellViewS.model);
	for (var i = 0; i < links.length; i++)
	{
		var link = links[i];
		if (link.attributes.source.id === cellViewS.model.id && link.attributes.source.port === magnetS.attributes.port.nodeValue && link.attributes.target.id)
		{
			var targetCell = state.graph.getCell(link.attributes.target.id);
			if (targetCell.attributes.type !== targetType)
				return false; // We can only connect to multiple targets of the same type
			if (targetCell === cellViewT.model)
				return false; // Already connected
		} 
	}

	return true;
};

func.validate_magnet = function(cellView, magnet)
{
	if ($(magnet).parents('.outPorts').length === 0)
		return false; // we only want output ports

	// If unlimited connections attribute is null, we can only ever connect to one object
	// If it is not null, it is an array of type strings which are allowed to have unlimited connections
	var unlimitedConnections = magnet.getAttribute('unlimitedConnections');
	var links = state.graph.getConnectedLinks(cellView.model);
	for (var i = 0; i < links.length; i++)
	{
		var link = links[i];
		if (link.attributes.source.id === cellView.model.id && link.attributes.source.port === magnet.attributes.port.nodeValue)
		{
			// This port already has a connection
			if (unlimitedConnections && link.attributes.target.id)
			{
				var targetCell = state.graph.getCell(link.attributes.target.id);
				if (unlimitedConnections.indexOf(targetCell.attributes.type) !== -1)
					return true; // It's okay because this target type has unlimited connections
			} 
			return false;
		}
	}

	return true;
};

func.optimized_data = function()
{
	var cells = state.graph.toJSON().cells;
	var nodesByID = {};
	var cellsByID = {};
	var nodes = [];
	for (var i = 0; i < cells.length; i++)
	{
		var cell = cells[i];
		if (cell.type != 'link')
		{
			var node =
			{
				type: cell.type.slice('dialogue.'.length),
				id: cell.id,
			};
			if (node.type === 'Branch')
			{
				node.variable = cell.name;
				node.branches = {};
				for (var j = 0; j < cell.values.length; j++)
				{
					var branch = cell.values[j];
					node.branches[branch] = null;
				}
			}
			else if (node.type === 'Set')
			{
				node.variable = cell.name;
				node.value = cell.value;
				node.next = null;
			}
			else
			{
				node.name = cell.name;
				node.next = null;
			}
			if (node.type === "Choice") {
				node.value = cell.value;
			}
			nodes.push(node);
			nodesByID[cell.id] = node;
			cellsByID[cell.id] = cell;
		}
	}
	for (var i = 0; i < cells.length; i++)
	{
		var cell = cells[i];
		if (cell.type === 'link')
		{
			var source = nodesByID[cell.source.id];
			var target = cell.target ? nodesByID[cell.target.id] : null;
			if (source)
			{
				if (source.type === 'Branch')
				{
					var portNumber = parseInt(cell.source.port.slice('output'.length));
					var value;
					if (portNumber === 0)
						value = '_default';
					else
					{
						var sourceCell = cellsByID[source.id];
						value = sourceCell.values[portNumber - 1];
					}
					source.branches[value] = target ? target.id : null;
				}
				else if ((source.type === 'Text' || source.type === 'Node') && target && target.type === 'Choice')
				{
					if (!source.choices)
					{
						source.choices = [];
						delete source.next;
					}
					source.choices.push(target.id);
				}
				else
					source.next = target ? target.id : null;
			}
		}
	}
	return nodes;
};

// Menu actions

func.flash = function(text)
{
	var $flash = $('#flash');
	$flash.text(text);
	$flash.stop(true, true);
	$flash.show();
	$flash.css('opacity', 1.0);
	$flash.fadeOut({ duration: 1500 });
};

func.apply_fields = function()
{
	$('input[type=text], select').blur();
};

func.save = function()
{
	func.apply_fields();

	if (!state.filepath)
		func.show_save_dialog();
	else
		func.do_save();
};

func.optimized_filename = function(f)
{
	return f.substring(0, f.length - 2) + 'dlz';
};

func.perfect_typescript = function(f)
{
	return f.substring(0, f.length - 2) + 'dl.ts';
};

func.just_name = function (f) {
	return f.substring(0, f.length - 3);
};

func.do_save = function()
{
	if (state.filepath)
	{
		fs.writeFileSync(state.filepath, JSON.stringify(state.graph), 'utf8');
		//fs.writeFileSync(func.optimized_filename(state.filepath), JSON.stringify(func.optimized_data()), 'utf8');
		let name = func.just_name(func.filename_from_filepath(state.filepath));
		let cleants = "// File created automatically by custom Dialogger on " + new Date().toLocaleString() + "\n";
		cleants += "// Do not tamper with this file.\n// It will be replaced automatically by Dialogger and all changes will be lost.\n// Instead change " + name + ".dl.";
		cleants += "\nmodule DialogueTrees {\n	export let " + name + " = (function () {\n";
		cleants += func.ts_data(func.optimized_data(), name).replace(/^/gm, '        ') + "\n        return tree;\n    })();\n}";
		
		fs.writeFileSync(func.perfect_typescript(state.filepath), cleants, 'utf8');
		func.flash('Saved ' + state.filepath);
	}
};

func.filename_from_filepath = function(f)
{
	return f.replace(/^.*[\\\/]/, '');
};

func.show_open_dialog = function()
{
	$('#file_open').click();
};

func.show_save_dialog = function()
{
	$('#file_save').click();
};

func.add_node = function(constructor)
{
	return function()
	{
		var container = $('#container')[0];
		var element = new constructor(
		{
			position: { x: state.context_position.x + container.scrollLeft, y: state.context_position.y + container.scrollTop },
		});
		state.graph.addCells([element]);
	};
};

func.clear = function()
{
	state.graph.clear();
	state.filepath = null;
	document.title = 'Dialogger';
};

func.handle_open_files = function(files)
{
	state.filepath = files[0].path;
	var data = fs.readFileSync(state.filepath);
	document.title = func.filename_from_filepath(state.filepath);
	state.graph.clear();
	state.graph.fromJSON(JSON.parse(data));
};

func.handle_save_files = function(files)
{
	state.filepath = files[0].path;
	func.do_save();
};

func.ts_data = function(nodes, name) {
	let declarations = [];
	let addDeclaration = (dec) => {
		if (declarations.indexOf(dec) == -1) {
			declarations.push(dec);
		}
	}
	addDeclaration("let tree : DialogueTree = new DialogueTree(" + JSON.stringify(name) + ");\n");
	
	let finalString = "";
	for (let i = 0; i < nodes.length; i++) {
		finalString += "\n\n";
		let node = nodes[i];
        if (node.type == "Node") {
            finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
            finalString += "node.setName(" + JSON.stringify(node.name) + ");\n";
            if (node.next != null && node.next != undefined) {
                finalString += "node.setNext(" + JSON.stringify(node.next) + ");\n";
            }
            if (node.choices != undefined && node.choices != null) {
                finalString += "node.setChoices(" + JSON.stringify(node.choices) + ");\n";
            }
            finalString += "tree.addNode(node);\n";
            addDeclaration("let node : DialogueNode;\n");
        } else if (node.type == "GoToLabel") {
            finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
            finalString += "node.setNext(" + JSON.stringify(node.name) + ");\n";
            finalString += "tree.addNode(node);\n";
            addDeclaration("let node : DialogueNode;\n");
        } else if (node.type == "Tree") {
            finalString += "nodetree = new DialogueNodeTree(" + JSON.stringify(node.id) + ");\n";
            finalString += "nodetree.setTree(() => { return " + node.name + "; });\n";
            if (node.next != null && node.next != undefined) {
                finalString += "nodetree.setNext(" + JSON.stringify(node.next) + ");\n";
            }
            if (node.choices != undefined && node.choices != null) {
                finalString += "nodetree.setChoices(" + JSON.stringify(node.choices) + ");\n";
            }
            finalString += "tree.addNode(nodetree);\n";
            addDeclaration("let nodetree : DialogueNodeTree;\n");
        } else if (node.type == "StartNode") {
			finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
			if (node.next != null && node.next != undefined) {
				finalString += "node.setNext(" + JSON.stringify(node.next) + ");\n";
			}
			if (node.choices != undefined && node.choices != null) {
				finalString += "node.setChoices(" + JSON.stringify(node.choices) + ");\n";
			}
			finalString += "tree.addStartNode(node);\n";
			addDeclaration("let node : DialogueNode;\n");
		} else if (node.type == "Text") {
			finalString += "text = new DialogueText(" + JSON.stringify(node.id) + ");\n";
			//finalString += "node.setSay(" + node.name + ");\n"; // this way all code we placed will be added right away!
			// SetSay needs to be a function, otherwise data might not be updated when printing!
			finalString += "text.setSay(() => { return new Say(" + node.name + ");});\n";
			if (node.next != null && node.next != undefined) {
				finalString += "text.setNext(" + JSON.stringify(node.next) + ");\n";
			}
			if (node.choices != undefined && node.choices != null) {
				finalString += "text.setChoices(" + JSON.stringify(node.choices) + ");\n";
			}
			finalString += "tree.addNode(text);\n";
			addDeclaration("let text : DialogueText;\n");
		} else if (node.type == "Choice") {
			finalString += "choice = new DialogueChoice(" + JSON.stringify(node.id) + ");\n";
			//finalString += "node.setSay(" + node.name + ");\n"; // this way all code we placed will be added right away!
			// SetSay needs to be a function, otherwise data might not be updated when printing!
			finalString += "choice.setSay(() => { return new Say(" + node.name + ");});\n";
			if (node.value != undefined && node.value != null && node.value != "") {
				finalString += "choice.setConditions(() => { return " + node.value + ";});\n"
			}
			if (node.next != null && node.next != undefined) {
				finalString += "choice.setNext(" + JSON.stringify(node.next) + ");\n";
			}
			if (node.choices != undefined && node.choices != null) {
				finalString += "choice.setChoices(" + JSON.stringify(node.choices) + ");\n";
			}
			finalString += "tree.addNode(choice);\n";
			addDeclaration("let choice : DialogueChoice;\n");
		} else if (node.type == "Branch") {
			finalString += "branch = new DialogueBranch(" + JSON.stringify(node.id) + ");\n";
			finalString += "branch.setVariable(() => { return " + node.variable + ";});\n";
			for (let key in node.branches) {
				let branchCondition = key;
				let branchTarget = node.branches[key];
				if (branchCondition == "_default") {
					finalString += "branch.setNext(" + JSON.stringify(branchTarget) + ");\n";
				} else {
					finalString += "branch.addBranch(" + JSON.stringify(branchTarget) + ", () => { return " + branchCondition + ";});\n";
				}
			}
			finalString += "tree.addNode(branch);\n";
			addDeclaration("let branch : DialogueBranch;\n");
		} else if (node.type == "Set" || node.type == "Function") {
			finalString += "set = new DialogueSet(" + JSON.stringify(node.id) + ");\n";
			if (node.type == "Function") {
				finalString += "set.setFunction(() => {" + node.name + "});\n";
			} else {
				finalString += "set.setFunction(() => { " + node.variable + " = " + node.value + ";});\n";
			}
			if (node.next != null && node.next != undefined) {
				finalString += "set.setNext(" + JSON.stringify(node.next) + ");\n";
			}
			if (node.choices != undefined && node.choices != null) {
				finalString += "set.setChoices(" + JSON.stringify(node.choices) + ");\n";
			}
			finalString += "tree.addNode(set);\n";
			addDeclaration("let set : DialogueSet;\n");
		}
	}
	return declarations.join("") + finalString;
};

// Initialize

(function()
{
	state.paper = new joint.dia.Paper(
	{
		el: $('#paper'),
		width: 32000,
		height: 16000,
		model: state.graph,
		gridSize: 1,
		defaultLink: con.default_link,
		validateConnection: func.validate_connection,
		validateMagnet: func.validate_magnet,
		snapLinks: { radius: 75 }, // enable link snapping within 75px lookup radius
		markAvailable: true,
	});

	state.paper.on('blank:pointerdown', function(e, x, y)
	{
		if (e.button === 0 || e.button === 1)
		{
			state.panning = true;
			state.mouse_position.x = e.pageX;
			state.mouse_position.y = e.pageY;
			$('body').css('cursor', 'move');
			func.apply_fields();
		}
	});
	state.paper.on('cell:pointerdown', function(e, x, y)
	{
		func.apply_fields();
	});

	$('#container').mousemove(function(e)
	{
		if (state.panning)
		{
			var $this = $(this);
			$this.scrollLeft($this.scrollLeft() + state.mouse_position.x - e.pageX);
			$this.scrollTop($this.scrollTop() + state.mouse_position.y - e.pageY);
			state.mouse_position.x = e.pageX;
			state.mouse_position.y = e.pageY;
		}
	});

	$('#container').mouseup(function(e)
	{
		state.panning = false;
		$('body').css('cursor', 'default');
	});

	$('#file_open').on('change', function()
	{
		if (this.files)
			func.handle_open_files(this.files);
		// clear files from this input
		var $this = $(this);
		$this.wrap('<form>').parent('form').trigger('reset');
		$this.unwrap();
	});

	$('#file_save').on('change', function()
	{
		func.handle_save_files(this.files);
	});

	$('body').on('dragenter', function(e)
	{
		e.stopPropagation();
		e.preventDefault();
	});

	$('body').on('dragexit', function(e)
	{
		e.stopPropagation();
		e.preventDefault();
	});

	$('body').on('dragover', function(e)
	{
		e.stopPropagation();
		e.preventDefault();
	});

	$('body').on('drop', function(e)
	{
		e.stopPropagation();
		e.preventDefault();
		func.handle_open_files(e.originalEvent.dataTransfer.files);
	});

	$(window).on('keydown', function(event)
	{
		// Catch Ctrl-S or key code 19 on Mac (Cmd-S)
		if (((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 's') || event.which === 19)
		{
			event.stopPropagation();
			event.preventDefault();
			func.save();
			return false;
		}
		else if ((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 'o')
		{
			event.stopPropagation();
			event.preventDefault();
			func.show_open_dialog();
			return false;
		}
		return true;
	});

	$(window).resize(function()
	{
		func.apply_fields();
		var $window = $(window);
		var $container = $('#container');
		$container.height($window.innerHeight());
		$container.width($window.innerWidth());
		return this;
	});

	$(window).trigger('resize');

	// Context menu

	state.menu = new nw.Menu();
	state.menu.append(new nw.MenuItem({ label: 'Text', click: func.add_node(joint.shapes.dialogue.Text) }));
	state.menu.append(new nw.MenuItem({ label: 'Choice', click: func.add_node(joint.shapes.dialogue.Choice) }));
	state.menu.append(new nw.MenuItem({ label: 'Branch', click: func.add_node(joint.shapes.dialogue.Branch) }));
	state.menu.append(new nw.MenuItem({ label: 'Set', click: func.add_node(joint.shapes.dialogue.Set) }));
	state.menu.append(new nw.MenuItem({ label: 'Function', click: func.add_node(joint.shapes.dialogue.Function) }));
	state.menu.append(new nw.MenuItem({ label: 'GoToLabel', click: func.add_node(joint.shapes.dialogue.GoToLabel) }));
	state.menu.append(new nw.MenuItem({ label: 'Tree', click: func.add_node(joint.shapes.dialogue.Tree) }));
	state.menu.append(new nw.MenuItem({ label: 'Label', click: func.add_node(joint.shapes.dialogue.Node) }));
	state.menu.append(new nw.MenuItem({ label: 'Start Point', click: func.add_node(joint.shapes.dialogue.StartNode) }));
	state.menu.append(new nw.MenuItem({ type: 'separator' }));
	state.menu.append(new nw.MenuItem({ label: 'Save', click: func.save }));
	state.menu.append(new nw.MenuItem({ label: 'Open', click: func.show_open_dialog }));
	state.menu.append(new nw.MenuItem({ label: 'New', click: func.clear }));

	document.body.addEventListener('contextmenu', function(e)
	{
		e.preventDefault();
		state.context_position.x = e.x;
		state.context_position.y = e.y;
		state.menu.popup(e.x, e.y);
		return false;
	}, false);
})();
