/**
 * deploy-action-project.js
 * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@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 async = require('async');
var fs = require('fs');
var path = require('path');
var _ = require('underscore');

var dibs = require('../../core/dibs.js');
var DError = require('../../core/exception.js');
var Job = require('../dibs.model.common/job.js');
var Package = require('../org.tizen.common/package.js');
var Installer = require('../org.tizen.projects/installer.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');

/**
 * Tizen WDK deploy action project plugin
 * @module models/tizen-deploy-project/project
 */

module.exports.createJob = createJob;
module.exports.initializeJob = initializeJob;
module.exports.executeJob = executeJob;
module.exports.checkMutualExclusiveJob = checkMutualExclusiveJob;
module.exports.checkJobPrivilege = checkJobPrivilege;

/**
 * Tizen WDK deploy action job
 * @constructor
 * @augments module:models/job
 * @param {module:models/job~Job} baseJob - base job object
 */

function TizenWSDKDeployActionJob(baseJob) {
    Job.copy(this, baseJob);

    this.compatJob = null;
    this.snapshot = null;
    this.options.packages = [];

    this.initEnvironments = dibs.projectTypes['Tizen-WSDK-Deploy-Action'].environments;
}

/**
 * Create Tizen WDK deploy action job instance
 * @function createJob
 * @param {string} userEmail - user email which creates this job
 * @param {string} distName - distribution name
 * @param {string} prjName - project name
 * @param {string} prjType - project type
 * @param {module:models/job.options} options - job options
 * @param {module:lib/utils.callback_error} callback - callback(error)
 * @memberOf module:models/tizen-project/project
 */

function createJob(userEmail, distName, prjName, prjType, environmentName, parentId, distType, options, callback) {
    Job.create(userEmail, distName, distType, prjName, prjType, environmentName, parentId, null, options,
        function (err, newJob) {
            var deployActionJob = null;
            if (err) {
                dibs.log.error(err);
            } else {
                deployActionJob = new TizenWSDKDeployActionJob(newJob);
                if (options.BUILDER_ID) {
                    dibs.log.info('set builderId ' + options.BUILDER_ID + ' into TizenWSDKDeployActionJob');
                    deployActionJob.assignedBuilderId = options.BUILDER_ID;
                }
            }
            callback(err, deployActionJob);
        });
}

/**
 * Initialize Tizen WSDK deploy action job
 * @function initializeJob
 * @param {module:models/job~Job} job - job
 * @param {string} jobWorkPath - jobWorkPath
 * @param {module:core/base-server.BaseServer} server - server
 * @param {module:lib/utils.callback_error} callback - callback(error)
 * @memberOf module:models/tizen-project/project
 */
function initializeJob(job, options, callback) {
    if (job.status === 'INITIALIZED') {
        return callback(new DError('WSDKDEPLOY006', { jobId: job.id }), job);
    }

    callback(null, job);
}


/**
 * Execute Tizen WSDK deploy action job
 * @function executeJob
 * @param {module:models/job~Job} job - job
 * @param {string} jobWorkPath - jobWorkPath
 * @param {module:core/base-server.BaseServer} server - server
 * @param {module:lib/utils.callback_error} callback - callback(error)
 * @memberOf module:models/tizen-project/project
 */
function executeJob(job, options, callback) {
    var jobWorkPath = options.jobWorkPath;
    var monitor = options.monitor;

    if (!job.options) {
        return callback(new DError('WSDKDEPLOY009', {jobId: job.id}), job);
    }

    var deployInfo = job.options.DEPLOY_SERVER;
    if (!deployInfo) {
        return callback(new DError('WSDKDEPLOY009', {jobId: job.id}), job);
    }

    var envJson = deployInfo.deployEnv;
    if (!envJson) {
        return callback(new DError('WSDKDEPLOY018'), job);
    }

    if (!envJson.env || !envJson.env.deployPath) {
        return callback(new DError('WSDKDEPLOY016'), job);
    }

    var installOptions = {};

    dibs.log.info('job options INSTALL: ' + job.options.INSTALL);
    if (job.options.INSTALL === 'FALSE') {
        uninstallDeployActions(job, deployInfo.packageName, envJson, monitor, function (err) {
            callback(err, job);
        });
    } else {
        installDeployActions(job, jobWorkPath, installOptions, deployInfo.packageName, envJson, monitor, function (err) {
            callback(err, job);
        });
    }
}

