/*
 *
 *   Copyright 2016 RIFT.IO Inc
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

/**
 * Main skyquake module.
 * @module skyquake
 * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
 */

// Standard library imports for forking
var cluster = require("cluster");
var cpu = require('os').cpus().length;
var clusteredLaunch = process.env.CLUSTER_SUPPORT || false;
var constants = require('./framework/core/api_utils/constants');
// Uncomment for Replay support
// const Replay  = require('replay');
var freePorts = [];
for (var i = 0; i < constants.SOCKET_POOL_LENGTH; i++) {
	freePorts[i] = constants.SOCKET_BASE_PORT + i;
};


if (cluster.isMaster && clusteredLaunch) {
    console.log(cpu, 'CPUs found');
    for (var i = 0; i < cpu; i ++) {
    	var worker = cluster.fork();
    	worker.on('message', function(msg) {
	    	if (msg && msg.getPort) {
				worker.send({
					port: freePorts.shift()
				});
				console.log('freePorts after shift for worker', this.process.pid, ':', freePorts);
			} else if (msg && msg.freePort) {
				freePorts.unshift(msg.port);
				console.log('freePorts after unshift of', msg.port, 'for worker', this.process.pid, ':', freePorts);
			}
	    });
    }

    cluster.on('online', function(worker) {
        console.log("Worker Started pid : " + worker.process.pid);
    });
    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' stopped');
    });
} else {
	// Standard library imports
	var argv = require('minimist')(process.argv.slice(2));
	var pid = process.pid;
	var fs = require('fs');
	var https = require('https');
	var http = require('http');
	var express = require('express');
	var session = require('express-session');
	var cors = require('cors');
	var bodyParser = require('body-parser');
	var _ = require('lodash');
	var reload = require('require-reload')(require);
	var Sockets = require('./framework/core/api_utils/sockets.js');

	require('require-json');

	// SSL related configuration bootstrap
	var httpServer = null;
	var secureHttpServer = null;

	var httpsConfigured = false;

	var sslOptions = null;

	var apiServer = argv['api-server'] ? argv['api-server'] : 'localhost';
	var uploadServer = argv['upload-server'] ? argv['upload-server'] : null;

	try {
		if (argv['enable-https']) {
			var keyFilePath = argv['keyfile-path'];
			var certFilePath = argv['certfile-path'];

			sslOptions = {
				key: fs.readFileSync(keyFilePath),
		    	cert: fs.readFileSync(certFilePath)
			};

			httpsConfigured = true;
		}
	} catch (e) {
		console.log('HTTPS enabled but file paths missing/incorrect');
		process.exit(code = -1);
	}

	var app = express();

	app.use(session({
	  secret: 'ritio rocks',
	  resave: true,
	  saveUninitialized: true
	}));
	app.use(bodyParser.json());
	app.use(cors());
	app.use(bodyParser.urlencoded({
		extended: true
	}));

	var socketManager = new Sockets();
	var socketConfig = {
		httpsConfigured: httpsConfigured
	};

	if (httpsConfigured) {
		socketConfig.sslOptions = sslOptions;
	}

	// Rift framework imports
	var constants = require('./framework/core/api_utils/constants');
	var skyquakeEmitter = require('./framework/core/modules/skyquakeEmitter');
	var navigation_routes = require('./framework/core/modules/routes/navigation');
	var socket_routes = require('./framework/core/modules/routes/sockets');
	var restconf_routes = require('./framework/core/modules/routes/restconf');
	var inactivity_routes = require('./framework/core/modules/routes/inactivity');
	var descriptor_routes = require('./framework/core/modules/routes/descriptorModel');
	var configuration_routes = require('./framework/core/modules/routes/configuration');
	var configurationAPI = require('./framework/core/modules/api/configuration');
	/**
	 * Processing when a plugin is added or modified
	 * @param {string} plugin_name - Name of the plugin
	 */
	function onPluginAdded(plugin_name) {
		// Load plugin config
		var plugin_config = reload('./plugins/' + plugin_name + '/config.json');

		// Load all app's views
		app.use('/' + plugin_name, express.static('./plugins/' + plugin_name + '/' + plugin_config.root));

		// Load all app's routes
		app.use('/' + plugin_name, require('./plugins/' + plugin_name + '/routes'));

		// Publish navigation links
		if (plugin_config.routes && _.isArray(plugin_config.routes)) {
			skyquakeEmitter.emit('config_discoverer.navigation_discovered', plugin_name, plugin_config);
		}

	}

	/**
	 * Start listening on a port
	 * @param {string} port - Port to listen on
	 * @param {object} httpServer - httpServer created with http(s).createServer
	 */
	function startListening(port, httpServer) {
		var server = httpServer.listen(port, function () {
			var host = server.address().address;

			var port = server.address().port;

			console.log('Express server listening on port', port);
		});
		return server;
	}

	/**
	 * Initialize skyquake
	 */
	function init() {
		skyquakeEmitter.on('plugin_discoverer.plugin_discovered', onPluginAdded);
		skyquakeEmitter.on('plugin_discoverer.plugin_updated', onPluginAdded);
	}

	/**
	 * Configure skyquake
	 */
	function config() {
		// Conigure any globals
		process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;

		// Configure navigation router
		app.use(navigation_routes);

		// Configure restconf router
		app.use(restconf_routes);

		//Configure inactivity route(s)
		app.use(inactivity_routes);

		// Configure global config with ssl enabled/disabled
		var globalConfig = {
			ssl_enabled: httpsConfigured,
			api_server: apiServer
		};

		if (uploadServer) {
			globalConfig.upload_server = uploadServer;
		}

		configurationAPI.globalConfiguration.update(globalConfig);

		// Configure configuration route(s)
		app.use(configuration_routes);

		//Configure descriptor route(s)
		app.use(descriptor_routes);

		// app.get('/testme', function(req, res) {
		// 	res.sendFile(__dirname + '/index.html');
		// });

		// Configure HTTP/HTTPS server and populate socketConfig.
		if (httpsConfigured) {
			console.log('HTTPS configured. Will create 2 servers');
			secureHttpServer = https.createServer(sslOptions, app);
			// Add redirection on SERVER_PORT
			httpServer = http.createServer(function(req, res) {
				var host = req.headers['host'];
				host = host.replace(/:\d+$/, ":" + constants.SECURE_SERVER_PORT);

				res.writeHead(301, { "Location": "https://" + host + req.url });
    			res.end();
			});

			socketConfig.httpServer = secureHttpServer;
		} else {
			httpServer = http.createServer(app);
			socketConfig.httpServer = httpServer;
		}

		// Configure socket manager
		socketManager.configure(socketConfig);

		// Configure socket router
		socket_routes.routes(socketManager);
		app.use(socket_routes.router);

		// Serve multiplex-client
		app.get('/multiplex-client', function(req, res) {
			res.sendFile(__dirname + '/node_modules/websocket-multiplex/multiplex_client.js');
		});
	}

	/**
	 * Run skyquake functionality
	 */
	function run() {

		// Start plugin_discoverer
		var navigation_manager = require('./framework/core/modules/navigation_manager');
		var plugin_discoverer = require('./framework/core/modules/plugin_discoverer');

		// Initialize asynchronous modules
		navigation_manager.init();
		plugin_discoverer.init();

		// Configure asynchronous modules
		navigation_manager.config()
		plugin_discoverer.config({
			plugins_path: './plugins'
		});

		// Run asynchronous modules
		navigation_manager.run();
		plugin_discoverer.run();


		// Server start
		if (httpsConfigured) {
			console.log('HTTPS configured. Will start 2 servers');
			// Start listening on SECURE_SERVER_PORT (8443)
			var secureServer = startListening(constants.SECURE_SERVER_PORT, secureHttpServer);
		}
		// Start listening on SERVER_PORT (8000)
		var server = startListening(constants.SERVER_PORT, httpServer);

	}

	init();

	config();

	run();
}
