/**
 * tizen_common.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 _ = require('underscore');
var async = require('async');
var path = require('path');

var dibs = require('../../core/dibs.js');
var TizenGit = require('../org.tizen.projects/git-control.js');
var DError = require('../../core/exception.js');
var JobDep = require('../org.tizen.projects/job-dep.js');
var utils = require('../../lib/utils.js');


/**
 * logs saving method
 * it expect to avoid db string length limit
 * process
 * 1. save logs length as count
 * 2. save each log with log index
 * if empty logs in then job never changed
 * @public
 * @function setDBLogs
 * @param {Array} job - owner of logs
 * @param {Array} logs - log list
 * @param {Array} type - prefix of each log ( ex COMMIT_LOG, CHANGE_LOG )
 * @param callack - callback (error)
 * @memberOf module:models/tizen-common/tizen_common
 */
function setDBLogs(job, logs, type) {
    logs = _.compact(logs);
    if (logs.length > 0) {
        job.options[type + '_COUNT'] = logs.length;
        _.each(logs, function (log, idx) {
            job.options[type + '_' + idx] = log;
        });
    }
}


/**
 * get attribute from last build info
 * require
 * @public
 * @function getLatestAttribute
 * @param {Array} job - job object
 * @param {Array} attr - search field name
 * @param callack - callback (error)
 */
function getLatestAttribute(job, attr) {
    switch (job.type) {
    case 'visualstudio-solution':
        if (job.options.LAST_BUILD_INFO && job.options.LAST_BUILD_INFO[job.options.TARGET_OS] &&
            job.options.LAST_BUILD_INFO[job.options.TARGET_OS][attr]) {
            return job.options.LAST_BUILD_INFO[job.options.TARGET_OS][attr];
        } else {
            return;
        }
    default:
        return;
    }
}


module.exports.set_commit_to_jobinfo = set_commit_to_jobinfo;
function set_commit_to_jobinfo(job, srcPath, callback) {
    var lastCommitId = getLatestAttribute(job, 'gitCommit');
    if (lastCommitId) {
        TizenGit.getLog(srcPath, lastCommitId, '',
            function (err, logs) {
                // if LAST BUILD INFO is ruined
                if (err) {
                    TizenGit.getLog(srcPath, '-1', '',
                        function (err2, logs2) {
                            if (!err2) {
                                setDBLogs(job, [logs2[0]], 'COMMIT_LOG');
                            }
                        });
                    callback(null);
                } else {
                    // right COMMIT DIFF
                    setDBLogs(job, logs, 'COMMIT_LOG');
                    callback(null);
                }
            });
    } else {
        // new COMMIT LOG
        TizenGit.getLog(srcPath, '-1', '',
            function (err, logs) {
                if (!err) {
                    setDBLogs(job, [logs[0]], 'COMMIT_LOG');
                }
                callback(null);
            }
        );
    }
}


/**
 * simple upload process
 * process
 * 1. get repo server
 * 2. register local files to repo server
 * @public
 * @function upload
 * @param {String} uploadFiles - local file list to upload
 * @param {String} distName - distribution name
 * @param {Boolean} compat - set uploadCompat option of repo.registerRemotePackages
 * @param {Boolean} force - set forceupload option of repo.registerRemotePackages
 * @param {String} userEmail - set userEmail as uploader
 * @param {Boolean} isTemp - temp upload option of repo.registerRemotePackages
 * @param {String} jobId - when temp upload using jobId for temp dir
 * @param callack - callback (error, snapshotInfo)
 */
// callback(err, snapshotInfo)
module.exports.upload = upload;
function upload(uploadFiles, distName, force, userEmail, isTemp, jobId, buildInfo, monitor, callback) {
    if (isTemp) {
        monitor.updateProgress('Upload result files to temporary repository...');
    } else {
        monitor.updateProgress('Uploading...');
    }
    monitor.updateProgress(' - [' + uploadFiles.map(function (e) {
        return path.basename(e);
    }).join(',') + ']');

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

    // register files
    repoServers[0].registerRemotePackages(uploadFiles, {
        repoType: 'tizen-visualstudio',
        distName: distName,
        forceUpload: (force || isTemp) ? true : false,
        userEmail: userEmail,
        isTemp: isTemp,
        tempJobId: jobId ? jobId : null,
        buildInfo: buildInfo,
        progress: function (msg) {
            monitor.updateProgress(' - ' + msg);
        }
    }, function (err, snapshotInfo) {
        if (!err) {
            monitor.updateProgress(' - uploading package succeeded! ');
        } else {
            monitor.updateProgress({
                log: ' - uploading package failed! ',
                logType: 'error'
            });
        }
        callback(err, snapshotInfo);
    });
}


