/**
 * release-image-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 fs = require('fs');
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 ImageGenerator = require('../org.tizen.common/image-generator.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 TizenDeployImageJob(baseJob) {
    Job.copy(this, baseJob);

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

    // NOTE. Initializing/Executing on windows is very slow
    this.initEnvironments = dibs.projectTypes['Tizen-Release-Image'].environments
        .filter(function (e) {
            return (e.indexOf('windows') === -1);
        });
    this.execEnvironments = dibs.projectTypes['Tizen-Release-Image'].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 TizenDeployImageJob(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) {
    if (job.status === 'INITIALIZED') {
        return callback(null, job);
    }

    if (!job.options.RELEASE_NAME) {
        return callback(new DError('TIZENDEPLOY008'), job);
    }
    if (!job.options.ORIGIN_REPO_URL) {
        return callback(new DError('TIZENDEPLOY009'), job);
    }

    callback(null, 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;
    var server = dibs.getServer(options.parentServerId);

    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('Making SDK Image(s)...');
            makeSDKImage(job, jobWorkPath, monitor, cb);
        },
        function (cb) {
            monitor.updateProgress('Making SDK image is completed!', cb);
        },
        function (cb) {
            monitor.updateProgress('Preparing to upload result files...');
            prepareToUpload(server, jobWorkPath, monitor, function (err, rpaths) {
                if (!err) {
                    job.resultFiles = rpaths;
                }
                cb(err);
            });
        },
        function (cb) {
            if (job.options.UPLOAD) {
                monitor.updateProgress('Uploading SDK Image(s)...');
                uploadSDKImage(job, jobWorkPath, monitor, cb);
            } else {
                monitor.updateProgress('Uploading is skipped by \'UPLOAD:false\' option.');
                cb(null);
            }
        },
        function (cb) {
            monitor.updateProgress('Deploying SDK image 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 makeSDKImage(job, jobWorkPath, monitor, callback) {
    ImageGenerator.makeImage({
        os: job.options.TARGET_OS,
        workspace: jobWorkPath,
        imgName: job.options.RELEASE_NAME + '_' + job.options.TARGET_OS + '.zip',
        imgInfo: job.options.ORIGIN_REPO_URL,
        pServerURL: null,
        distributionName: job.distName,
        snapshotName: job.snapshot.name,
        excludeList: job.options.IMG_EXCLUDE_LIST
    }, callback);
}


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


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 prepareToUpload(server, srcPath, monitor, callback) {
    monitor.updateProgress('Preparing to upload result files...');

    async.waterfall([
        function (wcb) {
            fs.readdir(srcPath, wcb);
        },
        function (files, wcb) {
            var zipFiles = _.select(files, function (file) {
                return (file.indexOf('.zip') > -1);
            });
            var zipPaths = _.map(zipFiles, function (file) {
                return path.join(srcPath, file);
            });
            addToDFS(server, zipPaths, wcb);
        }
    ], function (err, result) {
        if (err) {
            monitor.updateProgress(err.message);
        }
        callback(err, result);
    });
}


function addToDFS(server, lpaths, callback) {
    if (lpaths.length === 0) {
        callback(null, []); return;
    }
    async.mapSeries(lpaths,
        function (lpath, cb) {
            server.dfsAddFile(null, lpath, {
                lifetime: 60 * 60 * 1000
            }, cb);
        },
        function (err, result) {
            callback(err, result);
        });
}


function uploadFiles(resultFiles, distName, releaseName, 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
    releaseResultFiles(resultFiles, releaseName, distName, repoServers[0], callback);
}


function releaseResultFiles(resultFiles, releaseName, distName, repoServer, callback) {
    var releaseFiles = resultFiles.map(function (rpath) {
        return {
            path: rpath,
            category: 'image'
        };
    });

    repoServer.getRelease(releaseName, distName, function (err, release) {
        if (!err) {
            repoServer.updateRelease(release, distName, {
                releaseFiles: releaseFiles
            }, function (err) {
                callback(err);
            });
        } else if (err.errno === 'TREPO027') {
            repoServer.createRelease(releaseName, distName, {
                releaseFiles: releaseFiles
            }, function (err) {
                callback(err);
            });
        } else {
            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);
        });
}
