/**
 * multi-testsuite.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 DError = require('../../core/exception.js');
var dibs = require('../../core/dibs.js');
var Job = require('../dibs.model.common/job.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');

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

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


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

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

    this.snapshot = null;
    this.initEnvironments = dibs.projectTypes['Tizen-Multi-Testsuite'].environments;
    this.execEnvironments = dibs.projectTypes['Tizen-Multi-Testsuite'].environments;
}

/**
 * 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/tizenGit/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 TizenMultiTestsuiteJob(newJob));
        });
}


/**
 * Initialize Tizen multi 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/tizenGit/project
 */
function initializeJob(job, options, callback) {
    var monitor = options.monitor;

    // get projets for creating sub-jobs
    var prjInfos = job.options.SUB_JOBS;

    var subJobIds = null;
    async.waterfall([
        // create sub jobs
        function (cb) {
            if (prjInfos.length !== 0) {
                createSubJobs(job, prjInfos, cb);
            } else {
                cb(null, []);
            }
        },
        // get infomation from target
        function (jobIds, cb) {
            subJobIds = jobIds;
            monitor.updateProgress('Initializing sub jobs...', cb);
        },
        // wait for sub jobs initialized
        function (cb) {
            waitForSubJobsInitialized(subJobIds, cb);
        },
        function (cb) {
            monitor.updateProgress('Initializing sub jobs done ', cb);
        },
        // update subJobs
        function (cb) {
            updateSubJobs(job, subJobIds, cb);
        }], function (err) {
        callback(err, null);
    });
}


function createSubJobs(parentJob, prjInfos, callback) {
    async.map(prjInfos,
        function (prjInfo, cb) {
            createSubJob(parentJob, prjInfo, cb);
        },
        function (err, results) {
            callback(err, results);
        });
}


function createSubJob(parentJob, prjInfo, callback) {

    async.waterfall([
        // query project
        function (cb) {
            dibs.rpc.datamgr.searchProjects({
                name: prjInfo.projectName,
                distName: parentJob.distName
            },
                function (err, prjs) {
                    if (err) {
                        cb(err, null);
                    } else if (prjs.length === 0) {
                        cb(new DError('TIZENTA006', {
                            prjName: prjInfo.projectName,
                            distName: parentJob.distName
                        }), null);
                    } else {
                        cb(err, prjs[0]);
                    }
                });
        },
        function (prj, cb) {
            dibs.rpc.jobmgr.addSubJob(parentJob.id, prjInfo.projectName, prjInfo.targetOS, {
            }, cb);
        }], function (err, result) {
        callback(err, result);
    });
}

function waitForAllSubJobsDone(subJobIds, callback) {
    if (subJobIds.length === 0) {
        callback(null); return;
    }

    async.each(subJobIds,
        function (jobId, cb) {
            // callback means jobs status is Done : not necessary to know success or error
            dibs.rpc.jobmgr.waitForJobStatus(jobId, 'FINISHED', function () {
                cb(null);
            });
        },
        function (err) {
            callback(err);
        });

}


function cancelAllSubjobs(subJobIds, callback) {
    if (subJobIds.length === 0) {
        callback(null); return;
    }

    async.each(subJobIds,
        function (jobId, cb) {
            dibs.rpc.jobmgr.cancelJob(jobId, cb);
        },
        function (err) {
            callback(err);
        });
}


function waitForSubJobsInitialized(jobIds, callback) {
    if (jobIds.length === 0) {
        callback(null); return;
    }

    async.each(jobIds,
        function (jobId, cb) {
            dibs.rpc.jobmgr.waitForJobStatus(jobId, 'INITIALIZED',
                function (err, status) {
                    if (err) {
                        cb(err);
                    } else if (status !== 'INITIALIZED') {
                        cb(new DError('TIZENTA007', {
                            jobId: jobId
                        }));
                    } else {
                        cb(err);
                    }
                });
        },
        function (err) {
            if (err) {
                cancelAllSubjobs(jobIds, function () {
                    waitForAllSubJobsDone(jobIds, function () {
                        callback(err);
                    });
                });
            } else {
                callback(err);
            }
        });
}


function updateSubJobs(parentJob, subJobIds, callback) {
    async.map(subJobIds,
        function (jobId, cb) {
            dibs.rpc.jobmgr.queryJob(jobId, cb);
        },
        function (err, results) {
            if (!err) {
                parentJob.subJobs = results;
            }
            callback(err);
        });
}


/**
 * Execute Tizen multi job
 * - it wait for sub-jobs finished & upload them
 * @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/tizenGit/project
 */
function executeJob(job, options, callback) {
    var monitor = options.monitor;

    async.series([
        function (cb) {
            monitor.updateProgress('Waiting for sub jobs finished...', cb);
        },
        // wait for all job finished
        function (cb) {
            waitForSubJobsFinished(job, monitor, cb);
        },
        function (cb) {
            monitor.updateProgress('Waiting for all sub jobs finished is done!', cb);
        },
        // update subJobs
        function (cb) {
            var subJobIds = job.subJobs.map(function (subJob) {
                return subJob.id;
            });
            updateSubJobs(job, subJobIds, cb);
        },
        function (cb) {
            monitor.updateProgress('Terminating job...', cb);
        },
        // save test result
        function (cb) {
            job.options.TEST_RESULT = [];
            _.each(job.subJobs, function (subJob) {
                var result = {};
                result.jobId = subJob.id;
                result.projectName = subJob.projectName;
                result.environmentName = subJob.environmentName;
                result.testsuiteName = subJob.options.TESTSUITE_NAME;
                if (subJob.options.TEST_RESULT) {
                    result.tests = subJob.options.TEST_RESULT.tests;
                    result.failures = subJob.options.TEST_RESULT.failures;
                    result.errors = subJob.options.TEST_RESULT.errors;
                    result.time = subJob.options.TEST_RESULT.time;
                }
                job.options.TEST_RESULT.push(result);
            });
            cb(null);
        }], function (err) {
        callback(err, job);
    });
}


function waitForSubJobsFinished(job, monitor, callback) {

    if (job.subJobs.length === 0) {
        callback(null); return;
    }

    async.each(job.subJobs,
        function (subJob, cb) {
            dibs.rpc.jobmgr.waitForJobStatus(subJob.id, 'FINISHED',
                function (err) {
                    if (err) {
                        monitor.updateProgress({
                            log: 'Sub job \'' + subJob.id + '\' is finished by error...' + err.message,
                            logType: 'warn'
                        });
                    }
                    cb(null);
                });
        },
        function (err) {
            callback(err);
        });
}


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);
        });
}