/**
 * upload Tizen packages process form job
 * process
 * 1. setting job.board
 * 2. upload
 * 3. setting job.snapshotName
 * 4. settring job.board
 * @public
 * @function upload
 * @param {object} job - job object that must include below
 *                      { id : string,
 *                        resultFiles : [string...],
 *                        userEmail : string}
 * @param {object} monitor - monitor object
 * @param callack - callback (error)
 * @memberOf module:models/tizen-common/tizen_common
 */
// callback(err)
module.exports.uploadJobPackages = uploadJobPackages;
function uploadJobPackages(job, tempUpload, monitor, callback) {
    if (job.board) {
        job.board.push({
            type: 'STAMP',
            time: utils.getTimeString(),
            name: 'upload'
        });
    }
    upload(job.resultFiles,
        job.distName,
        job.options.FORCE_REBUILD,
        job.userEmail,
        tempUpload,
        job.id,
        job.buildInfo || null,
        monitor,
        function (err, snapshotInfo) {
            if (!err) {
                job.snapshotName = snapshotInfo.name;
                monitor.updateProgress('Snapshot published...' + job.snapshotName);

                if (job.board) {
                    job.board.push({
                        type: 'STAMP',
                        time: utils.getTimeString(),
                        name: 'upload done'
                    });
                }
            }
            callback(err, job);
        });
}


module.exports.checkMutualExclusiveJob = checkMutualExclusiveJob;
function checkMutualExclusiveJob(srcJob, tarJob) {
    if (srcJob.distName !== tarJob.distName) {
        return false;
    }

    if (JobDep.hasSamePackages(srcJob, tarJob)) {
        return true;
    }

    return false;
}


module.exports.generateTizenBuildSummary = generateTizenBuildSummary;
function generateTizenBuildSummary(job) {
    var index = '';
    var summary = '';

    summary += ' Distribution: ' + job.distName + '\n\n';
    if (job.projectType === 'visualstudio-multi') {
        var projectNameList = _.uniq(_.pluck(job.subJobs, 'projectName'));
        var subJobs = [];
        projectNameList.forEach(function (projectName) {
            var subJob = _.find(job.subJobs, function (subJob) {
                return subJob.projectName === projectName;
            });
            subJobs.push(subJob);
        });

        summary += _.map(subJobs, function (subJob) {
            var tempSummary = '';
            tempSummary += ' Project Name: ' + subJob.projectName + '\n';
            if (subJob.projectType === 'visualstudio-solution') {
                tempSummary += ' Source Information:\n';
                for (var i = 0; i < subJob.options.COMMIT_LOG_COUNT; i++) {
                    index = 'COMMIT_LOG_' + i;
                    tempSummary += '  - Commit: ' + subJob.options[index].Commit + '\n';
                    tempSummary += '  - Author: ' + subJob.options[index].Author + '\n';
                    tempSummary += '  - Date: ' + subJob.options[index].Date + '\n';
                    tempSummary += '  - Contents:\n' + subJob.options[index].Contents + '\n';
                }
            }

            tempSummary += '\n';
            return tempSummary;
        }).join('\n');
    } else {
        summary += ' Project Name: ' + job.projectName + '\n';
        if (job.projectType === 'visualstudio-solution') {
            summary += ' Source Information:\n';
            for (var i = 0; i < job.options.COMMIT_LOG_COUNT; i++) {
                index = 'COMMIT_LOG_' + i;
                summary += '  - Commit: ' + job.options[index].Commit + '\n';
                summary += '  - Author: ' + job.options[index].Author + '\n';
                summary += '  - Date: ' + job.options[index].Date + '\n';
                summary += '  - Contents:\n' + job.options[index].Contents + '\n';
            }
        }
    }


    if (job.snapshotName) {
        summary += ' Snapshot : ' + job.snapshotName + '\n';
    }

    summary += ' Package(s) Name:\n  ';
    summary += _.map(job.resultFiles, function (file) {
        return file.split(/[/]+/).pop();
    }).join('\n  ');
    summary += '\n';

    return summary;
}


