"use strict";

var cluster = require('cluster');
var log4js = require('../log4js');

/**
 * Takes a loggingEvent object, returns string representation of it.
 */
function serializeLoggingEvent(loggingEvent) {
	// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
	// The following allows us to serialize errors correctly.
	for (var i = 0; i < loggingEvent.data.length; i++) {
		var item = loggingEvent.data[i];
		if (item && item.stack && JSON.stringify(item) === '{}') { // Validate that we really are in this case
			loggingEvent.data[i] = {stack : item.stack};
		}
	}
	return JSON.stringify(loggingEvent);
}

/**
 * Takes a string, returns an object with
 * the correct log properties.
 *
 * This method has been "borrowed" from the `multiprocess` appender 
 * by `nomiddlename` (https://github.com/nomiddlename/log4js-node/blob/master/lib/appenders/multiprocess.js)
 *
 * Apparently, node.js serializes everything to strings when using `process.send()`, 
 * so we need smart deserialization that will recreate log date and level for further processing by log4js internals.
 */
function deserializeLoggingEvent(loggingEventString) {

	var loggingEvent;
	
	try {
	
		loggingEvent = JSON.parse(loggingEventString);
		loggingEvent.startTime = new Date(loggingEvent.startTime);
		loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
		// Unwrap serialized errors
		for (var i = 0; i < loggingEvent.data.length; i++) {
			var item = loggingEvent.data[i];
			if (item && item.stack) {
				loggingEvent.data[i] = item.stack;
			}
		}

	} catch (e) {
		
		// JSON.parse failed, just log the contents probably a naughty.
		loggingEvent = {
			startTime: new Date(),
			categoryName: 'log4js',
			level: log4js.levels.ERROR,
			data: [ 'Unable to parse log:', loggingEventString ]
		};
	}
	return loggingEvent;
} 

/**
 * Creates an appender. 
 *
 * If the current process is a master (`cluster.isMaster`), then this will be a "master appender".
 * Otherwise this will be a worker appender, that just sends loggingEvents to the master process.
 *
 * If you are using this method directly, make sure to provide it with `config.actualAppenders` array 
 * of actual appender instances.
 *
 * Or better use `configure(config, options)`
 */
function createAppender(config) {

	if (cluster.isMaster) {

		var masterAppender = function(loggingEvent) {
	
			if (config.actualAppenders) {
				var size = config.actualAppenders.length;
				for(var i = 0; i < size; i++) {
			                if (!config.appenders[i].category || config.appenders[i].category === loggingEvent.categoryName) {
						// Relying on the index is not a good practice but otherwise the change would have been bigger.
						config.actualAppenders[i](loggingEvent);
                    			}
				}
			}
		}
		
		// Listen on new workers
		cluster.on('fork', function(worker) {
		
			worker.on('message', function(message) {
				if (message.type && message.type === '::log-message') {
					// console.log("master : " + cluster.isMaster + " received message: " + JSON.stringify(message.event));
					
					var loggingEvent = deserializeLoggingEvent(message.event);
					
					// Adding PID metadata
					loggingEvent.pid = worker.process.pid;
					loggingEvent.cluster = {
						master: process.pid,
						worker: worker.process.pid,
						workerId: worker.id
					};
					
					masterAppender(loggingEvent);
				}
			});
		
		});
		
		return masterAppender;
		
	} else {

		return function(loggingEvent) {
			// If inside the worker process, then send the logger event to master.
			if (cluster.isWorker) {
				// console.log("worker " + cluster.worker.id + " is sending message");
				process.send({ type: '::log-message', event: serializeLoggingEvent(loggingEvent)});
			}
		}
	}
}

function configure(config, options) {

	if (config.appenders && cluster.isMaster) {
	
		var size = config.appenders.length;
		config.actualAppenders = new Array(size);
	
		for(var i = 0; i < size; i++) {
		
			log4js.loadAppender(config.appenders[i].type);
			config.actualAppenders[i] = log4js.appenderMakers[config.appenders[i].type](config.appenders[i], options);
		
		}
	}
	
	return createAppender(config);
}

exports.appender = createAppender;
exports.configure = configure; 
