/**
 * binary-project.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 _ = require('underscore');

var DError = require('../../core/exception');
var dibs = require('../../core/dibs');
var Job = require('../dibs.model.common/job.js');
var Package = require('../org.tizen.common/package.js');
var Parser = require('../org.tizen.common/parser.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');
var Tutils = require('../org.tizen.common/tizen_utils');
var ReverseBuildChecker = require('./reverse-build.js');
var Snapshot = require('../org.tizen.repository/snapshot.js');
var Zip = require('../dibs.core/zip.js');


module.exports.createJob = createJob;
module.exports.initializeJob = initializeJob;
module.exports.executeJob = executeJob;
module.exports.resumeJob = resumeJob;
module.exports.pendingJobHandler = pendingJobHandler;
module.exports.checkMutualExclusiveJob = TizenCommon.checkMutualExclusiveJob;

/**
 * Tizen binary project plugin
 * @module models/tizen-binary-project/project
 */

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

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

    this.compatJob = null;
    this.snapshot = null;
    this.distribution = null;
    if (!this.options.packages) {
        this.options.packages = [];
    }

    // NOTE. Initializing/Executing on windows is very slow
    this.initEnvironments = dibs.projectTypes['Tizen-Binary'].environments
        .filter(function (e) {
            return (e.indexOf('windows') === -1);
        });
    this.execEnvironments = dibs.projectTypes['Tizen-Binary'].environments
        .filter(function (e) {
            return (e.indexOf('windows') === -1);
        });
}

/**
 * Create tizen git job instance
 * @function createJob
 * @param {string} prjName - project name
 * @param {string} prjType - project type
 * @param {string} distName - distribution name
 * @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) {
    var pkgInfo = Tutils.getInfoFromPackageFileName(path.basename(options.FILEPATH));
    var envName = pkgInfo ? pkgInfo.os : 'ubuntu-32';

    Job.create(userEmail, distName, distType, prjName, prjType, envName, parentId, null, options,
        function (err, newJob) {
            if (err) {
                callback(err, null);
            } else {
                TizenCommon.checkReleasePhase(new TizenBinaryJob(newJob), callback);
            }
        });
}

/**
 * Initialize Tizen binary job
 * @function intializeJob
 * @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) {
    /*
     *  1. state check
     */
    if ((job.status !== 'INITIALIZING')) {
        return callback(new DError('TIZENJOB008', {
            jobId: job.id
        }), job);
    }

    /*
     *  2. set variable
     */
    var jobWorkPath = options.jobWorkPath;
    var monitor = options.monitor;
    var server = dibs.getServer(options.parentServerId);

    // gloval error
    var error = null;
    // package file name
    var fileName = path.basename(job.options.FILEPATH);
    // package file path
    var pkgPath = path.join(jobWorkPath, fileName);
    // package info from format of file name
    var pInfo = Tutils.getInfoFromPackageFileName(fileName);
    // parseed pkginfo.manifest => 5. section
    var pkgs = null;


    /*
     *  3. options check
     */
    //check package info
    if (!pInfo) {
        return callback(new DError('TIZENBINJOB002', {
            name: fileName
        }), job);
    }
    // check package name
    if (pInfo.name !== job.options.PKG_NAME) {
        return callback(new DError('TIZENBINJOB003', {
            name: pInfo.name
        }), job);
    }

    async.waterfall([
        /*
         *  4. prepare target
         */
        // get binary package file
        function (cb) {
            monitor.updateProgress('Downloading package files...' + pkgPath, cb);
        },
        function (cb) {
            server.dfsGetFile(pkgPath, job.options.FILEPATH, cb);
        },
        function (cb) {
            monitor.updateProgress(' - downloading done', cb);
        },
        /*
         *  5. get infomation from target
         */
        // check pkginfo.manifest
        function (cb) {
            monitor.updateProgress('Extracting package(' + pkgPath + ')...', cb);
        },
        function (cb) {
            Package.getPkgInfoFromPkgFile(pkgPath, cb);
        },
        // check
        function (pkgInfo, cb) {
            var invalidInstallDep = false;
            pkgInfo.os = pInfo.os; // set target os
            pkgInfo.installDepList = _.map(pkgInfo.installDepList,
                function (dep) {
                    if (!dep.os) {
                        dep.os = pInfo.os;
                    } else {
                        if (dep.os !== pInfo.os) {
                            invalidInstallDep = true;
                        }
                    }
                    return dep;
                });
            if (invalidInstallDep) {
                cb(new DError('TIZENJOB015', {
                    pkgName: pkgInfo.name,
                    targetOs: pkgInfo.os,
                    depOs: pInfo.os
                }), null);
            } else {
                cb(null, pkgInfo);
            }
        },
        function (pkgInfo, cb) {
            //*** job.packages ***
            job.options.packages.push(pkgInfo);
            if (job.options.FORCE_REBUILD) {
                monitor.updateProgress('Skipped checking package information by FORCE_REBUILD option', cb);
            } else if (job.options.UPLOAD_TEMP) {
                monitor.updateProgress('Skipped checking package information by UPLOAD_TEMP option', cb);
            } else {
                monitor.updateProgress('Checking package information...');
                checkPackageInfo(job, pkgInfo, function (err) {
                    cb(err);
                });
            }
        },
        /*
         *  6. check change log
         */
        function (wcb) {
            monitor.updateProgress('Checking change-log option of distriubtion...', wcb);
        },
        function (wcb) {
            dibs.rpc.datamgr.searchDistributions({
                name: job.distName
            }, function (err, data) {
                if (err) {
                    error = new DError('TIZENJOB014', {
                        distName: job.distName
                    }, err);
                }
                wcb(error, data);
            });
        },
        function (distributions, cb) {
            job.distribution = distributions[0];
            if (job.distribution.options &&
                job.distribution.options.CHECK_CHANGE_LOG) {
                checkChangelog(pkgs, fileName, cb);
            } else {
                cb(null);
            }
        },
        /*
         *  7. check dependency list in repo
         */
        function (wcb) {
            monitor.updateProgress('Checking dependency information...', wcb);
        },
        function (wcb) {
            job.depList = TizenCommon.getDepList(job.options.packages);
            monitor.updateProgress(' - [' + _.map(job.depList, function (dep) {
                return dep.packageName + '(' + dep.os + ')';
            }).toString() + ']', wcb);
        },
        function (wcb) {
            if (job.parentId === null && job.depList.length > 0) {
                TizenCommon.checkDependencyExistInRepo(job, wcb);
            } else {
                wcb(null);
            }
        }], function (err) {
        if (err && dibs.getServersByType('messenger')[0] && job.parentId === null && job.userEmail !== 'admin@user') {
            monitor.updateProgress('sending email');
            TizenCommon.sendEmail(err, job, monitor, function (error) {
                if (error) {
                    monitor.updateProgress('sending email failed');
                    monitor.updateProgress('error: ' + error.message);
                }
                callback(err, job);
            });
        } else {
            monitor.updateProgress('ignore sending email');
            callback(err, job);
        }
    });
}