// callback(err)
module.exports.checkApproval = checkApproval;
function checkApproval(approvalId, callback) {

    dibs.rpc.approvalmgr.getApproval(approvalId, function (err, approval) {
        if (err) {
            callback(err);
        } else if (!approval) {
            callback(new DError('VSJOB013'));
        } else if (approval.submissionStatus === 'SUBMITTED') { //Already approval process is done
            var approvalmgr = dibs.getServersByType('approvalmgr')[0];

            approvalmgr.addEventListener(
                'APPROVAL_STATUS_CHANGED',
                {
                    approvalId: approvalId
                },
                function (eventObj, eventCb) {
                    switch (eventObj.status) {
                    case 'APPROVED':
                        callback(null);
                        break;
                    case 'REJECTED':
                        callback(new DError('VSJOB007'));
                        break;
                    case 'CANCELED':
                        callback(new DError('VSJOB012'));
                        break;
                    default:
                        break;
                    }
                    eventCb(null);
                },
                function (err) {
                    if (err) { // is not PENDING
                        callback(err);
                    }
                });
        } else {
            switch (approval.submissionStatus) {
            case 'APPROVED':
                callback(null);
                break;
            case 'REJECTED':
                callback(new DError('VSJOB007'));
                break;
            case 'CANCELED':
                callback(new DError('VSJOB012'));
                break;
            default:
                callback(new Error('System error- not defined status: ' + approval.submissionStatus));
                break;
            }
        }
    });
}


module.exports.resumeJob = resumeJob;
function resumeJob(jobSvc, job, options, monitor, callback) {
    if (job.status !== 'PENDING') {
        return callback(new Error('cancel'));
    }

    if (!job.approvalId) {
        return callback(new Error('cancel'));
    }

    if (options.error) {
        monitor.updateProgress('Approval is failed: ' + options.error);
        return callback(options.error);
    }

    job.statusInfo = 'Job #' + job.id + ' is approval done.';
    if (job.board) {
        job.board.push({
            type: 'STAMP',
            time: utils.getTimeString(),
            name: 'approval done'
        });
    }

    async.series([
        function (cb) {
            monitor.updateProgress('status change : WORKING');
            jobSvc.updateJobStatus(job, 'WORKING', cb);
        },
        function (cb) {
            if (!job.resultFiles) {
                job.resultFiles = job.options.resultFiles; //for recovery job
            }
            uploadJobPackages(job, false, monitor, cb);
        }
    ], function (err) {
        if (err) {
            monitor.updateProgress('- approval upload failed');
        } else {
            monitor.updateProgress('- approval upload done');
        }
        callback(err);
    });
}


module.exports.pendingJobHandler = pendingJobHandler;
function pendingJobHandler(job, callback) {
    checkApproval(job.approvalId, callback);
}


function approvalProcess(jobSvc, job, monitor, callback) {
    async.waterfall([
        function (cb) {
            monitor.updateProgress('Publishing temp snapshot...');
            uploadJobPackages(job, true, monitor, function (err) {
                cb(err);
            });
        },
        function (cb) {
            monitor.updateProgress('Start approval process...', cb);
        },
        function (cb) {
            if (job.board) {
                job.board.push({
                    type: 'STAMP',
                    time: utils.getTimeString(),
                    name: 'approval'
                });
            }
            var summary = generateTizenBuildSummary(job);
            var subject = '';
            if (job.options.approvalInfo.subject) {
                subject = '[JOB-' + job.id + '] ' + job.options.approvalInfo.subject;
            } else {
                if (job.projectName) {
                    subject = '[JOB-' + job.id + '] ' + job.projectName;
                } else {
                    subject = '[JOB-' + job.id + '] ' + job.subJobs[0].projectName + ' and other ' + (job.subJobs.length - 1) + ' projects';
                }
            }

            // for recovery
            // TODO: remove duplicated information about resultFiles
            job.options.resultFiles = job.resultFiles;

            dibs.rpc.approvalmgr.addApproval(
                job.userEmail,
                subject,
                job.options.approvalInfo.contents,
                summary,
                job.options.approvalInfo.approvalRoutes,
                'PACKAGE_UPLOAD',
                cb);
        },
        function (approval, cb) {
            job.statusInfo = 'Job #' + job.id + ' is under approval.';
            job.approvalId = approval.id;
            monitor.updateProgress('- job status updating PENDING...');
            jobSvc.updateJobStatus(job, 'PENDING', cb);
        }
    ],
    function (err) {
        if (err) {
            monitor.updateProgress('- approval process failed');
        } else {
            monitor.updateProgress('Job #' + job.id + ' is under approval.');
        }
        callback(err, job);
    });
}


//callback(err)
module.exports.binaryUploadApprovalProcess = binaryUploadApprovalProcess;
function binaryUploadApprovalProcess(jobSvc, job, monitor, callback) {

    if (job.options.UPLOAD_TEMP && job.parentId === null) {
        uploadJobPackages(job, true, monitor, callback);
    } else if (job.options.UPLOAD) {
        if (job.options.approvalInfo) {
            approvalProcess(jobSvc, job, monitor, callback);
        } else {
            uploadJobPackages(job, false, monitor, callback);
        }
    } else {
        monitor.updateProgress(' - skipped uploading');
        callback(null, job);
    }
}