function checkMutualExclusiveJob() {
    return ;
}


function checkJobPrivilege(user, groups, distribution, project, jobOptions, callback) {
    callback(null);
}


function downloadPackage(packageName, distName, snapshotName, targetOS, jobWorkPath, callback) {
    var repo = dibs.getServersByType('repo')[0];
    if (_.size(repo) === 0) {
        callback(new DError('WSDKDEPLOY008'));
        return;
    }

    repo.downloadPackage(packageName, {
        repoType: 'tizen',
        os: targetOS,
        distName: distName,
        snapshotName: snapshotName,
        targetDir: jobWorkPath
    }, function (err, downloadedFile) {
        if (err) {
            dibs.log.error(err);
        }
        callback(err, downloadedFile);
    });
}


function installPackage(jobId, downloadedFile, installPath, targetOs, options, monitor, callback) {
    var accInstalledPkgs = [];
    var packageInfo = null;
    async.waterfall([
        function (cb) {
            var installedPackagePath = path.join(installPath, '.info', 'installedpackage.list');
            Package.getPkgListFromFile(installedPackagePath, function (err1, installedPkgs) {
                accInstalledPkgs = installedPkgs;
                cb(null);
            });
        },
        function (cb) {
            Package.getPkgInfoFromPkgFile(downloadedFile, function (err1, pkgInfo) {
                if (!err1) {
                    packageInfo = pkgInfo;
                }
                cb(err1);
            });
        },
        function (cb) {
            // if it already exists in installed package list, then skip
            var results = accInstalledPkgs.filter(function (installedPkg) {
                return (installedPkg.name === packageInfo.name && installedPkg.os === packageInfo.os &&
                    Package.compareVersion(installedPkg.version, packageInfo.version) >= 0);
            });

            if (results.length > 0) {
                monitor.updateProgress(' - The package \'' + packageInfo.name + '(' + packageInfo.os + ')\' is already installed!');
                cb(null, false);
            } else {
                cb(null, true);
            }
        },
        function (install, cb) {
            if (install) {
                Installer.forceInstallLocalPackage(packageInfo, downloadedFile, installPath, accInstalledPkgs, targetOs, options, monitor,
                    function (err1) {
                        if (err1) {
                            dibs.log.error(err1);
                            cb(new DError('WSDKDEPLOY010', {name: packageInfo.name, jobId: jobId}));
                        } else {
                            cb(null);
                        }
                    });
            } else {
                dibs.log.info('Skipping package installation');
                monitor.updateProgress(' - The package \'' + packageInfo.name + '(' + packageInfo.os + ')\' is skipped!');
                cb(null);
            }
        }
    ],
    function (err) {
        callback(err, packageInfo);
    });
}


