/**
 * pkg-builder.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 os = require('os');
var fs = require('fs');
var extfs = require('fs-extra');
var path = require('path');
var async = require('async');

var utils = require('../../lib/utils.js');
var Process = require('../dibs.core/process.js');
var Zip = require('../dibs.core/zip.js');
var DError = require('../../core/exception.js');

module.exports.build = build;


function build(options, srcPath, monitor, callback) {
    var tempDir = null;
    var pkgInfo = null;

    async.waterfall([
        // get pkg info
        function (cb) {
            monitor.updateProgress('Checking package information...');
            readPackageInfo(srcPath, cb);
        },
        function (pInfo, cb) {
            pkgInfo = pInfo;
            monitor.updateProgress('Creating temporary package install directory...');
            utils.genTemp(cb);
        },
        // execute build script
        function (temp, cb) {
            tempDir = temp;
            monitor.updateProgress('Executing build script...');
            executeBuildScript(pkgInfo, srcPath, tempDir, monitor, cb);
        },
        // copy package info file
        function (cb) {
            monitor.updateProgress('Copying install/remove scripts...');
            installPackageInfo(pkgInfo, srcPath, tempDir, monitor, cb);
        },
        // copy install/remove script
        function (cb) {
            monitor.updateProgress('Copying install/remove scripts...');
            installScriptFiles(pkgInfo, srcPath, tempDir, monitor, cb);
        },
        // packaging
        function (cb) {
            monitor.updateProgress('Creating package/plugin file...');
            createPackageFile(pkgInfo, srcPath, tempDir, monitor, cb);
        }
    ], function (err) {
        if (tempDir) {
            utils.removePathIfExist(tempDir, function (err1) {
                if (err1) {
                    monitor.updateProgress('Removing temporary package directory failed!');
                }
                callback(err);
            });
        } else {
            callback(err);
        }
    });
}


function readPackageInfo(srcPath, callback) {
    var info = null;

    if (fs.existsSync(path.join(srcPath, 'package.json'))) {
        info = extfs.readJsonSync(path.join(srcPath, 'package.json'));
        callback(null, {
            isPlugin: false,
            name: info.name,
            version: info.version
        });
    } else if (fs.existsSync(path.join(srcPath, 'plugin.json'))) {
        info = extfs.readJsonSync(path.join(srcPath, 'plugin.json'));
        callback(null, {
            isPlugin: true,
            name: info.name,
            version: info.version
        });
    } else {
        callback(new DError('DPKG001'), null);
    }
}


function executeBuildScript(pkgInfo, srcPath, installPath, monitor, callback) {
    var runFile = null;
    if (pkgInfo.isPlugin) {
        if (os.platform() === 'win32') {
            runFile = path.join(srcPath, 'plugin.build.bat');
        } else {
            runFile = path.join(srcPath, 'plugin.build.sh');
        }
    } else {
        if (os.platform() === 'win32') {
            runFile = path.join(srcPath, 'package.build.bat');
        } else {
            runFile = path.join(srcPath, 'package.build.sh');
        }
    }

    // execute
    async.waterfall([
        // run
        function (cb) {
            var env = process.env;
            env.SRCDIR = srcPath;
            env.INSTALLDIR = installPath;
            env.VERSION = pkgInfo.version;
            var cmd = null;
            var cmdArgs = null;
            if (os.platform() === 'win32') {
                cmd = 'cmd.exe';
                cmdArgs = ['/C', utils.path2string(runFile)];
            } else {
                cmd = 'bash';
                cmdArgs = ['-c', utils.path2string(runFile)];
            }
            var script = Process.create(cmd,
                cmdArgs,
                {
                    cwd: srcPath,
                    env: env
                }, {
                    onStdout: function (line) {
                        monitor.updateProgress(line);
                    },
                    onStderr: function (line) {
                        monitor.updateProgress(line);
                    },
                    onExit: function (code) {
                        if (code !== 0) {
                            monitor.updateProgress({
                                log: 'Executing script failed with code! (' + code + ')',
                                logType: 'error'
                            });
                            var error = new DError('DPKG002');
                            cb(error);
                        } else {
                            monitor.updateProgress('Executing script succeeded!');
                            cb(null);
                        }
                    }
                });
        }], function (err) {
        callback(err);
    });
}


function installPackageInfo(pkgInfo, srcPath, installPath, monitor, callback) {
    var pkgInfoPath = null;
    var destPkgInfoPath = null;

    if (!pkgInfo.isPlugin) {
        pkgInfoPath = path.join(srcPath, 'package.json');
        destPkgInfoPath = path.join(installPath, 'package.json');
    } else {
        pkgInfoPath = path.join(srcPath, 'plugin.json');
        destPkgInfoPath = path.join(installPath, 'plugin.json');
    }

    extfs.copy(pkgInfoPath, destPkgInfoPath, callback);
}


function installScriptFiles(pkgInfo, srcPath, installPath, monitor, callback) {

    async.series([
        // copy install script if exists
        function (cb) {
            copyInstallScriptFiles(pkgInfo, srcPath, installPath, cb);
        },
        function (cb) {
            copyRemoveScriptFiles(pkgInfo, srcPath, installPath, cb);
        }], function () {
        callback(null);
    });
}


function copyInstallScriptFiles(pkgInfo, srcPath, installPath, callback) {
    var packageType = pkgInfo.isPlugin ? 'plugin' : 'package';

    async.waterfall([
        function (cb) {
            findScriptFiles(packageType, 'install', srcPath, cb);
        },
        function (scripts, cb) {
            copyScriptFiles(scripts, installPath, cb);
        }], function (err) {
        callback(err);
    });
}


function copyRemoveScriptFiles(pkgInfo, srcPath, installPath, callback) {
    var packageType = pkgInfo.isPlugin ? 'plugin' : 'package';

    async.waterfall([
        function (cb) {
            findScriptFiles(packageType, 'remove', srcPath, cb);
        },
        function (scripts, cb) {
            copyScriptFiles(scripts, installPath, cb);
        }], function (err) {
        callback(err);
    });
}


function findScriptFiles(packageType, scriptType, workDir, callback) {
    var candidates = [];

    // check os name
    candidates.push(path.join(workDir, packageType + '.' + scriptType + '.sh'));
    candidates.push(path.join(workDir, packageType + '.' + scriptType + '.bat'));

    // get existing files
    async.filterSeries(candidates, fs.exists,
        function (results) {
            callback(null, results);
        });
}


function copyScriptFiles(scripts, installDir, callback) {
    async.eachSeries(scripts,
        function (scriptPath, cb) {
            var destScriptPath = path.join(installDir, path.basename(scriptPath).split('.').slice(1).join('.'));

            async.waterfall([
                function (cb1) {
                    extfs.copy(scriptPath, destScriptPath, cb1);
                },
                function (cb1) {
                    fs.chmod(destScriptPath, '0755', cb1);
                }
            ], cb);
        }, function (err) {
            callback(err);
        });
}


function createPackageFile(pkgInfo, srcPath, installPath, monitor, callback) {
    async.waterfall([
        function (cb) {
            var zipFile = pkgInfo.name + '_' + pkgInfo.version + '.zip';
            var targetFile = path.join(srcPath, zipFile);

            monitor.updateProgress('Creating package file ... ' + zipFile);
            var zip = Zip.compress(targetFile, installPath,
                {
                    onStdout: function (line) {
                        monitor.updateProgress(line);
                    },
                    onStderr: function (line) {
                        monitor.updateProgress({
                            log: line,
                            logType: 'error'
                        });
                    },
                    onExit: function (code) {
                        if (code !== 0) {
                            var error = new Error('Zipping process exited with code ' + code);
                            cb(error);
                        } else {
                            monitor.updateProgress('Zipping package succeeded!');
                            cb(null);
                        }
                    }
                });
        }
    ], function (err) {
        callback(err);
    });
}