module.exports.checkReleasePhase = checkReleasePhase;
//callback(err, job)
function checkReleasePhase(job, callback) {
    if (job.parentId && job.options.approvalInfo) {
        callback(new DError('VSJOB010', {
            parentId: job.parentId
        }), job);
    } else if (!(job.options.UPLOAD) && job.options.approvalInfo) {
        callback(new DError('VSJOB011', {}), job);
    } else {
        async.waterfall([
            function (cb) {
                dibs.rpc.datamgr.searchDistributions({
                    name: job.distName
                }, cb);
            },
            function (dist, cb) {
                var error = null;
                if (dist[0].options.PHASE === 'release') { // approvalInfo must have
                    if (!job.options.approvalInfo && !job.parentId && job.options.UPLOAD) {
                        error = new DError('VSJOB009', {
                            distName: job.distName
                        });
                    }
                    if (job.options.approvalInfo && (!job.options.approvalInfo.approvalRoutes ||
                        _.isEmpty(job.options.approvalInfo.approvalRoutes))) {
                        error = new DError('VSJOB014');
                    }
                } else { // approvalInfo must haven't
                    if (job.options.approvalInfo) {
                        error = new DError('VSJOB008', {
                            distName: job.distName
                        });
                    }
                }
                cb(error);
            }
        ],
        function (err) {
            callback(err, job);
        });
    }
}


// NOTE. MUST strip unnecessary information for reducing object size
//       if not, DNODE RPC cannnot receive callback
module.exports.stripJob = stripVSJOB;
function stripVSJOB(job) {
    if (job.snapshot) {
        job.snapshot = null;
    }
    if (job.distribution) {
        job.distribution = null;
    }
}


module.exports.sendEmail = sendEmail;
function sendEmail(jobResult, job, monitor, callback) {
    var subject = '';
    var msg = '';
    var targets = job.userEmail;

    async.waterfall([
        function (scb) {
            if (job.projectType === 'visualstudio-multi') {
                msg += 'MultiJob ID: ' + job.id + '\n';
                msg += 'Distribution: ' + job.distName + '\n';
                msg += 'SubJobs:\n';

                job.subJobs.forEach(function (sjob) {
                    msg += ' ID.' + sjob.id + ' ' + sjob.status + ') ' + sjob.projectName + '[' + sjob.environmentName + ']\n';
                    if (sjob.error_code) {
                        msg += ' - Error Code:' + sjob.error_code + '\n';
                        msg += ' - Error Message:' + sjob.statusInfo + '\n';
                    }
                    msg += ' - Working Time' + sjob.startTime + ' ~ ' + sjob.endTime + '\n';
                    msg += '\n';
                });
            } else {
                msg += 'JOB ID: ' + job.id + '\n';
                msg += 'Distribution: ' + job.distName + '\n';
                msg += 'Project: ' + job.projectName + '\n';
                msg += 'TargetOS: ' + job.environmentName + '\n';
                msg += 'StartTime: ' + job.startTime + ' ~\n';
            }
            scb(null);
        }, function (scb) {
            if (jobResult) {
                subject = '[JOB-' + job.id + '] Build Failed';
                if (job.projectType !== 'visualstudio-multi') {
                    msg += 'Error: ' + jobResult.message + '\n';
                }
            } else {
                subject = '[JOB-' + job.id + '] Build succeeded';
            }
            scb(null);
        }, function (scb) {
            if (job.approvalId) {
                sendToApprovalRoute(job, subject, msg, scb);
            } else {
                dibs.rpc.messenger.notify('email', subject, targets, msg, scb);
            }
        }
    ], callback);
}


function sendToApprovalRoute(job, subject, message, callback) {
    dibs.rpc.approvalmgr.getApproval(job.approvalId, function (err, approval) {
        if (!err && approval.submissionStatus === 'APPROVED') {
            async.eachLimit(approval.approvalRoutes, 1, function (target, cb) {
                dibs.rpc.messenger.notify('email', subject, target.email, message, function (err) {
                    if (err) {
                        dibs.log.warn('Fail sending email to ' + target.email);
                    }
                    cb(null);
                });
            }, function () {
                callback(null);
            });
        } else {
            dibs.rpc.messenger.notify('email', subject, job.userEmail, message, callback);
        }
    });
}
