/**
 * git.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
**/

/**
 * Tizen git control module
 * @module models/tizen-project/git-control
 */

var path = require('path');
var kill = require('tree-kill');
var os = require('os');
var _ = require('underscore');

var Process = require('../dibs.core/process.js');

module.exports.clone = clone;
module.exports.pull = pull;
module.exports.fetch = fetch;
module.exports.checkout = checkout;
module.exports.reset = reset;
module.exports.configGet = getConfig;
module.exports.getCommitId = getCommitId;
module.exports.getlog = getlog;
module.exports.submodule = submodule;
module.exports.revParse = revParse;
module.exports.tag = tag;


function clone(gitRepoPath, options, callback) {
    var workPath = options.workPath ? options.workPath : process.cwd();
    var gitBranch = options.gitBranch;

    var targetDir = null;
    if (options.targetDir) {
        // project git cache path
        targetDir = options.targetDir;
    } else {
        targetDir = path.join(workPath, path.basename(gitRepoPath).split('.')[0]);
    }

    // invoke process
    var errorLog = 'git clone  ' + gitRepoPath + ' ' + targetDir + ' -b ' + gitBranch + ' => ';
    var timeout = null;
    var timeoutsec = 20 * 60; // 20 minutes
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'clone', '--progress', gitRepoPath, targetDir];

    } else {
        bin = 'git';
        opt = ['clone', '--progress', gitRepoPath, targetDir];
    }

    if (gitBranch) {
        opt.push('-b');
        opt.push(gitBranch);
    }

    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
                if (timeout) {
                    clearTimeout(timeout);
                }
                timeout = setTimeout(function () {
                    errorLog += 'Request Timeout ' + timeoutsec + ' second!';
                    kill(proc.pid, 'SIGKILL');
                }, timeoutsec * 1000);
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
                if (timeout) {
                    clearTimeout(timeout);
                }
                timeout = setTimeout(function () {
                    errorLog += 'Request Timeout ' + timeoutsec + ' second!';
                    kill(proc.pid, 'SIGKILL');
                }, timeoutsec * 1000);
            },
            onExit: function (code) {
                clearTimeout(timeout);
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog), targetDir);
                } else {
                    callback(null, targetDir);
                }
            }
        });

    timeout = setTimeout(function () {
        errorLog += 'Request Timeout ' + timeoutsec + ' second!';
        kill(proc.pid, 'SIGKILL');
    }, timeoutsec * 1000);
    return proc;
}


function fetch(gitRepo, refSpec, options, callback) {
    var workPath = options.workPath ? options.workPath : process.cwd();
    var errorLog = 'git fetch ' + gitRepo + ' ' +
        (refSpec ? refSpec : '') + ' => ';

    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'fetch', gitRepo];
    } else {
        bin = 'git';
        opt = ['fetch', gitRepo];
    }

    if (refSpec) {
        opt.push(refSpec);
    } else if (options.fetchOpts) {
        opt.push(options.fetchOpts);
    }
    opt = _.flatten(opt, true);

    // invoke process
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null);
                }
            }
        });

    return proc;
}




function revParse(refSpec, options, callback) {
    var workPath = options.workPath ? options.workPath : process.cwd();

    var errorLog = 'git rev-parse ' + refSpec + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'rev-parse', refSpec];
    } else {
        bin = 'git';
        opt = ['rev-parse', refSpec];
    }

    var commitId = '';

    // invoke process
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                commitId = line;

                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null, commitId);
                }
            }
        });

    return proc;
}


function checkout(branchName, options, callback) {
    // bracnName can be commit id.
    var workPath = options.workPath ? options.workPath : process.cwd();
    var checkoutOpts = options.checkoutOpts ? options.checkoutOpts : [];

    var errorLog = 'git checkout  ' + branchName + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'checkout', branchName, checkoutOpts];
    } else {
        bin = 'git';
        opt = ['checkout', branchName, checkoutOpts];
    }
    opt = _.flatten(opt, true);

    // invoke process
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null);
                }
            }
        });

    return proc;
}


function pull(gitRepo, refSpec, options, callback) {
    var workPath = options.workPath ? options.workPath : process.cwd();
    var errorLog = 'git pull ' + gitRepo + ' ' + (refSpec ? refSpec : '') + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        if (refSpec) {
            opt = ['/C', 'git', 'pull', gitRepo, refSpec];
        } else {
            opt = ['/C', 'git', 'pull', gitRepo];
        }
    } else {
        bin = 'git';
        if (refSpec) {
            opt = ['pull', gitRepo, refSpec];
        } else {
            opt = ['pull', gitRepo];
        }
    }

    // invoke process
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null);
                }
            }
        });

    return proc;
}


