/**
 * server.js
 * Copyright (c) 2000 - 2017 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * 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.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

var path = require('path');
var async = require('async');
var fs = require('fs');
var extfs = require('fs-extra');
var synchronized = require('synchronized');
var spawn = require('child_process').spawn;
var os = require('os');

var dibs = require('../../core/dibs.js');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs');
var utils = require('../../lib/utils.js');
var DError = require('../../core/exception.js');
var FileSystem = require('../dibs.core/filesystem.js');
var Builder = require('../dibs.core.server/pkg-builder.js');
var Installer = require('../dibs.core.server/pkg-installer.js');
var Monitor = require('../../lib/monitor.js');


module.exports.createServer = function (sid) {
    AgentServer.prototype = dibs.BaseServer.createServer(sid, 'agent');
    return new AgentServer();
};


function AgentServer() {
    var self = this;
    this.name = 'agent';

    this.getDefaultConfiguration = function (baseConfig) {
        return baseConfig;
    };


    this.OnServerStarted = function (callback) {
        // add myself into server list
        dibs.addServer(self);

        async.series([
            function (cb) {
                // set sid for master-server
                var agentIp;
                var agentPort;
                var configIp = self.config.get('host-ip');
                var configPort = self.config.get('host-port');

                if (configIp) {
                    agentIp = configIp;
                } else {
                    agentIp = utils.getHostIp();
                }

                if (configPort) {
                    agentPort = configPort;
                } else {
                    agentPort = self.port;
                }

                dibs.log.info('change server id ' + dibs.thisServer.id + ' to ' + agentIp + '_agent_' + agentPort);
                dibs.thisServer.id = agentIp + '_agent_' + agentPort;
                cb(null);
            },
            function (cb) {
                dibs.log.info('Opening DFS repository...');
                dfs.openRepository(self.config.get('dfs-path'),
                    self.config.get('dfs-size'),
                    self.startOptions.cleanStart,
                    self.port + 1,
                    function (err) {
                        if (err) {
                            dibs.log.info('DFS repository load failed');
                        } else {
                            dibs.log.info('DFS repository loaded');
                        }
                        cb(err);
                    });
            },
            function (cb) {
                self.addConnectionHandler('dfs', dfs.server);
                dfs.connect(function (err) {
                    if (err) {
                        dibs.log.error('DFS connect failed');
                    } else {
                        dibs.log.info('DFS connected');
                    }
                    cb(err);
                });
            }
        ], function (err) {
            callback(err);
        });
    };

    this.OnServerTerminating = function (callback) {
        self.log.info('close dfs connect');
        // close dfs repo
        if (dfs.isOpened()) {
            dfs.closeRepository();
        }

        callback(null);
    };


    this.innerGetDibsPath = function (mIp, mPort) {
        return path.resolve(__dirname, '..', '..', 'workspace', mIp + mPort);
    };


    this.innerIsDibsInstall = function (mIp, mPort, callback) {
        var dibsPath = this.innerGetDibsPath(mIp, mPort);
        var fileList = [];
        fileList.push(path.join(dibsPath, 'app.js'));
        fileList.push(path.join(dibsPath, 'package.json'));
        fileList.push(path.join(dibsPath, 'core', 'dibs.js'));
        fileList.push(path.join(dibsPath, 'plugins', 'dibs.core', 'base-remote.js'));
        fileList.push(path.join(dibsPath, 'web-plugins', 'dibs.web.core', 'common-page.js'));

        async.eachSeries(fileList, function (file, cb) {
            fs.exists(file, function (exists) {
                if (exists) {
                    cb(null);
                } else {
                    cb(new Error(file + 'file does not exist'));
                }
            });
        }, function (err) {
            if (err) {
                dibs.thisServer.log.info(err);
                callback(null, false);
            } else {
                callback(null, true);
            }
        });
    };


    this.innerDibsInstall = function (mIp, mPort, version, binPath, callback) {
        var dibsPath = this.innerGetDibsPath(mIp, mPort);
        var realPath = path.join(self.config.get('dfs-path'), 'local', binPath);

        // install dibs
        async.series([
            // create directory if not exist
            function (cb) {
                if (!fs.existsSync(dibsPath)) {
                    dibs.thisServer.log.info('Creating DIBS directory...');
                    extfs.mkdirp(dibsPath, cb);
                } else if (!fs.existsSync(path.join(dibsPath, '.info'))) {
                    dibs.thisServer.log.info('Removing DIBS directory...');
                    FileSystem.remove(dibsPath, function (err) {
                        if (!err) {
                            dibs.thisServer.log.info('Creating DIBS directory...');
                            extfs.mkdirp(dibsPath, cb);
                        } else {
                            cb(err);
                        }
                    });
                } else {
                    cb(null);
                }
            },
            function (cb) {
                var monitor = new Monitor({
                    onProgress: function (info, cb) {
                        dibs.thisServer.log.info(info.log);
                    }
                });
                dibs.thisServer.log.info('Installing DIBS package...');
                Installer.installPackages([realPath], dibsPath, {}, monitor, cb);
            }
        ], function (err) {
            callback(err);
        });
    };


    this.getBaseConfig = function (config, callback) {
        self.getRemoteBaseConfig(config, callback);
    };

    this.innerGetBaseConfig = function (config, callback) {
        callback(null, dibs.config.getBaseConfig(config));
    };

    this.createServerConfig = function (server, config, callback) {
        self.generateServerConfig(server, config, callback);
    };

    this.innerGenerateServerConfig = function (server, config, callback) {
        dibs.config.createServerConfig(server, config);
        callback(null);
    };

    this.innerNpmInstall = function (mIp, mPort, callback) {
        var isWin = false;
        var dibsPath = this.innerGetDibsPath(mIp, mPort);
        var npm = null;

        dibs.thisServer.log.info('Installing NPM...' + dibsPath);
        if (os.platform() === 'win32') {
            isWin = true;
        }
        if (isWin) {
            npm = spawn('cmd', ['/c', 'npm', 'install'], { cwd: dibsPath });
        } else {
            npm = spawn('npm', ['install'], { cwd: dibsPath });
        }
        npm.stdout.on('data', function (data) {
            process.stdout.write('    ' + data);
            dibs.log.info('[' + mIp + ':' + mPort + '] ' + data.toString('utf-8'));
        });
        npm.stderr.on('data', function (data) {
            process.stderr.write('    ' + data);
            dibs.log.error('[' + mIp + ':' + mPort + '] ' + data.toString('utf-8'));
        });
        npm.on('exit', function (code) {
            if (code !== 0) {
                dibs.thisServer.log.info('Installing NPM... failed');
                callback(new DError('AGENT008', { code: code }));
            } else {
                dibs.thisServer.log.info('Installing NPM... done');
                callback(null);
            }
        });
    };


    this.innerExecServer = function (mIp, mPort, id, type, port, startOptions, callback) {
        var dibsPath = this.innerGetDibsPath(mIp, mPort);

        async.waterfall([
            function (cb) {
                dibs.thisServer.log.info('Checking DIBS installation... ' + self.host + ':' + self.port);
                self.isDibsInstall(mIp, mPort, cb);
            },
            function (isInstalled, cb) {
                if (isInstalled) {
                    dibs.thisServer.log.info('Checking DIBS installation... done');
                    cb(null);
                } else {
                    dibs.thisServer.log.info('DIBS is not installed');
                    cb(new DError('AGENT009', {}));
                }
            }
        ], function (err) {
            if (err) {
                callback(err);
            } else {
                var appPath = path.join(dibsPath, 'app.js');
                var child;
                var innerError = null;
                var masterAddr = startOptions.masterAddr;
                var cbCalled = false;

                var nodeOptions = [];
                if (startOptions.debug) {
                    var debugPort = Number(port) + 1000;
                    nodeOptions.push('--stack-size=1024');
                    nodeOptions.push('--debug=' + debugPort);
                    dibs.thisServer.log.info('  -> ' + id + ' debug port: ' + debugPort);
                } else {
                    nodeOptions = ['--stack-size=1024'];
                }

                if (startOptions.cleanStart) {
                    nodeOptions = nodeOptions.concat([appPath, '-i', id, '-t', type, '-p', port, '--cleanStart', '--masterAddr', masterAddr]);
                } else {
                    nodeOptions = nodeOptions.concat([appPath, '-i', id, '-t', type, '-p', port, '--masterAddr', masterAddr]);
                }

                dibs.thisServer.log.info('spawn child proc:' + nodeOptions);
                child = spawn('node', nodeOptions);

                child.stdout.on('data', function (out) {
                    dibs.log.info('[' + id + '] ' + out.toString('utf-8'));

                    if (out.toString().indexOf('Server is LISTENING!') !== -1) {
                        cbCalled = true;
                        callback(null);
                    } else if (out.toString().indexOf('WARN: Shutting down from  SIGINT (Crtl-C)') !== -1) {
                        console.log('Terminating server: ' + id);
                        dibs.log.info('Terminating server: ' + id);
                    } else {
                        console.log('[' + id + '] ' + out);
                    }
                });

                child.stderr.on('data', function (err) {
                    dibs.log.error('[' + id + '] ' + err.toString('utf-8'));
                    if (/^execvp\(\)/.test(err)) {
                        callback(new Error('Failed to start child process:' + id));
                    }
                    innerError = new Error(err.toString().split(os.EOL).join(''));
                    console.error('[' + id + '] ' + err);
                });

                child.on('error', function (err) {
                    if (!cbCalled) {
                        cbCalled = true;
                        if (innerError) {
                            callback(new DError('AGENT001', { id: id, code: 255 }, innerError));
                        } else {
                            callback(new DError('AGENT001', { id: id, code: 255 }, err));
                        }
                    } else {
                        dibs.log.warn('[' + id + '] Spawned process error! => ' + err.toString());
                    }
                });

                child.on('close', function (code) {
                    if (code !== 0) {
                        if (!cbCalled) {
                            cbCalled = true;
                            if (innerError) {
                                callback(new DError('AGENT001', { id: id, code: code }, innerError));
                            } else {
                                callback(new DError('AGENT001', { id: id, code: code }));
                            }
                        } else {
                            dibs.log.warn('[' + id + '] Spawned process closed! (' + code + ')');
                        }
                    }
                });
            }
        });
    };

    //
    // CLIENT API
    //

    this.removeServerResource = function (server, callback) {
        if (server.config && server.config.getConfigData) {
            var config = server.config.getConfigData();
            var resourcePath = path.join(config.config_root_dir, config.id);
            utils.removePathIfExist(resourcePath, callback);
        } else {
            callback(new Error('server ' + server.name + 'config information is not exist'));
        }
    };

    this.connectAgentServer = function (server, master, callback) {
        async.series([
            function (cb) {
                dibs.thisServer.log.info('Connecting to DIBS agent... ' + self.host + ':' + self.port);
                connectToAgentServer(cb);
            },
            function (cb) {
                updateDIBS(server, master, cb);
            }
        ],
            function (err) {
                callback(err);
            });
    };

    // launch a server on remote site
    this.launchServer = function (server, master, startOptions, callback) {
        dibs.thisServer.log.info('Executing DIBS using agent... ' + self.host + ':' + self.port);
        self.execServer(master.host, master.port, server.id, server.type,
            server.port, startOptions, callback);
    };


    function connectToAgentServer(callback) {
        self.__getStatus(function (err, status) {
            if (err) {
                callback(new DError('AGENT002', { host: self.host, port: self.port }, err));
            } else {
                if (status.status !== 'RUNNING') {
                    callback(new DError('AGENT002', { host: self.host, port: self.port }, new Error('Not RUNNING status')));
                } else {
                    callback(null);
                }
            }
        });
    }


    function updateDIBS(server, master, callback) {
        var isInstall = false;
        var needUpgrade = false;
        var masterServerIp = master.host;
        var masterServerPort = master.port;
        var remoteBinPath;
        var dibsDir = path.resolve(__dirname, '..', '..');
        var dibsVersion = extfs.readJsonSync(path.join(dibsDir, 'package.json')).version;
        var dibsBinaryPath = path.join(dibsDir, 'dibs_' + dibsVersion + '.zip');

        async.waterfall([
            // check if DIBS is installed
            function (cb) {
                dibs.thisServer.log.info('Checking DIBS installation... ' + self.host + ':' + self.port);
                self.isDibsInstall(masterServerIp, masterServerPort, cb);
            },
            // get DIBS version if installed
            function (isInstalled, cb) {
                isInstall = isInstalled;
                if (isInstall) {
                    dibs.thisServer.log.info('Checking DIBS version...' + self.host + ':' + self.port);
                    self.getDibsVersion(masterServerIp, masterServerPort, function (err, rVersion) {
                        if (!err && utils.compareVersion(dibsVersion, rVersion) > 0) {
                            needUpgrade = true;
                        }
                        cb(err);
                    });
                } else {
                    cb(null);
                }
            },
            // create dibs package if needed
            function (cb) {
                synchronized('createDIBS', function (cb1) {
                    if (!fs.existsSync(dibsBinaryPath) && (!isInstall || needUpgrade)) {
                        dibs.thisServer.log.info('Creating DIBS package file...');
                        var monitor = new Monitor({
                            onProgress: function (info, cb2) {
                                // do nothing
                            }
                        });
                        Builder.build({}, dibsDir, monitor, cb1);
                    } else {
                        cb1(null);
                    }
                }, function (err) {
                    cb(err);
                });
            },
            // transfer package file to remote
            function (cb) {
                if (!isInstall || needUpgrade) {
                    dibs.thisServer.log.info('Transferring DIBS package file...' + self.host + ':' + self.port);
                    dfs.addFileToServer(path.join(masterServerIp + masterServerPort, path.basename(dibsBinaryPath)),
                        dibsBinaryPath, self,
                        function (err, dfsPath) {
                            remoteBinPath = dfsPath;
                            if (err && err.errno === 'DFS002') {
                                dibs.thisServer.log.info('OK - Already exists!');
                                cb(null);
                            } else {
                                cb(err);
                            }
                        });
                } else {
                    cb(null);
                }
            },
            // install DIBS on remote site
            function (cb) {
                if (!isInstall || needUpgrade) {
                    dibs.thisServer.log.info('Installing DIBS on remote site...' + self.host + ':' + self.port);
                    self.dibsInstall(masterServerIp, masterServerPort, dibsVersion, remoteBinPath, cb);
                } else {
                    cb(null);
                }
            },
            function (cb) {
                if (!isInstall || needUpgrade) {
                    dibs.thisServer.log.info('Installing DIBS NPM...' + self.host + ':' + self.port);
                    self.npmInstall(masterServerIp, masterServerPort, cb);
                } else {
                    cb(null);
                }
            }

        ], function (err) {
            callback(err);
        });
    }
}

/**
 * @function createServer
 * @param {string} sid - sid
 * @returns {string}
 * @memberOf module:servers/agent/server
 */


/**
 * @module servers/agent/server
 */
