/**
 * filesystem.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 os = require('os');
var fs = require('fs');
var extfs = require('fs-extra');
var path = require('path');
var async = require('async');
var _ = require('underscore');

var utils = require('../../lib/utils.js');
var Process = require('./process.js');
var DError = require('../../core/exception.js');


module.exports.copy = copyFileRecursive;
module.exports.copyFileSync = copyFileSync;
module.exports.walkDirectory = walkDirectory;
module.exports.move = moveFileRecursive;
module.exports.remove = removeFileRecursive;

module.exports.createEmptyDir = createEmptyDirectory;
module.exports.createDirIfNotExists = createDirectoryIfNotExists;


function copyFileRecursive(source, target, option, callback) {
    copyFileWithCpCommand(source, target, option, function (err) {
        if (err && os.platform() !== 'win32' && option.hardlink) {
            // try again using "no-hardlink"
            var newOpt = _.clone(option);
            newOpt.hardlink = false;
            copyFileWithCpCommand(source, target, newOpt, callback);
        } else {
            callback(err);
        }
    });
}


function copyFileWithCpCommand(source, target, option, callback) {
    // option can be ommitted
    if (typeof (option) === 'function') {
        callback = option;
        option = {};
    }

    if (!fs.existsSync(source)) {
        callback(new DError('FS001', { path: source }));
        return;
    }

    if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
        if (fs.readdirSync(source).length > 0) {
            copySubFilesWithCpCommand(source, target, option, callback);
        } else {
            callback(null);
        }
    } else {
        var cmd = '';
        var args = [];
        if (os.platform() === 'win32') {
            var newSource = utils.path2string(source);
            var newTarget = utils.path2string(target);
            cmd = 'cmd.exe';
            if (fs.statSync(source).isDirectory()) {
                args = ['/C', 'xcopy', newSource, newTarget, '/E', '/I', '/K', '/S', '/Y', '/H'];
            } else {
                args = ['/C', 'copy', newSource, newTarget, '/Y'];
            }
        } else if (os.platform() === 'darwin') {
            if (option.hardlink) {
                cmd = 'rsync';
                args = ['--archive', '--link-dest=' + source, source, target];
            } else {
                cmd = 'cp';
                args = ['-af', source, target];
            }
        } else {
            cmd = 'cp';
            if (option.hardlink) {
                args = ['-rfl', source, target];
            } else {
                args = ['-af', source, target];
            }
        }
        var outMsg = '';
        Process.create(cmd, args, {}, {
            onStdout: function (line) {
                outMsg += (line + '\n');
            },
            onStderr: function (line) {
                outMsg += (line + '\n');
            },
            onExit: function (code) {
                if (code === 0) {
                    callback(null);
                } else {
                    callback(new DError('FS002', {
                        cmd: cmd,
                        args: args.join(' '),
                        msg: outMsg
                    }));
                }
            }
        });
    }
}


function copySubFilesWithCpCommand(source, target, option, callback) {
    var cmd = '';
    var args = [];
    if (os.platform() === 'win32') {
        var newSource = utils.path2string(source);
        var newTarget = utils.path2string(target);
        cmd = 'cmd.exe';
        // use robocopy to resolve file copy issue that occurs when its length is over 256.
        args = ['/C', 'robocopy', newSource, newTarget, '/E', '/NJS', '/NJH', '/NP', '/NC'];
    } else if (os.platform() === 'darwin') {
        cmd = '/bin/sh';
        if (option.hardlink) {
            args = ['-c', 'rsync --archive --link-dest=' + path.join(source, '*') + ' ' + (source + path.sep) + ' ' + target];
        } else {
            args = ['-c', 'cp -af ' + path.join(source, '*') + ' ' + target];
        }
    } else {
        cmd = '/bin/sh';
        if (option.hardlink) {
            args = ['-c', 'cp -rfl ' + path.join(source, '*') + ' ' + target];
        } else {
            args = ['-c', 'cp -af ' + path.join(source, '*') + ' ' + target];
        }
    }
    var outMsg = '';
    Process.create(cmd, args, {}, {
        onStdout: function (line) {
            outMsg += (line + '\n');
        },
        onStderr: function (line) {
            outMsg += (line + '\n');
        },
        onExit: function (code) {
            if (code === 0) {
                callback(null);
            } else {
                if (os.platform() === 'win32') {
                    // robocopy exit code olny #16 is serious error
                    if (code < 16) {
                        callback(null);
                    } else {
                        callback(new DError('FS002', {
                            cmd: cmd,
                            args: args.join(' '),
                            msg: outMsg
                        }));
                    }
                } else {
                    callback(new DError('FS002', {
                        cmd: cmd,
                        args: args.join(' '),
                        msg: outMsg
                    }));
                }
            }
        }
    });
}


function copyFileSync(src, dst) {
    // create directory if needed
    var dirname = path.dirname(dst);
    extfs.mkdirsSync(dirname);

    // copy file
    copyFileSyncInternal(src, dst);
}


function copyFileSyncInternal(srcFile, destFile) {
    var BUF_LENGTH;
    var buff;
    var bytesRead;
    var fdr;
    var fdw;
    var pos;
    BUF_LENGTH = 64 * 1024;
    buff = new Buffer(BUF_LENGTH);
    fdr = fs.openSync(srcFile, 'r');
    fdw = fs.openSync(destFile, 'w');
    bytesRead = 1;
    pos = 0;
    while (bytesRead > 0) {
        bytesRead = fs.readSync(fdr, buff, 0, BUF_LENGTH, pos);
        fs.writeSync(fdw, buff, 0, bytesRead);
        pos += bytesRead;
    }
    fs.closeSync(fdr);
    return fs.closeSync(fdw);
}


function walkDirectory(dir, done) {
    var results = [];
    fs.readdir(dir, function (err, list) {
        if (err) return done(err);
        var i = 0;
        (function next() {
            var file = list[i++];
            if (!file) return done(null, results);
            file = path.resolve(dir, file);
            fs.stat(file, function (err, stat) {
                if (stat && stat.isDirectory()) {
                    walkDirectory(file, function (err, res) {
                        results.push(file);
                        results = results.concat(res);
                        next();
                    });
                } else {
                    results.push(file);
                    next();
                }
            });
        })();
    });
}


function moveFileRecursive(source, target, callback) {
    moveFileWithMvCommand(source, target, callback);
}


function moveFileWithMvCommand(source, target, callback) {

    if (!fs.existsSync(source)) {
        callback(new DError('FS003', {
            path: source
        }));
        return;
    }

    if (fs.existsSync(target)) {
        callback(new DError('FS004', {
            path: target
        }));
        return;
    }

    var cmd = '';
    var args = [];
    if (os.platform() === 'win32') {
        var newSource = utils.path2string(source);
        var newTarget = utils.path2string(target);
        cmd = 'cmd.exe';
        args = ['/C', 'move', '/Y', newSource, newTarget];
    } else {
        cmd = 'mv';
        args = [source, target];
    }

    var outMsg = '';
    Process.create(cmd, args, {}, {
        onStdout: function (line) {
            outMsg += (line + '\n');
        },
        onStderr: function (line) {
            outMsg += (line + '\n');
        },
        onExit: function (code) {
            if (code === 0) {
                callback(null);
            } else {
                callback(new DError('FS002', {
                    cmd: cmd,
                    args: args.join(' '),
                    msg: outMsg
                }));
            }
        }
    });
}


function removeFileRecursive(source, callback) {
    //retry 3 times to remove files
    async.retry(3,
        function (cb, result) {
            removeFileWithRmCommand(source, function (err) {
                if (!err) {
                    cb(err, null);
                } else {
                    // if error, try again after 5 sec
                    setTimeout(function () {
                        cb(err, null);
                    }, 5000);
                }
            });
        }, function (err, result) {
            callback(err);
        });
}


function removeFileWithRmCommand(source, callback) {
    if (!fs.existsSync(source)) {
        callback(new DError('FS005', {
            path: source
        }));
        return;
    }

    var cmd = '';
    var args = [];
    if (os.platform() === 'win32') {
        var newSource = utils.path2string(source);
        var stat = fs.statSync(newSource);
        if (stat.isDirectory()) {
            cmd = 'cmd.exe';
            args = ['/C', 'rmdir', '/S', '/Q', newSource];
        } else {
            cmd = 'cmd.exe';
            args = ['/C', 'del', '/F', '/S', '/Q', newSource];
        }
    } else {
        cmd = 'rm';
        args = ['-rf', source];
    }

    var outMsg = '';
    Process.create(cmd, args, {}, {
        onStdout: function (line) {
            outMsg += (line + '\n');
        },
        onStderr: function (line) {
            outMsg += (line + '\n');
        },
        onExit: function (code) {
            if (code === 0) {
                callback(null);
            } else {
                callback(new DError('FS002', {
                    cmd: cmd,
                    args: args.join(' '),
                    msg: outMsg
                }));
            }
        }
    });
}


function createEmptyDirectory(dirPath, callback) {
    fs.exists(dirPath, function (exists) {
        if (exists) {
            extfs.remove(dirPath, function (err) {
                if (err) {
                    callback(err);
                } else {
                    extfs.mkdirs(dirPath, function (err1) {
                        callback(err1);
                    });
                }
            });
        } else {
            extfs.mkdirs(dirPath, function (err) {
                callback(err);
            });
        }
    });
}


function createDirectoryIfNotExists(dirPath, callback) {
    fs.exists(dirPath, function (exists) {
        if (exists) {
            callback(null);
        } else {
            extfs.mkdirs(dirPath, function (err) {
                callback(err);
            });
        }
    });
}