function reset(commitId, options, callback) {
    var workPath = options.workPath ? options.workPath : process.cwd();
    var errorLog = 'git reset --hard ' + commitId + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'reset', '--hard', commitId];
    } else {
        bin = 'git';
        opt = ['reset', '--hard', commitId];
    }

    // invoke process
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null);
                }
            }
        });

    return proc;
}


function getConfig(key, options, callback) {
    var value = '';
    var workPath = options.workPath ? options.workPath : process.cwd();
    var errorLog = 'git config --get ' + key + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'config', '--get', key];
    } else {
        bin = 'git';
        opt = ['config', '--get', key];
    }

    // invoke process
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                value += line;
                errorLog += line;
            },
            onStderr: function (line) {
                errorLog += line;
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog), null);
                } else if (value === '') {
                    callback(new Error('Cannot get \'git config --get ' + key + '\':' + errorLog), null);
                } else {
                    callback(null, value);
                }
            }
        });

    return proc;
}


function getCommitId(workPath, callback) {
    var commitId = null;

    // invoke process
    var errorLog = 'git log -1 => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'log', '-1'];
    } else {
        bin = 'git';
        opt = ['log', '-1'];
    }
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                if (line.indexOf('commit') === 0 && line.split(' ').length === 2) {
                    commitId = line.split(' ')[1];
                }
                errorLog += line;
            },
            onStderr: function (line) {
                errorLog += line;
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog), null);
                } else if (commitId === null) {
                    callback(new Error('Cannot get commit id from log:' + errorLog), null);
                } else {
                    callback(null, commitId);
                }
            }
        });

    return proc;
}

function getlog(workPath, since, until, callback) {
    var gitLog = null;

    // invoke process
    var variation = since + '..' + until;
    var errorLog = 'git log ' + variation + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'log', variation];
    } else {
        bin = 'git';
        opt = ['log', variation];
    }
    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                gitLog += line + '\n';
                errorLog += line;
            },
            onStderr: function (line) {
                errorLog += line;
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog), null);
                } else if (gitLog === null) {
                    callback(new Error('Cannot get git log:' + errorLog), null);
                } else {
                    callback(null, gitLog);
                }
            }
        });

    return proc;
}


function submodule(command, commandOpts, options, callback) {
    // git submodule update
    var workPath = options.workPath ? options.workPath : process.cwd();

    // invoke process
    var errorLog = 'git submodule ' + command + ' => ';
    var timeout = null;
    var timeoutsec = 20 * 60;
    var bin = null;
    var opt = [];

    // git submodule init
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'submodule'];
    } else {
        bin = 'git';
        opt = ['submodule'];
    }
    opt = _.union(opt, [command], commandOpts);

    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
                if (timeout) {
                    clearTimeout(timeout);
                }
                timeout = setTimeout(function () {
                    errorLog += 'Request Timeout ' + timeoutsec + ' second!';
                    kill(proc.pid, 'SIGKILL');
                }, timeoutsec * 1000);
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
                if (timeout) {
                    clearTimeout(timeout);
                }
                timeout = setTimeout(function () {
                    errorLog += 'Request Timeout ' + timeoutsec + ' second!';
                    kill(proc.pid, 'SIGKILL');
                }, timeoutsec * 1000);
            },
            onExit: function (code) {
                clearTimeout(timeout);
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null);
                }
            }
        });

    timeout = setTimeout(function () {
        errorLog += 'Request Timeout ' + timeoutsec + ' second!';
        kill(proc.pid, 'SIGKILL');
    }, timeoutsec * 1000);
    return proc;
}



function tag(gitCommit, tagName, options, callback) {
    var workPath = options.workPath ? options.workPath : process.cwd();
    var tagOptions = options.tagOptions ? options.tagOptions : [];

    // invoke process
    var errorLog = 'git tag  -a ' + tagName + ' ' + gitCommit + ' => ';
    var bin = '';
    var opt = [];
    if (os.platform() === 'win32') {
        bin = 'cmd.exe';
        opt = ['/C', 'git', 'tag', '-a', tagName, gitCommit, tagOptions];

    } else {
        bin = 'git';
        opt = ['tag', '-a', tagName, gitCommit, tagOptions];
    }
    opt = _.flatten(opt, true);

    var proc = Process.create(bin, opt,
        {
            cwd: workPath,
            env: process.env
        },
        {
            onStdout: function (line) {
                errorLog += line;
                if (options.onStdout) {
                    options.onStdout(line);
                }
            },
            onStderr: function (line) {
                errorLog += line;
                if (options.onStderr) {
                    options.onStderr(line);
                }
            },
            onExit: function (code) {
                if (code !== 0) {
                    callback(new Error('Process exited with code ' + code + ':' + errorLog));
                } else {
                    callback(null);
                }
            }
        });

    return proc;
}
