/**
 * package-push-project.js
 * Copyright (c) 2000 - 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 _ = require('underscore');
var path = require('path');

var dibs = require('../../core/dibs.js');
var DError = require('../../core/exception.js');
var Job = require('../dibs.model.common/job.js');
var JobDep = require('../org.tizen.projects/job-dep.js');
var Snapshot = require('../org.tizen.repository/snapshot.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');
var TizenUtils = require('../org.tizen.common/tizen_utils.js');

/**
 * Tizen deploy 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 deploy job
 * @constructor
 * @augments module:models/job
 * @param {module:models/job~Job} baseJob - base job object
 */

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

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

/**
 * Create tizen deploy 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) {
    Job.create(userEmail, distName, distType, prjName, prjType, environmentName, parentId, null, options,
        function (err, newJob) {
            callback(err, new TizenDeployUploadUpstreamJob(newJob));
        });
}

/**
 * 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) {
    var monitor = options.monitor;

    if (job.status === 'INITIALIZED') {
        callback(null, job); return;
    }

    async.waterfall([
        function (cb) {
            monitor.updateProgress('Checking target snapshot...', cb);
        },
        function (cb) {
            var queryOptions = {
                name: null,
                repoType: 'tizen',
                distName: job.distName
            };

            if (job.options.SNAPSHOT_NAME) {
                queryOptions.name = job.options.SNAPSHOT_NAME;
            }

            dibs.rpc.repo.searchSnapshots(queryOptions, function (err, snapshots) {
                if (!err) {
                    if (snapshots.length > 0) {

                        // update snapshot options
                        job.options.SNAPSHOT_NAME = snapshots[0].name;

                        cb(null);
                    } else {
                        cb(new DError('TIZENDEPLOY002', {
                            sname: queryOptions.name
                        }));
                    }
                } else {
                    cb(err);
                }
            });
        },
        function (cb) {
            monitor.updateProgress('Checking upstream distribution...', cb);
        },
        // check upstream distribution exists
        function (cb) {
            if (job.options.UPSTREAM_DIST_NAME) {
                return cb(null, job.options.UPSTREAM_DIST_NAME);
            }

            // if not, check the property of distributon
            dibs.rpc.datamgr.searchDistributions({
                name: job.distName,
                type: 'tizen'
            }, function (err, dists) {
                if (err) {
                    return cb(err);
                }

                var upstreamDistName = dists[0].options.UPSTREAM_DIST_NAME;
                if (upstreamDistName) {
                    // update job property
                    job.options.UPSTREAM_DIST_NAME = upstreamDistName;

                    cb(null, upstreamDistName);
                } else {
                    cb(new DError('TIZENDEPLOY003'), null);
                }
            });
        },
        // check upstream distribution
        function (upstreamDistName, cb) {
            dibs.rpc.repo.searchDistributions({
                repoType: 'tizen',
                distName: upstreamDistName
            }, function (err, dists) {
                if (!err) {
                    if (dists.length === 0) {
                        cb(new DError('TIZENDEPLOY004', {
                            distName: upstreamDistName
                        }));
                    } else {
                        cb(null);
                    }
                } else {
                    cb(err);
                }
            });
        }],
        function (err) {
            callback(err, job);
        });
}

/**
 * Execute Tizen deploy 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;

    async.series([
        function (cb) {
            monitor.updateProgress('Checking if target snapshot is valid...', cb);
        },
        function (cb) {
            var queryOptions = {
                name: null,
                repoType: 'tizen',
                distName: job.distName
            };

            if (job.options.SNAPSHOT_NAME) {
                queryOptions.name = job.options.SNAPSHOT_NAME;
            }

            dibs.rpc.repo.searchSnapshots(queryOptions, function (err, snapshots) {
                if (!err) {
                    if (snapshots.length > 0) {
                        job.snapshot = snapshots[0];
                        job.snapshot.distName = job.distName;
                        cb(null);
                    } else {
                        cb(new DError('TIZENDEPLOY002', {
                            sname: queryOptions.name
                        }));
                    }
                } else {
                    cb(err);
                }
            });
        },
        function (cb) {
            monitor.updateProgress('The target snapshot name is \'' + job.snapshot.name + '\'.', cb);
        },
        function (cb) {
            monitor.updateProgress('Uploading to upstream distribution...');
            uploadToUpstreamDistribution(job, jobWorkPath, monitor, cb);
        },
        function (cb) {
            monitor.updateProgress('Uploding to upstream is completed!', cb);
        }], function (err) {
        // NOTE. MUST strip unnecessary information for reducing object size
        //       if not, DNODE RPC cannnot receive callback
        TizenCommon.stripJob(job);

        callback(err, job);
    });
}


function uploadToUpstreamDistribution(job, jobWorkPath, monitor, callback) {
    var repo = dibs.getServersByType('repo')[0];
    var updatedPkgs = null;

    async.waterfall([
        function (cb) {
            repo.searchSnapshots({
                name: job.snapshot.name,
                distName: job.options.UPSTREAM_DIST_NAME,
                repoType: 'tizen'
            }, function (err, snapshots) {
                if (!err) {
                    if (snapshots.length === 0) {
                        cb(null);
                    } else {
                        cb(new DError('TIZENDEPLOY013', {
                            snapshotName: job.snapshot.name,
                            distName: job.options.UPSTREAM_DIST_NAME
                        }));
                    }
                } else {
                    cb(err);
                }
            });
        },
        // get upstream distribution information
        function (cb) {
            monitor.updateProgress('Getting repository...');
            repo.searchSnapshots({
                name: null,
                distName: job.options.UPSTREAM_DIST_NAME,
                repoType: 'tizen'
            }, function (err, snapshots) {
                if (!err) {
                    if (snapshots.length === 0) {
                        var dummySnapshot = Snapshot.create(null, null, {});
                        cb(null, dummySnapshot);
                    } else {
                        cb(null, snapshots[0]);
                    }
                } else {
                    cb(err, null);
                }
            });
        },
        function (s, cb) {
            monitor.updateProgress('Got ' + job.options.UPSTREAM_DIST_NAME + ' distribution\'s snapshot : ' + s.name,
                function (err) {
                    cb(err, s);
                });
        },
        // get "CONFLICT" packages
        function (s, cb) {
            var filteredCurrentSnapshot = Snapshot.getFilteredSnapshot(job.snapshot, job.options.INCLUDE, job.options.EXCLUDE, job.options.INCLUDE_ALL, true, job.options.USE_BUILD_DEPENDENCY);
            monitor.updateProgress('Checking conflict packages...');
            var targetSnapshot = s;
            var conflictPkgs = Snapshot.getConflictPackages(filteredCurrentSnapshot,
                targetSnapshot, {
                    osList: job.options.TARGET_OS_LIST
                });
            if (conflictPkgs.length > 0) {
                cb(new DError('TIZENDEPLOY011', {
                    pkgs: conflictPkgs.map(function (pkg) {
                        return '(' + pkg.name + ':' + pkg.os + ')';
                    }).join(',')
                }, null));
            } else {
                cb(null, s, filteredCurrentSnapshot);
            }
        },
        // get "OUTDATED" packages
        function (s, c, cb) {
            monitor.updateProgress('Checking outdated packages...');
            var targetSnapshot = s;
            var outdatedPkgs = Snapshot.getOutdatedPackages(c,
                targetSnapshot, {
                    osList: job.options.TARGET_OS_LIST
                });
            if (outdatedPkgs.length > 0) {
                cb(new DError('TIZENDEPLOY012', {
                    pkgs: outdatedPkgs.map(function (pkg) {
                        return '(' + pkg.name + ':' + pkg.os + ')';
                    }).join(',')
                }, null));
            } else {
                cb(null, s, c);
            }
        },
        // get updated packages
        function (s, c, cb) {
            monitor.updateProgress('Getting updated packages...');
            var targetSnapshot = s;
            updatedPkgs = Snapshot.getUpdatedPackages(c,
                targetSnapshot, {
                    osList: job.options.TARGET_OS_LIST
                });
            var unmets = Snapshot.checkUnmetDepsIfUpdated(targetSnapshot, updatedPkgs);
            if (unmets.length > 0) {
                cb(new DError('TIZENDEPLOY006', {
                    unmetMsg: unmets.map(function (dep) {
                        return '(' + dep.name + ':' + dep.os + ')';
                    }).join(',')
                }));
            } else {
                cb(null);
            }
        },
        // download packages
        function (cb) {
            monitor.updateProgress('Preparing to register updated packages...' +
                updatedPkgs.map(function (e) {
                    return e.name + '(' + e.os + ')';
                }).join(','));
            preparePackages(updatedPkgs, repo, job.distName, job.snapshot.name, monitor, cb);
        },
        function (rpaths, cb) {
            var buildInfo = [];
            _.each(rpaths, function (dfsPath) {
                var pInfo = TizenUtils.getInfoFromPackageFileName(path.basename(dfsPath));
                if (pInfo && pInfo.os && pInfo.name) {
                    var build = {};
                    var regex = /__[A-Z]/;
                    _.each(job.snapshot.osPackages[pInfo.os][pInfo.name].options, function (value, key) {
                        var match = regex.exec(key);
                        if (match) {
                            build[match.input] = value;
                        }
                    });
                    buildInfo.push({
                        rpath: dfsPath,
                        build: build
                    });
                }
            });
            cb(null, rpaths, buildInfo);
        },
        // register packages to upstream
        function (rpaths, buildInfo, cb) {
            monitor.updateProgress('Registering updated packages...');
            repo.registerRemotePackages(rpaths, {
                distName: job.options.UPSTREAM_DIST_NAME,
                repoType: 'tizen',
                syncSnapshotName: job.snapshot.name,
                buildInfo: buildInfo,
                progress: function (msg) {
                    monitor.updateProgress('  ' + msg);
                }
            }, cb);
        }],
        function (err, snapshot) {
            if (!err) {
                monitor.updateProgress('New snapshot has been generated! ' + snapshot.name);
            }
            callback(err);
        });
}

function preparePackages(pkgs, repo, distName, snapshotName, monitor, callback) {
    async.mapLimit(pkgs, 5,
        function (pkg, cb) {
            monitor.updateProgress('Downloading...' + pkg.name + '(' + pkg.os + ')');
            if (pkg.name) { // binary packages
                repo.downloadRemotePackage(pkg.name, {
                    repoType: 'tizen',
                    os: pkg.os,
                    distName: distName,
                    snapshotName: snapshotName
                }, function (err, rpath) {
                    cb(err, rpath);
                });
            } else { // archive packages
                repo.downloadRemotePackage(pkg, {
                    repoType: 'tizen',
                    distName: distName,
                    snapshotName: snapshotName
                }, function (err, rpath) {
                    cb(err, rpath);
                });
            }
        },
        function (err, rpaths) {
            callback(err, rpaths);
        });
}


function checkMutualExclusiveJob(srcJob, tarJob) {
    if (srcJob.distName !== tarJob.distName) {
        return false;
    }
    if (JobDep.hasSamePackages(srcJob, tarJob) ||
        JobDep.hasBuildDependency(srcJob, tarJob) ||
        JobDep.hasBuildDependency(tarJob, srcJob)) {
        return true;
    }
    return false;
}


function checkJobPrivilege(user, groups, distribution, project, jobOptions, callback) {
    var subJobInfo = jobOptions.SUB_JOBS;
    async.eachSeries(_.pluck(subJobInfo, 'projectName'),
        function (prjName, ecb) {
            if (jobOptions.UPLOAD_TEMP) {
                ecb(null);
            } else {
                dibs.rpc.datamgr.checkJobPrivilege(user.email, distribution.name, prjName, jobOptions, ecb);
            }
        },
        function (err) {
            callback(err);
        });
}