function installDeployActions(job, jobWorkPath, installOptions, packageName, deployEnv, monitor, callback) {
    async.waterfall([
        function (cb) {
            var queryOptions = {
                name: null,
                repoType: 'tizen',
                distName: job.distName
            };

            if (job.options.SNAPSHOT_NAME) {
                queryOptions.name = job.options.SNAPSHOT_NAME;
            } else {
                return cb(new DError('WSDKDEPLOY017'));
            }

            monitor.updateProgress('Checking if ' + job.options.SNAPSHOT_NAME + ' snapshot is valid...');
            dibs.rpc.repo.searchSnapshots(queryOptions, function (err, snapshots) {
                if (err) {
                    cb(err);
                } else {
                    if (snapshots.length > 0) {
                        job.snapshot = snapshots[0];
                        job.snapshot.distName = job.distName;
                        cb(null);
                    } else {
                        cb(new DError('WSDKDEPLOY002', { name: queryOptions.name }));
                    }
                }
            });
        },
        function (cb) {
            fs.stat(deployEnv.env.packages, function (err) {
                if (err) {
                    dibs.log.info('Create a directory for packages: ' + deployEnv.env.packages);
                    fs.mkdir(deployEnv.env.packages, cb);
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            fs.stat(deployEnv.env.deployPath, function (err) {
                if (err) {
                    dibs.log.info('Create a directory for deployPath: ' + deployEnv.env.deployPath);
                    fs.mkdir(deployEnv.env.deployPath, cb);
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            var envFileName = packageName + '-env.json';
            var envFilePath = path.join(deployEnv.env.packages, envFileName);

            dibs.log.info(envFilePath);

            fs.stat(envFilePath, function (err) {
                if (err) {
                    dibs.log.info('Writing ' + envFileName + ' under ' + deployEnv.env.packages);
                    deployEnv.env.update = false;
                } else {
                    dibs.log.info('Updating \'env.update\' value into ' + envFilePath);
                    deployEnv.env.update = true;
                }
                fs.writeFile(envFilePath, JSON.stringify(deployEnv, null, 4), { encoding: 'utf-8' }, cb);
            });

        },
        function (cb) {
            monitor.updateProgress('Start downloading package(s) ...');

            downloadPackage(packageName, job.distName,
                job.options.SNAPSHOT_NAME, job.options.TARGET_OS, jobWorkPath,
                function (err, results) {
                    cb(err, results);
                });
        },
        function (downloadedFile, cb) {
            monitor.updateProgress('Start installing package(s) ... ' + downloadedFile);

            dibs.log.info('Start installing package(s) ... ' + downloadedFile);
            installPackage(job.id, downloadedFile, deployEnv.env.packages,
                job.options.TARGET_OS, installOptions, monitor,
                function (err, pkgInfo) {
                    if (!err) {
                        job.options.packages = pkgInfo;
                    }
                    cb(err);
                });
        },
        function (cb) {
            monitor.updateProgress('Finish installing package(s) ...');

            var envFileName = packageName + '-env.json';
            var envFilePath = path.join(deployEnv.env.packages, envFileName);

            dibs.log.info('Updating \'env.update\' value into ' + envFilePath);
            deployEnv.env.update = true;
            fs.writeFile(envFilePath, JSON.stringify(deployEnv, null, 4), { encoding: 'utf-8' }, cb);
        }
    ],
    function (err) {
        TizenCommon.stripJob(job);
        callback(err);
    });
}

function uninstallDeployActions(job, packageName, deployEnv, monitor, callback) {
    // uninstall package
    var envFileName = packageName + '-env.json';
    var envFilePath = path.join(deployEnv.env.packages, envFileName);

    async.waterfall([
        function (cb) {
            dibs.log.info('Uninstalling package(s)... ' + packageName);
            monitor.updateProgress('Uninstalling package(s)... ' + packageName, cb);
        },
        function (cb) {
            dibs.log.info('Create new file ' + envFileName);

            deployEnv.env.update = false;
            fs.writeFile(envFilePath, JSON.stringify(deployEnv, null, 4), { encoding: 'utf-8' }, cb);
        },
        function (cb) {
            dibs.log.info('package name: ' + packageName);
            Installer.uninstallSinglePackage(packageName, deployEnv.env.packages, monitor, cb);
        },
        function (cb) {
            fs.stat(envFilePath, function (err) {
                if (err) {
                    dibs.log.warn(envFilePath + ' does not exist');
                    cb(null);
                } else {
                    dibs.log.info('call unlink syscall to remove ' + envFilePath);
                    fs.unlink(envFilePath, cb);
                }
            });
        }
    ],
    function (err) {
        TizenCommon.stripJob(job);
        callback(err);
    });
}
