/**
 * release-installer-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 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 InstallerGenerator = require('./installer-generator.js');
var JobDep = require('../org.tizen.projects/job-dep.js');
var Tutils = require('../org.tizen.common/tizen_utils.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');
var FileSystem = require('../dibs.core/filesystem.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 TizenDeployInstallerJob(baseJob) {
    Job.copy(this, baseJob);

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

    // NOTE. Initializing on windows is very slow
    this.initEnvironments = dibs.projectTypes['Tizen-Release-Installer'].environments
        .filter(function (e) {
            return (e.indexOf('windows') === -1);
        });
    this.execEnvironments = [Tutils.EnvironmentName2Id('Tizen-Release-Installer', this.options.TARGET_OS)];
}

/**
 * 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 TizenDeployInstallerJob(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 Installer...');
            makeSDKInstallers(job, jobWorkPath, monitor, function (err, results) {
                job.options.RELEASE_INFO = results;
                cb(err);
            });
        },
        function (cb) {
            monitor.updateProgress('Making SDK Installer is completed!', cb);
        },
        function (cb) {
            monitor.updateProgress('Preparing to upload result files...');
            prepareToUploadInstaller(server, jobWorkPath, job, monitor, function (err, rpaths) {
                if (!err) {
                    job.resultFiles = rpaths;
                }
                cb(err);
            });
        },
        function (cb) {
            if (job.options.UPLOAD) {
                monitor.updateProgress('Uploading SDK Installer...');
                uploadSDKInstallers(job, jobWorkPath, monitor, cb);
            } else {
                monitor.updateProgress('Uploading is skipped by \'UPLOAD:false\' option.');
                cb(null);
            }
        },
        function (cb) {
            dibs.rpc.datamgr.updateJob(job, cb);
        },
        function (cb) {
            monitor.updateProgress('Deploying SDK Installer 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 makeSDKInstallers(job, jobWorkPath, monitor, callback) {
    InstallerGenerator.generate(null, job.distName, job.options.TARGET_OS,
        {
            workspace: jobWorkPath,
            snapshotName: job.snapshot.name,
            includeList: job.options.INSTALLER_INCLUDE_LIST,
            excludeList: job.options.INSTALLER_EXCLUDE_LIST,
            orginRepoURL: job.options.ORIGIN_REPO_URL,
            isCLI: job.options.CLI_INSTALLER,
            releaseName: job.options.RELEASE_NAME,
            installerTitle: job.options.INSTALLER_TITLE
        },
        monitor,
        function (err, results) {
            callback(err, results);
        }
    );
}


function uploadSDKInstallers(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 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: 'update-manager'
        };
    });

    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 prepareToUploadInstaller(server, srcPath, job, monitor, callback) {
    var releaseName = job.options.RELEASE_NAME;
    var installerName = job.options.INSTALLER_NAME;
    var cliInstallerName = job.options.CLI_INSTALLER_NAME;

    async.waterfall([
        function (wcb) {
            fs.readdir(srcPath, wcb);
        },
        function (files, wcb) {
            var filtered = files.filter(function (file) {
                return (file.indexOf('installer_') > -1);
            }).map(function (file) {
                return path.join(srcPath, file);
            });
            // change installer name to use official format
            async.mapSeries(filtered,
                function (filePath, cb1) {
                    var orgFileName = path.basename(filePath);
                    // NOTE. extname includes dot(.)
                    var ext = path.extname(orgFileName);
                    var name = orgFileName.split(ext)[0];
                    var os = name.split('_')[1];
                    var targetPath;
                    if (filePath.indexOf('cli-installer_') > -1) {
                        if (cliInstallerName) {
                            targetPath = path.join(srcPath, cliInstallerName + '_' + releaseName + '_' + os + ext);
                        } else {
                            targetPath = path.join(srcPath,
                                'tizen-web-cli_' + releaseName + '_' + os + ext);
                        }
                    } else {
                        if (installerName) {
                            targetPath = path.join(srcPath, installerName + '_' + releaseName + '_' + os + ext);
                        } else {
                            targetPath = path.join(srcPath, 'tizen-web-ide_' + releaseName + '_' + os + ext);
                        }
                    }
                    FileSystem.move(filePath, targetPath, function (err) {
                        cb1(err, targetPath);
                    });
                },
                function (err, newPaths) {
                    if (!err) {
                        addToDFS(server, newPaths, wcb);
                    } else {
                        wcb(err);
                    }
                });
        }
    ], function (err, result) {
        if (err) {
            monitor.updateProgress(err.message);
        }
        callback(err, result);
    });
}


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