'use strict';

var url = require('url');
var http = require('http');
var util = require('util');

/*
var netdata = require('netdata');

var example_chart = {
	id: 'id',						// the unique id of the chart
	name: 'name',					// the name of the chart
	title: 'title',					// the title of the chart
	units: 'units',					// the units of the chart dimensions
	family: 'family',				// the family of the chart
	context: 'context',				// the context of the chart
	type: netdata.chartTypes.line,	// the type of the chart
	priority: 0,					// the priority relative to others in the same family
	update_every: 1,				// the expected update frequency of the chart
	dimensions: {
		'dim1': {
			id: 'dim1',				// the unique id of the dimension
			name: 'name',			// the name of the dimension
			algorithm: netdata.chartAlgorithms.absolute,	// the id of the netdata algorithm
			multiplier: 1,			// the multiplier
			divisor: 1,				// the divisor
			hidden: false,			// is hidden (boolean)
		},
		'dim2': {
			id: 'dim2',				// the unique id of the dimension
			name: 'name',			// the name of the dimension
			algorithm: 'absolute',	// the id of the netdata algorithm
			multiplier: 1,			// the multiplier
			divisor: 1,				// the divisor
			hidden: false,			// is hidden (boolean)
		}
		// add as many dimensions as needed
	}
};
*/