function checkChangelog(pkginfoList, packageFile, callback) {
    changelogFromPkgFile(packageFile, function (err, data) {
        if (err) {
            callback(err);
        } else {
            TizenCommon.checkChangelog(pkginfoList, data, callback);
        }
    });
}

function changelogFromPkgFile(pkgFile, callback) {
    async.waterfall([
        function (wcb) {
            Zip.unzipAFileString(pkgFile, 'changelog', function (err, data) {
                if (err && !data) {
                    var error = new DError('TIZENJOB013');
                    wcb(error);
                } else {
                    wcb(null, data);
                }
            });
        },
        function (data, wcb) {
            Parser.changelogStringParser(data, wcb);
        }
    ], callback);
}


/**
 * Execute Tizen binary job
 * - it checks binary package and upload
 * @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 jobSvc = options.jobSvc;
    var jobWorkPath = options.jobWorkPath;
    var monitor = options.monitor;

    async.waterfall([
        function (cb) {
            monitor.updateProgress('Getting the latest snapshot of repository ...');
            dibs.rpc.repo.searchSnapshots({
                name: null,
                repoType: 'tizen',
                distName: job.distName
            },
            function (err, snapshots) {
                if (!err && snapshots.length > 0) {
                    job.snapshot = snapshots[0];
                    monitor.updateProgress(' - ' + job.snapshot.name);
                    job.snapshot.distName = job.distName;
                }
                cb(err);
            });
        },
        function (cb) {
            monitor.updateProgress('Checking build options...', cb);
        },
        // check
        function (cb) {
            if (job.options.FORCE_REBUILD) {
                monitor.updateProgress('Skipped checking package information by FORCE_REBUILD option...', cb);
            } else if (job.options.UPLOAD_TEMP) {
                monitor.updateProgress('Building project using UPLOAD_TEMP option...', cb);
            } else {
                monitor.updateProgress('Checking package information...');
                checkPackageInfo(job, job.options.packages[0], cb);
            }
        },
        function (cb) {
            if (!job.options.BUILD_ONLY) {
                monitor.updateProgress('Preparing to upload result files...');
                job.resultFiles = [job.options.FILEPATH];
            }
            cb(null);
        },
        function (cb) {
            if (!job.options.BUILD_ONLY) {
                monitor.updateProgress('Collecting build information...');
                job.buildInfo = [];
                _.each(job.resultFiles, function (rpath) {
                    job.buildInfo.push({
                        rpath: rpath,
                        build: {
                            __BUILD_DISTRIBUTION: job.distName,
                            __PROJECT_TYPE: job.projectType,
                            __PROJECT_NAME: job.projectName
                        }
                    });
                });

                // save build informations for recover
                job.options.buildInfo = job.buildInfo;
            }
            cb(null);
        },
        function (cb) {
            monitor.updateProgress('Checking reverse build...');
            if (job.options.NO_REVERSE_CHECK) {
                return cb(null);
            } else {
                ReverseBuildChecker.check(job, monitor, function (err, result) {
                    updateReverseJobs(job, result, function (err1) {
                        if (err && !err1) {
                            monitor.updateProgress('WARNING! Reverse build failed!');
                            return cb(err);
                        } else if (!err && err1) {
                            monitor.updateProgress('WARNING! Job information update failed!');
                            return cb(err1);
                        } else if (err && err1) {
                            monitor.updateProgress('WARNING! Reverse build failed!');
                            monitor.updateProgress('WARNING! Job information update failed!');
                            return cb(err);
                        } else {
                            return cb(null);
                        }
                    });
                });
            }
        },
        function (wcb) {
            // set job info with changelog
            var fileName = path.basename(job.options.FILEPATH);
            var pkgPath = path.join(jobWorkPath, fileName);
            Zip.unzipAFileString(pkgPath, 'changelog', function (err, changelogString) {
                if (err) {
                    wcb(null);
                } else {
                    monitor.updateProgress('Setting change-log information to DB...');
                    Parser.changelogStringParser(changelogString, function (err, changelogList) {
                        if (err) {
                            wcb(err);
                        } else {
                            TizenCommon.set_changelog_to_jobinfo(job, changelogList, wcb);
                        }
                    });
                }
            });
        },
        function (cb) {
            job.options.PACKAGES = job.options.packages.map(function (pkg) {
                return pkg.name;
            });
            job.options.VERSION = job.options.packages[0].version;
            cb(null);
        },
        function (cb) {
            monitor.updateProgress('Checking uploading...');
            TizenCommon.binaryUploadApprovalProcess(jobSvc, job, monitor, cb);
        }
    ], function (err, job1) {
        job1 = job1 || job;

        if (!err && job1.status === 'PENDING') {
            return callback(null, job1);
        }

        finalize(err, job1, monitor, callback);
    });
}


function updateReverseJobs(parentJob, reverseJobIds, callback) {
    async.mapLimit(reverseJobIds, 5,
        function (jobId, cb) {
            dibs.rpc.jobmgr.queryJob(jobId, cb);
        },
        function (err, rjobs) {
            if (!err) {
                parentJob.subJobs = _.union(parentJob.subJobs, rjobs);
            }
            callback(err);
        });
}


function finalize(err, job, monitor, callback) {
    // NOTE. MUST strip unnecessary information for reducing object size
    //       if not, DNODE RPC cannnot receive callback
    TizenCommon.stripJob(job);

    if (dibs.getServersByType('messenger')[0] && job.parentId === null && job.userEmail !== 'admin@user') {
        monitor.updateProgress('sending email');
        TizenCommon.sendEmail(err, job, monitor, function (error) {
            if (error) {
                monitor.updateProgress('sending email failed');
                monitor.updateProgress('error: ' + error.message);
            }
            callback(err, job);
        });
    } else {
        monitor.updateProgress('ignore sending email');
        callback(err, job);
    }
}

function pendingJobHandler(job, callback) {
    TizenCommon.pendingJobHandler(job, callback);
}

function resumeJob(job, options, callback) {
    var jobSvc = options.jobSvc;
    var monitor = options.monitor;
    var resumeOptions = options.resumeOptions;

    // recover build information
    job.buildInfo = job.options.buildInfo;

    TizenCommon.resumeJob(jobSvc, job, resumeOptions, monitor, function (err) {
        finalize(err, job, monitor, callback);
    });

}


function checkPackageInfo(job, pkg, callback) {

    async.waterfall([
        // get latest snapshot
        function (cb) {
            getLatestSnapshot(job.distName, cb);
        },
        // check package version
        function (snapshot, cb) {
            // if no latest snapshot yet, it's ok
            if (snapshot === null) {
                cb(null); return;
            }

            var oldPkg = Snapshot.getPackage(snapshot, pkg.name, job.environmentName);

            if (oldPkg && Package.compareVersion(pkg.version, oldPkg.version) <= 0) {
                cb(new DError('TIZENJOB004', {
                    name: pkg.name,
                    version: pkg.version,
                    oldVersion: oldPkg.version
                }));
                return;
            }
            cb(null);
        }], function (err) {
        callback(err);
    });
}


function getLatestSnapshot(distName, callback) {
    var repoServers = dibs.getServersByType('repo');
    if (repoServers.length === 0) {
        var error = new DError('TIZENGITJOB007');
        callback(error);
        return;
    }

    repoServers[0].searchSnapshots({
        repoType: 'tizen',
        distName: distName,
        name: null
    },
    function (err, snapshots) {
        if (!err) {
            if (snapshots.length > 0) {
                callback(null, snapshots[0]);
            } else {
                callback(null, null);
            }
        } else {
            callback(err, null);
        }
    });
}

