/**
 * release-multi-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 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 TizenCommon = require('../org.tizen.common/tizen_common.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 TizenDeployMultipleJob(baseJob) {
    Job.copy(this, baseJob);

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

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

/**
 * 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 TizenDeployMultipleJob(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;

    var subJobIds = null;

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

    // check deploy target options
    if (!job.options.SDK_IMAGE && !job.options.SDK_INSTALLER &&
        !job.options.UPDATE_MANAGER && !job.options.CLI_SDK_INSTALLER) {
        return callback(new DError('TIZENDEPLOY001'), job);
    }

    // check target os list
    if (!job.options.TARGET_OS_LIST || job.options.TARGET_OS_LIST.length <= 0) {
        return callback(new DError('TIZENDEPLOY007'), job);
    }

    async.waterfall([
        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);
                }
            });
        },
        // create sub jobs
        function (cb) {
            monitor.updateProgress('Creating sub jobs...');
            createSubJobs(job, monitor, function (err1, results) {
                subJobIds = results;
                cb(err1);
            });
        },
        // wait for sub jobs initialized
        function (cb) {
            monitor.updateProgress('Waiting for intializing sub jobs...');
            waitForSubJobsInitialized(subJobIds, monitor, cb);
        },
        function (cb) {
            monitor.updateProgress('Initializing sub jobs done ');
            updateSubJobs(job, subJobIds, cb);
        }], function (err) {
        if (err) {
            async.waterfall([
                function (acb) {
                    if (!subJobIds) {
                        subJobIds = _.map(job.subJobs, function (sjob) {
                            return sjob.id;
                        });
                    }
                    monitor.updateProgress('Canceling all sub jobs ');
                    cancelAllSubjobs(subJobIds, monitor, acb);
                }, function (acb) {
                    updateSubJobs(job, subJobIds, acb);
                }, function (acb) {
                    monitor.updateProgress('Canceling all sub jobs done!');
                    acb(null);
                }], function (error) {
                if (error) {
                    monitor.updateProgress('Error :' + error.message);
                }
                callback(err, job);
            });
        } else {
            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('Waiting for sub jobs finished...', cb);
        },
        // update subJobs
        function (cb) {
            var subJobIds = job.subJobs.map(function (subJob) {
                return subJob.id;
            });
            updateSubJobs(job, subJobIds, 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('Checking uploading...', cb);
        },
        function (cb) {
            monitor.updateProgress('Preparing to upload result files...');
            prepareToUpload(job, monitor, function (err, results) {
                if (!err) {
                    job.options.RELEASE_INFO = results;
                }
                cb(err);
            });
        },
        // upload
        function (cb) {
            if (job.options.UPLOAD) {
                monitor.updateProgress(' - uploading...');
                release(job, jobWorkPath, monitor, cb);
            } else {
                monitor.updateProgress(' - skipped uploading');
                cb(null);
            }
        },
        function (cb) {
            monitor.updateProgress('Terminating Tizen-Release-Multi job...', 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 prepareToUpload(job, monitor, callback) {
    var result = [];
    job.subJobs.forEach(function (s) {
        if (s.resultFiles) {
            result.push({file: s.resultFiles[0], info: s.options.RELEASE_INFO});
        }
    });
    callback(null, result);
}


function release(job, jobWorkPath, monitor, callback) {
    var resultFiles = _.flatten(_.pluck(job.options.RELEASE_INFO, 'file'));

    async.series([
        function (cb) {
            releaseToRepository(job.options.RELEASE_NAME, resultFiles,
                job.distName, job.options, monitor, cb);
        },
        function (cb) {
            monitor.updateProgress('Uploading is done!', cb);
        }],
        callback);
}


function releaseToRepository(releaseName, resultFiles, distName, options, monitor, callback) {
    monitor.updateProgress('Upload result files to repository...');

    // get repo server
    var repoServers = dibs.getServersByType('repo');
    if (repoServers.length === 0) {
        var error = new DError('TIZENGITJOB007');
        callback(error, null);
        return;
    }

    // release files
    releaseToRepositoryServer(releaseName, resultFiles, distName, repoServers[0], options, callback);
}


function releaseToRepositoryServer(releaseName, resultFiles, distName, repoServer, options, callback) {
    var releaseInfo = options.RELEASE_INFO;
    if (!releaseInfo) {
        return callback(new DError('TIZENDEPLOY015'));
    }

    var releaseFiles = releaseInfo.map(function (fileInfo) {
        var results = { path: fileInfo.file };
        if (fileInfo.info) {
            results.repoUrl = fileInfo.info.repoUrl ? fileInfo.info.repoUrl : '';
            results.description = fileInfo.info.pkgList ? fileInfo.info.pkgList : '';
            results.includeList = fileInfo.info.includeList ? fileInfo.info.includeList : '';
            results.excludeList = fileInfo.info.excludeList ? fileInfo.info.excludeList : '';
        }

        return results;
    });

    var releaseNote = options.RELEASE_NOTE ? options.RELEASE_NOTE : '';
    repoServer.getRelease(releaseName, distName, function (err, release) {
        if (!err) {
            repoServer.updateRelease(release, distName, {
                releaseFiles: releaseFiles,
                releaseNote: releaseNote
            }, function (err) {
                callback(err);
            });
        } else if (err.errno === 'TREPO027') {
            repoServer.createRelease(releaseName, distName, {
                releaseFiles: releaseFiles,
                releaseNote: releaseNote
            }, function (err) {
                callback(err);
            });
        } else {
            callback(err);
        }
    });
}


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


function createSubJobs(job, monitor, callback) {
    var result = [];

    async.series([
        function (cb) {
            if (!job.options.SDK_IMAGE) {
                return cb(null);
            }
            createSubJobsForImages(job, monitor, function (err, jobIds) {
                if (!err) {
                    result = result.concat(jobIds);
                }
                cb(err);
            });
        },
        function (cb) {
            if (!job.options.SDK_INSTALLER) {
                return cb(null);
            }
            createSubJobsForInstallers(job, monitor, function (err, jobIds) {
                if (!err) {
                    result = result.concat(jobIds);
                }
                cb(err);
            });
        },
        function (cb) {
            if (!job.options.CLI_SDK_INSTALLER) {
                return cb(null);
            }
            createSubJobsForCLIInstallers(job, monitor, function (err, jobIds) {
                if (!err) {
                    result = result.concat(jobIds);
                }
                cb(err);
            });
        },
        function (cb) {
            if (!job.options.UPDATE_MANAGER) {
                return cb(null);
            }
            createSubJobsForUpdateManagers(job, monitor, function (err, jobIds) {
                if (!err) {
                    result = result.concat(jobIds);
                }
                cb(err);
            });
        }
    ],
    function (err) {
        callback(err, result);
    });
}


function createSubJobsForImages(job, monitor, callback) {
    async.mapSeries(job.options.TARGET_OS_LIST,
        function (targetOS, cb) {
            monitor.updateProgress('Creating Tizen-Release-Image sub-job...' + targetOS);
            dibs.rpc.jobmgr.addSubJob(job.id, 'Tizen-Release-Image', targetOS, {
                UPLOAD: false,
                TARGET_OS: targetOS,
                SNAPSHOT_NAME: job.snapshot.name,
                RELEASE_NAME: job.options.RELEASE_NAME,
                ORIGIN_REPO_URL: job.options.ORIGIN_REPO_URL,
                IMG_EXCLUDE_LIST: job.options.IMG_EXCLUDE_LIST,
                IMG_NAME: job.options.IMG_NAME
            }, cb);
        }, function (err, jobIds) {
            callback(err, jobIds);
        });
}


function createSubJobsForInstallers(job, monitor, callback) {
    async.mapSeries(job.options.TARGET_OS_LIST,
        function (targetOS, cb) {
            monitor.updateProgress('Creating Tizen-Release-Installer sub-job...' + targetOS);
            dibs.rpc.jobmgr.addSubJob(job.id, 'Tizen-Release-Installer', targetOS, {
                UPLOAD: false,
                TARGET_OS: targetOS,
                SNAPSHOT_NAME: job.snapshot.name,
                RELEASE_NAME: job.options.RELEASE_NAME,
                ORIGIN_REPO_URL: job.options.ORIGIN_REPO_URL,
                INSTALLER_INCLUDE_LIST: job.options.INSTALLER_INCLUDE_LIST,
                INSTALLER_EXCLUDE_LIST: job.options.INSTALLER_EXCLUDE_LIST,
                INSTALLER_NAME: job.options.INSTALLER_NAME,
                INSTALLER_TITLE: job.options.INSTALLER_TITLE
            }, cb);
        }, function (err, jobIds) {
            callback(err, jobIds);
        });
}


function createSubJobsForCLIInstallers(job, monitor, callback) {
    async.mapSeries(job.options.TARGET_OS_LIST,
        function (targetOS, cb) {
            monitor.updateProgress('Creating Tizen-Release-Installer sub-job...' + targetOS);
            dibs.rpc.jobmgr.addSubJob(job.id, 'Tizen-Release-Installer', targetOS, {
                UPLOAD: false,
                TARGET_OS: targetOS,
                SNAPSHOT_NAME: job.snapshot.name,
                RELEASE_NAME: job.options.RELEASE_NAME,
                ORIGIN_REPO_URL: job.options.ORIGIN_REPO_URL,
                INSTALLER_INCLUDE_LIST: job.options.CLI_INSTALLER_INCLUDE_LIST,
                INSTALLER_EXCLUDE_LIST: job.options.CLI_INSTALLER_EXCLUDE_LIST,
                CLI_INSTALLER: true,
                CLI_INSTALLER_NAME: job.options.CLI_INSTALLER_NAME
            }, cb);
        }, function (err, jobIds) {
            callback(err, jobIds);
        });
}


function createSubJobsForUpdateManagers(job, monitor, callback) {
    async.mapSeries(job.options.TARGET_OS_LIST,
        function (targetOS, cb) {
            monitor.updateProgress('Creating Tizen-Release-Update-Manager sub-job...' + targetOS);
            dibs.rpc.jobmgr.addSubJob(job.id, 'Tizen-Release-Update-Manager', targetOS, {
                UPLOAD: false,
                TARGET_OS: targetOS,
                SNAPSHOT_NAME: job.snapshot.name,
                RELEASE_NAME: job.options.RELEASE_NAME
            }, cb);
        }, function (err, jobIds) {
            callback(err, jobIds);
        });
}


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

    async.each(subJobIds,
        function (jobId, cb) {
            monitor.updateProgress(' - sending cancel request for ... ' + jobId);
            dibs.rpc.jobmgr.cancelJob(jobId, function () {
                cb(null);
            });
        },
        function (err) {
            callback(err);
        });
}


function waitForSubJobsInitialized(jobIds, monitor, 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) {
                        if (status && status !== 'INITIALIZED') {
                            monitor.updateProgress(' - failed with status \'' +
                                status + '\' ...' + jobId);
                            cb(new DError('TIZENMULTIJOB003', {
                                jobId: jobId
                            }));
                        } else {
                            monitor.updateProgress(' - failed with error \'' +
                                err.errno + '\' ...' + jobId);
                            cb(err);
                        }
                    } else {
                        monitor.updateProgress(' - succeeded ...' + jobId);
                        cb(err);
                    }
                });
        },
        function (err) {
            if (err) {
                monitor.updateProgress('Canceling all sub jobs...');
                cancelAllSubjobs(jobIds, monitor, function (err1) {
                    if (err1) {
                        dibs.log.info(err1);
                    }
                    monitor.updateProgress('Canceling all sub jobs done!');
                    callback(err);
                });
            } else {
                callback(err);
            }
        });
}


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, status) {
                    if (err) {
                        if (status && status !== 'FINISHED') {
                            monitor.updateProgress(' - failed with status \'' +
                                status + '\' ...' + subJob.id);
                            cb(new DError('TIZENMULTIJOB004', {
                                jobId: subJob.id
                            }));
                        } else {
                            monitor.updateProgress(' - failed with error \'' +
                                err.errno + '\' ...' + subJob.id);
                            cb(err);
                        }
                    } else {
                        monitor.updateProgress(' - succeeded ...' + subJob.id);
                        cb(null);
                    }
                });
        },
        function (err) {
            if (err) {
                var subJobIds = _.map(job.subJobs, function (subjob) {
                    return subjob.id;
                });
                monitor.updateProgress('Canceling all sub jobs...');
                cancelAllSubjobs(subJobIds, monitor, function (err1) {
                    if (err1) {
                        dibs.log.info(err1);
                    }
                    monitor.updateProgress('Canceling all sub jobs done!');
                    callback(err);
                });
            } else {
                callback(err);
            }
        });
}


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