var netdata = {
	options: {
		filename: __filename,
		DEBUG: false,
		update_every: 1,
	},

	chartAlgorithms: {
		incremental: 'incremental',
		absolute: 'absolute',
		percentage_of_absolute_row: 'percentage-of-absolute-row',
		percentage_of_incremental_row: 'percentage-of-incremental-row'
	},

	chartTypes: {
		line: 'line',
		area: 'area',
		stacked: 'stacked'
	},

	services: new Array(),
	modules_configuring: 0,
	charts: {},


	processors: {
		http: {
			name: 'http',

			process: function(service, callback) {
				if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': making ' + this.name + ' request: ' + netdata.stringify(service.request));

				var req = http.request(service.request, function(response) {
					if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got server response...');

					var end = false;
					var data = '';
					response.setEncoding('utf8');

					if(response.statusCode !== 200) {
						if(end === false) {
							service.error('Got HTTP code ' + response.statusCode + ', failed to get data.');
							end = true;
							callback(null);
						}
					}

					response.on('data', function(chunk) {
						if(end === false) data += chunk;
					});

					response.on('error', function() {
						if(end === false) {
							service.error(': Read error, failed to get data.');
							end = true;
							callback(null);
						}
					});

					response.on('end', function() {
						if(end === false) {
							if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.');
							end = true;
							callback(data);
						}
					});
				});

				req.on('error', function(e) {
					service.error('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
					callback(null);
				});

				// write data to request body
				if(typeof service.postData !== 'undefined' && service.request.method === 'POST') {
					if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': posting data: ' + service.postData);
					req.write(service.postData);
				}

				req.end();
			}
		}
	},

	stringify: function(obj) {
		return util.inspect(obj, {depth: 10});
	},

	// show debug info, if debug is enabled
	debug: function(msg) {
		if(this.options.DEBUG === true) {
			var now = new Date();
			console.error(now.toString() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
		}
	},

	// log an error
	error: function(msg) {
		var now = new Date();
		console.error(now.toString() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
	},

	// send data to netdata
	send: function(msg) {
		console.log(msg.toString());
	},

	service: function(service) {
		if(typeof service === 'undefined')
			service = {};

		var now = new Date().getTime();

		service._current_chart = null;	// the current chart we work on
		service._queue = '';			// data to be sent to netdata

		service.error_reported = false;	// error log flood control

		service.added = false;			// added to netdata.services
		service.enabled = true;
		service.updates = 0;
		service.running = false;
		service.started = 0;
		service.ended = 0;

		if(typeof service.module === 'undefined') {
			service.module = { name: 'not-defined-module' };
			service.error('Attempted to create service without a module.');
			service.enabled = false;
		}

		if(typeof service.name === 'undefined') {
			service.name = 'unnamed@' + service.module.name + '/' + now;
		}

		if(typeof service.processor === 'undefined')
			service.processor = netdata.processors.http;

		if(typeof service.update_every === 'undefined')
			service.update_every = service.module.update_every;

		if(typeof service.update_every === 'undefined')
			service.update_every = netdata.options.update_every;

		if(service.update_every < netdata.options.update_every)
			service.update_every = netdata.options.update_every;

		// align the runs
		service.next_run = now - (now % (service.update_every * 1000));

		service.commit = function() {
			if(this.added !== true) {
				this.added = true;
				
				var now = new Date().getTime();
				while( this.next_run < now )
					this.next_run += (this.update_every * 1000);

				netdata.services.push(this);
				if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': service committed.');
			}
		};

		service.execute = function(callback) {
			if(service.enabled === false) {
				callback(null);
				return;
			}

			this.module.active++;
			this.running = true;
			this.started = new Date().getTime();
			this.updates++;

			if(netdata.options.DEBUG === true)
				netdata.debug(this.module.name + ': ' + this.name + ': making ' + this.processor.name + ' request: ' + netdata.stringify(this));

			this.processor.process(this, function(response) {
				service.ended = new Date().getTime();
				service.duration = service.ended - service.started;

				if(typeof response === 'undefined')
					response = null;

				if(response !== null)
					service.errorClear();

				if(netdata.options.DEBUG === true)
					netdata.debug(service.module.name + ': ' + service.name + ': processing ' + service.processor.name + ' response (received in ' + (service.ended - service.started).toString() + ' ms)');

				callback(service, response);

				service.running = false;
				service.module.active--;
				if(service.module.active < 0) {
					service.module.active = 0;
					if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': active module counter below zero.');
				}

				if(service.module.active === 0) {
					// check if we run under configure
					if(service.module.configure_callback !== null) {
						if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': configuration finish callback called from processResponse().');
						var ccallback = service.module.configure_callback;
						service.module.configure_callback = null;
						ccallback();
					}
				}
			});
		};

		service.update = function() {
			if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': starting data collection...');

			this.module.update(this, function() {
				if(netdata.options.DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.');
			});
		};

		service.error = function(message) {
			if(this.error_reported === false) {
				netdata.error(this.module.name + ': ' + this.name + ': ' + message);
				this.error_reported = true;
			}
			else if(netdata.options.DEBUG === true)
				netdata.debug(this.module.name + ': ' + this.name + ': ' + message);
		};

		service.errorClear = function() {
			this.error_reported = false;
		};

		service.queue = function(txt) {
			this._queue += txt + '\n';
		};

		service._send_chart_to_netdata = function(chart) {
			// internal function to send a chart to netdata
			this.queue('CHART "' + chart.id + '" "' + chart.name + '" "' + chart.title + '" "' + chart.units + '" "' + chart.family + '" "' + chart.context + '" "' + chart.type + '" ' + chart.priority.toString() + ' ' + chart.update_every.toString());
			
			for(var dim in chart.dimensions) {
				var d = chart.dimensions[dim];

				this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true)?'hidden':'').toString());
				d._created = true;
				d._updated = false;
			}

			chart._created = true;
			chart._updated = false;
		};

		// begin data collection for a chart
		service.begin = function(chart) {
			if(this._current_chart !== null && this._current_chart !== chart) {
				this.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.');
				this.end();
			}

			if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] != chart) {
				this.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.');
				return false;
			}

			if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id);
			this._current_chart = chart;
			this._current_chart._began = true;

			if(this._current_chart._dimensions_count !== 0) {
				if(this._current_chart._created === false || this._current_chart._updated === true)
					this._send_chart_to_netdata(this._current_chart);

				var now = this.ended;
				this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString());
			}
			// else this.error('Called begin() for chart ' + chart.id + ' which is empty.');

			this._current_chart._last_updated = now;
			this._current_chart._began = true;
			this._current_chart._counter++;

			return true;
		};

		// set a collected value for a chart
		// we do most things on the first value we attempt to set
		service.set = function(dimension, value) {
			if(this._current_chart === null) {
				this.error('Called set(' + dimension + ', ' + value + ') without an open chart.');
				return false;
			}

			if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') {
				this.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".');
				return false;
			}

			if(typeof value === 'undefined' || value === null)
				return false;

			if(this._current_chart._dimensions_count !== 0)
				this.queue('SET ' + dimension + ' = ' + value);

			return true;
		};

		// end data collection for the current chart - after calling begin()
		service.end = function() {
			if(this._current_chart !== null && this._current_chart._began === false) {
				this.error('Called end() without an open chart.');
				return false;
			}

			if(this._current_chart._dimensions_count !== 0) {
				this.queue('END');
				netdata.send(this._queue);
			}

			this._queue = '';
			this._current_chart._began = false;
			if(netdata.options.DEBUG === true) netdata.debug('sent chart ' + this._current_chart.id);
			this._current_chart = null;
			return true;
		};

		// discard the collected values for the current chart - after calling begin()
		service.flush = function() {
			if(this._current_chart === null || this._current_chart._began === false) {
				this.error('Called flush() without an open chart.');
				return false;
			}

			this._queue = '';
			this._current_chart._began = false;
			this._current_chart = null;
			return true;
		};

		// create a netdata chart
		service.chart = function(id, chart) {
			if(typeof(netdata.charts[id]) === 'undefined') {
				netdata.charts[id] = {
					_created: false,
					_updated: true,
					_began: false,
					_counter: 0,
					_last_updated: 0,
					_dimensions_count: 0,
					id: id,
					name: id,
					title: 'untitled chart',
					units: 'a unit',
					family: '',
					context: '',
					type: netdata.chartTypes.line,
					priority: 50000,
					update_every: netdata.options.update_every,
					dimensions: {}
				};
			}

			var c = netdata.charts[id];

			if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its name');
				c.name = chart.name;
				c._updated = true;
			}

			if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its title');
				c.title = chart.title;
				c._updated = true;
			}

			if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its units');
				c.units = chart.units;
				c._updated = true;
			}

			if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its family');
				c.family = chart.family;
				c._updated = true;
			}

			if(typeof(chart.context) !== 'undefined' && chart.context !== c.context) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its context');
				c.context = chart.context;
				c._updated = true;
			}

			if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its type');
				c.type = chart.type;
				c._updated = true;
			}

			if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its priority');
				c.priority = chart.priority;
				c._updated = true;
			}

			if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) {
				if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every from ' + c.update_every + ' to ' + chart.update_every);
				c.update_every = chart.update_every;
				c._updated = true;
			}

			if(typeof(chart.dimensions) !== 'undefined') {
				for(var x in chart.dimensions) {
					if(typeof(c.dimensions[x]) === 'undefined') {
						c._dimensions_count++;

						c.dimensions[x] = {
							_created: false,
							_updated: false,
							id: x,					// the unique id of the dimension
							name: x,				// the name of the dimension
							algorithm: netdata.chartAlgorithms.absolute,	// the id of the netdata algorithm
							multiplier: 1,			// the multiplier
							divisor: 1,				// the divisor
							hidden: false,			// is hidden (boolean)
						};

						if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x);
						c._updated = true;
					}

					var dim = chart.dimensions[x];
					var d = c.dimensions[x];

					if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) {
						if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name');
						d.name = dim.name;
						d._updated = true;
					}

					if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) {
						if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm);
						d.algorithm = dim.algorithm;
						d._updated = true;
					}

					if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) {
						if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier');
						d.multiplier = dim.multiplier;
						d._updated = true;
					}

					if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) {
						if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor');
						d.divisor = dim.divisor;
						d._updated = true;
					}

					if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) {
						if(netdata.options.DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status');
						d.hidden = dim.hidden;
						d._updated = true;
					}

					if(d._updated) c._updated = true;
				}
			}

			//if(netdata.options.DEBUG === true) netdata.debug(netdata.charts);
			return netdata.charts[id];
		};

		return service;
	},

	runAllServices: function() {
		if(netdata.options.DEBUG === true) netdata.debug('runAllServices()');

		var now = new Date().getTime();
		var len = netdata.services.length;
		while(len--) {
			var service = netdata.services[len];

			if(service.enabled === false || service.running === true) continue;
			if(now <= service.next_run) continue;

			service.update();

			now = new Date().getTime();
			while(service.next_run < now)
				service.next_run += (service.update_every * 1000);
		}

		// 1/10th of update_every in pause
		setTimeout(netdata.runAllServices, netdata.options.update_every * 100);
	},

	start: function() {
		if(netdata.options.DEBUG === true) this.debug('started, services: ' + netdata.stringify(this.services));

		if(this.services.length === 0) {
			this.disableNodePlugin();
			process.exit(1);
		}
		else this.runAllServices();
	},

	// disable the whole node.js plugin
	disableNodePlugin: function() {
		this.send('DISABLE');
		process.exit(1);
	},

	requestFromParams: function(protocol, hostname, port, path, method) {
		return {
			protocol: protocol,
			hostname: hostname,
			port: port,
			path: path,
			//family: 4,
			method: method,
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Connection': 'keep-alive'
			},
			agent: new http.Agent({
				keepAlive: true,
				keepAliveMsecs: netdata.options.update_every * 1000,
				maxSockets: 2, // it must be 2 to work
				maxFreeSockets: 1
			})
		};
	},

	requestFromURL: function(a_url) {
		var u = url.parse(a_url);
		return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET');
	},

	configure: function(module, config, callback) {
		if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...');

		module.active = 0;
		module.update_every = this.options.update_every;

		if(typeof config.update_every !== 'undefined')
			module.update_every = config.update_every;

		module.enable_autodetect = (config.enable_autodetect)?true:false;

		if(typeof(callback) === 'function')
			module.configure_callback = callback;
		else
			module.configure_callback = null;

		var added = module.configure(config);

		if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.');

		if(module.configure_callback !== null && added === 0) {
			if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().');
			module.configure_callback = null;
			callback();
		}

		return added;
	}
};

if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from: ' + __filename);
module.exports = netdata;
