/**
 * utils.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 os = require('os');
var crypto = require('crypto');
var exec = require('child_process').exec;
var extfs = require('fs-extra');
var fs = require('fs');
var path = require('path');
var async = require('async');
var Url = require('url');
var http = require('http');
var _ = require('underscore');
var shouldProxy = require('should-proxy');

/**
 * @module lib/utils
 */

/**
 * @callback callback_error
 * @param {error|undefined} error -
 * @memberOf module:lib/utils
 */

/**
 * @callback callback_err_bool
 * @param {error|undefined} error -
 * @param {boolean} boolean -
 * @memberOf module:lib/utils
 */
module.exports.getHostIp = function () {
    var ifaces = os.networkInterfaces();
    var addr = [];
    for (var dev in ifaces) {
        ifaces[dev].forEach(function (details) {
            if (details.family === 'IPv4' && !details.internal) {
                addr.push(details.address);
            }
        });
    }
    if (addr.length === 0) {
        return '127.0.0.1';
    } else {
        return addr[0];
    }
};

module.exports.getHostIps = function () {
    var ifaces = os.networkInterfaces();
    var addr = [];
    for (var dev in ifaces) {
        ifaces[dev].forEach(function (details) {
            if (details.family === 'IPv4' && !details.internal) {
                addr.push(details.address);
            }
        });
    }
    return addr;
};

/**
 * @function isLocalAddr
 * @param {string} addr - addr
 * @returns {string}
 * @memberOf module:lib/utils
 */

module.exports.isLocalAddr = function (addr) {
    var locals = this.getHostIps();
    locals.push('127.0.0.1');
    locals.push('localhost');

    for (var i = 0; i < locals.length; i++) {
        if (locals[i] === addr) {
            return true;
        }
    }

    return false;
};

module.exports.osPathJoin = function (type, pathArray) {
    if (type === 'win32' || type === 'windows') {
        return pathArray.join('\\');
    } else {
        return pathArray.join('/');
    }
};


/**
 * @function getCheckSum
 * @param {string} filePath - filePath
 * @returns {string}
 * @memberOf module:lib/utils
 */


module.exports.getCheckSum = function (filePath, callback) {
    var checksum;
    if (isWindows()) {
        checksum = crypto.createHash('sha256');
        var rs = fs.ReadStream(filePath);

        rs.on('data', function (d) {
            checksum.update(d);
        });

        rs.on('end', function () {
            callback(null, checksum.digest('hex'));
        });
    } else {
        if (callback) {
            exec('shasum -a 256 ' + '"' + filePath + '"', function (err, stdout, stderr) {
                if (err) {
                    callback(err);
                } else if (!stdout) {
                    callback(new Error(stderr), null);
                } else {
                    var output = stdout.toString('utf8').split(/\r\n|[\n\r\u0085\u2028\u2029]/g);
                    var lineNum = output.length - 1;
                    if (!output[lineNum - 1]) {
                        callback(new Error('parse error: ' + output), null);
                    } else {
                        callback(null, output[lineNum - 1].split(' ')[0]);
                    }
                }
            });
        } else {
            checksum = crypto.createHash('sha256');
            var data = fs.readFileSync(filePath);
            checksum.update(data);
            return checksum.digest('hex');
        }
    }
};

/**
 * @function getHomePath
 * @memberOf module:lib/utils
 */


function getHomePath() {
    return process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
}
module.exports.getHomePath = getHomePath;


/**
 * @function getTempDirPath
 * @memberOf module:lib/utils
 */


module.exports.getTempDirPath = function () {
    if (process.platform.indexOf('win') === 0) {
        return process.env.TEMP;
    } else {
        return path.join(getHomePath(), '.dibs', 'tmp');
    }
};


/**
 * @function isWindows
 * @memberOf module:lib/utils
 */

module.exports.isWindows = isWindows;
function isWindows() {
    return (process.platform.indexOf('win') === 0);
}


/**
 * @function isLinux
 * @memberOf module:lib/utils
 */

module.exports.isLinux = function () {
    return (process.platform.indexOf('linux') === 0);
};


/**
 * @function getLocalAppDataPath
 * @memberOf module:lib/utils
 */

module.exports.getLocalAppDataPath = function () {
    if (isWindows()) {
        return process.env.LOCALAPPDATA;
    } else {
        return '';
    }
};


function removePathInternal(path, retryCount, prevError, callback) {
    // check retry count
    if (retryCount > 3) {
        return callback(prevError);
    }

    extfs.remove(path, function (err) {
        if (err) {
            setTimeout(function () {
                removePathInternal(path, retryCount + 1, err, callback);
            }, 2000);
        } else {
            callback(null);
        }
    });
}

function removePathIfExist(path, callback) {
    fs.exists(path, function (exists) {
        if (exists) {
            removePathInternal(path, 0, null, callback);
        } else {
            callback(null);
        }
    });
}
module.exports.removePathIfExist = removePathIfExist;

function makeDirIfNotExist(path, callback) {
    fs.exists(path, function (exists) {
        if (exists) {
            callback(null);
        } else {
            extfs.mkdirp(path, callback);
        }
    });
}
module.exports.makeDirIfNotExist = makeDirIfNotExist;


function retryCheckingFileExists(path, retryCount, callback) {
    // check retry count
    if (retryCount < 0) {
        return callback(false);
    }

    fs.exists(path, function (exists) {
        if (!exists) {
            setTimeout(function () {
                retryCheckingFileExists(path, retryCount - 1, callback);
            }, 2000);
        } else {
            callback(true);
        }
    });
}
module.exports.retryCheckingFileExists = retryCheckingFileExists;


/**
 * @function validateEmail
 * @param {string} email
 * @returns {boolean}
 * @memberOf module:lib/utils
 */
module.exports.validateEmail = function (email) {
    var reExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return reExp.test(email);
};

function genTemp(callback) {
    var rand = genRandom();
    var tempDir = null;
    var home = getHomePath();
    if (isWindows()) {
        // windows ha limitation for file path langth
        tempDir = path.join(home.split("\\")[0], rand);
    } else {
        tempDir = path.join(home, 'temp', rand);
    }
    async.waterfall([
        function (wcb) {
            removePathIfExist(tempDir, wcb);
        },
        function (wcb) {
            extfs.mkdirp(tempDir, wcb);
        }
    ],
        function (err) {
            callback(err, tempDir);
        });
}
module.exports.genTemp = genTemp;


function genRandom() {
    return Math.floor(Math.random() * 100000000).toString();
}
module.exports.genRandom = genRandom;


/**
 * @function getHostIp
 * @memberOf module:lib/utils
 */



function padZero(num, size) {
    var s = '000000000' + num;
    return s.substr(s.length - size);
}

module.exports.generateTimeStamp = function (useMilliSecond) {
    var date = new Date();
    var formattedStr = padZero(date.getFullYear(), 4) +
        padZero((date.getMonth() + 1), 2) +
        padZero(date.getDate(), 2) +
        padZero(date.getHours(), 2) +
        padZero(date.getMinutes(), 2) +
        padZero(date.getSeconds(), 2);
    if (useMilliSecond === true) {
        formattedStr += padZero(date.getMilliseconds(), 3);
    }

    return formattedStr;
};


module.exports.getDateFromTimeStamp = function (timestamp) {
    var d = new Date();
    var t = timestamp;

    d.setFullYear(
        parseInt(t.substring(0, 4), 10) // year
    );
    d.setMonth(
        parseInt(t.substring(4, 6), 10) - 1 // month
    );
    d.setDate(
        parseInt(t.substring(6, 8), 10) // day
    );
    d.setHours(
        parseInt(t.substring(8, 10), 10) // hour
    );
    d.setMinutes(
        parseInt(t.substring(10, 12), 10) // minute
    );
    d.setSeconds(
        parseInt(t.substring(12, 14), 10) // sec
    );

    if (t.length === 17) {
        d.setMilliseconds(
            parseInt(t.substring(14, 17), 10) // milli-sec
        );
    }

    return d;
};


function getHttpProxy() {
    return process.env.http_proxy;
}
function getHttpsProxy() {
    return process.env.https_proxy;
}

function getHttpOption(url) {
    var durl = Url.parse(url);
    var proxy = null;

    var doProxy = shouldProxy(url, {
        no_proxy: process.env.no_proxy
    });

    if (doProxy) {
        if (durl.protocol === 'http:') {
            proxy = getHttpProxy();
        }
        if (durl.protocol === 'https:') {
            proxy = getHttpsProxy();
        }
    }

    var purl = url;

    if (proxy && durl.hostname !== 'localhost' && durl.hostname !== '127.0.0.1') {
        var proxyUrl = Url.parse(proxy);
        purl = {
            host: proxyUrl.hostname,
            port: proxyUrl.port * 1,
            path: purl,
            agent: false
        };
    } else {
        purl = {
            host: durl.hostname,
            port: durl.port * 1,
            path: durl.path,
            agent: false
        };
    }
    return purl;
}

module.exports.getTextFromUrl = function (url, callback) {
    var retryCount = 0;

    async.retry({
        times: 3,
        interval: 10000
    }, function (cb, results) {
        retryCount++;

        getTextFromUrlInternal(url, function (err, data) {
            if (err) {
                console.log(path.basename(url) + ' DOWNLOAD RETRY(' + retryCount + ') caused below :');
                console.log(err);
            }
            cb(err, data);
        });
    }, function (err, data) {
        callback(err, data);
    });
};


function getTextFromUrlInternal(url, callback) {
    var request = http.get(getHttpOption(url), function (response) {
        if (response.statusCode === 200) {
            var contents = '';
            response.on('data', function (data) {
                contents += data;
            });
            response.on('end', function () {
                callback(null, contents);
            });
        } else {
            callback(new Error('get ' + url + ' return code : ' + response.statusCode), null);
        }
    }).on('error', function (e) {
        callback(e, null);
    });

    // abort request after 1 minutes
    request.setTimeout(60 * 1000, function () {
        request.abort();
    });

    request.on('error', function (err) {
        if (err.code === 'ECONNRESET') {
            console.log('timeout occurs!');
        }
    });
}


module.exports.download = function (url, targetDir, callback) {
    var retryCount = 0;
    async.retry({
        times: 3,
        interval: 10000
    }, function (cb, result) {
        retryCount++;
        download_internal(url, targetDir, function (err, data) {
            if (err && err.message.match(/^NETERR : /)) {
                console.log(path.basename(url) + ' DOWNLOAD RETRY(' + retryCount + ') caused below :');
                console.log(err);
                cb(err, {
                    error: err,
                    data: data
                });
            } else {
                cb(null, {
                    error: err,
                    data: data
                });
            }
        });
    }, function (err, data) {
        callback(data.error, data.data);
    });
};


function download_internal(url, targetDir, callback) {
    var urlToks = url.split('/');
    var fullpath = path.join(targetDir, urlToks[urlToks.length - 1]);

    extfs.mkdirp(targetDir, function (err) {
        if (err) {
            callback(err); return;
        }

        var request = http.get(getHttpOption(url), function (response) {
            var downloadSize = 0;

            if (response.statusCode === 200) {
                fs.writeFile(fullpath, '', {
                    flag: 'w'
                }, function (err) {
                    if (err) {
                        callback(err, fullpath);
                    } else {
                        var file = fs.createWriteStream(fullpath, {
                            flags: 'a'
                        }).on('finish', function () {
                            // console.log('[' +  path.basename(url) + '] downsize: ' + downloadSize + ':' + response.headers['content-length']);
                            if (Number(downloadSize) !== Number(response.headers['content-length'])) {
                                callback(new Error('NETERR : different size between writeStream and http.get request'), fullpath);
                            } else {
                                callback(null, fullpath);
                            }
                        });

                        response.on('data', function (chunk) {
                            downloadSize += chunk.length;
                            file.write(chunk);
                        });
                        response.on('end', function () {
                            file.end();
                        });
                    }
                });
            } else {
                callback(new Error('NETERR : get ' + url + ' return code : ' + response.statusCode), fullpath);
            }
        }).on('error', function (e) {
            callback(new Error('NETERR : ' + e.message), fullpath);
        });

        request.setTimeout(60 * 1000, function () {
            request.abort();
        });

        request.on('error', function (err) {
            if (err.code === 'ECONNRESET') {
                console.log('timeout occurs!');
            }
        });
    });
}


module.exports.isURL = function (s) {
    var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
    return regexp.test(s);
};


module.exports.removeArrayDup = function (arr) {
    return arr.reverse().filter(function (e, i, arr) {
        return arr.indexOf(e, i + 1) === -1;
    }).reverse();
};


function readdirRecursiveSync(dir, options) {
    var result = [];
    var files = fs.readdirSync(dir);

    if (options) {
        if (options.cutHead && !options.cutHeadDir) {
            options.cutHeadDir = dir;
        }

        if (options.cutHeadDir) {
            if (options.cutHeadDir[options.cutHeadDir.length - 1] !== path.sep) {
                options.cutHeadDir = options.cutHeadDir + path.sep;
            }

            if (!options.cutHeadRegEx) {
                options.cutHeadRegEx = new RegExp('^' + options.cutHeadDir);
            }
        }
    } else {
        options = {};
    }

    for (var i = 0; i < files.length; i++) {
        var filePath = files[i];
        var name = path.join(dir, filePath);
        if (fs.lstatSync(name).isDirectory() && !fs.lstatSync(name).isSymbolicLink()) {
            if (options && options.showDir) {
                if (options.cutHeadDir) {
                    result.push(name.replace(options.cutHeadDir, '') + path.sep);
                } else {
                    result.push(name + path.sep);
                }
            }
            result = result.concat(readdirRecursiveSync(name, options));
        } else {
            if (options && options.cutHeadDir) {
                result.push(name.replace(options.cutHeadDir, ''));
            } else {
                result.push(name);
            }
        }
    }

    return result;
}
module.exports.readdirRecursiveSync = readdirRecursiveSync;

function readdirRecursive(dir, options, callback) {
    if (options) {
        if (options.cutHead && !options.cutHeadDir) {
            options.cutHeadDir = dir;
        }

        if (options.cutHeadDir) {
            if (options.cutHeadDir[options.cutHeadDir.length - 1] !== path.sep) {
                options.cutHeadDir = options.cutHeadDir + path.sep;
            }

            if (!options.cutHeadRegEx) {
                options.cutHeadRegEx = new RegExp('^' + options.cutHeadDir.replace(/\\/g, '\\\\'));
            }
        }
    } else {
        options = {};
    }

    fs.readdir(dir, function (err, files) {
        if (err) {
            callback(err, []);
        } else {
            async.map(files, function (file, cb) {
                var filePath = path.join(dir, file);
                fs.lstat(filePath, function (err, stats) {
                    if (err) {
                        cb(err, []);
                    } else {
                        if (stats.isDirectory() && !stats.isSymbolicLink()) {
                            readdirRecursive(filePath, options, function (err, list) {
                                if (err) {
                                    cb(err, []);
                                } else {
                                    if (options.showDir) {
                                        cb(null, [filePath + path.sep].concat(list));
                                    } else {
                                        cb(null, list);
                                    }
                                }
                            });
                        } else {
                            cb(null, filePath);
                        }
                    }
                });
            }, function (err, list) {
                var fileList = _.flatten(list);

                if (options.cutHeadRegEx) {
                    fileList = _.map(fileList, function (file) {
                        return file.replace(options.cutHeadRegEx, '');
                    });
                }
                callback(err, fileList);
            });
        }
    });
}
module.exports.readdirRecursive = readdirRecursive;


function findFilesInDir(dir, regExp, callback) {
    async.waterfall([
        function (cb) {
            readdirRecursive(dir, { showDir: true, cutHead: false }, cb);
        },
        function (fileList, cb) {
            var results = [];

            // search files using regluar expression pattern.
            _.each(fileList, function (filePath) {
                var match = regExp.exec(filePath);
                if (match) {
                    results.push(match.input);
                }
            });

            // return matched file list
            cb(null, results);
        }
    ],
    function (err, results) {
        callback(err, results);
    });
}
module.exports.findFilesInDir = findFilesInDir;

function path2string(path_str) {
    var result = path_str;
    if (path.sep === "\\") {
        result = path_str.split(path.sep).join("\\\\");
    }
    return result;
}
module.exports.path2string = path2string;


function convertToMsysPath(src) {
    if (src.indexOf(':') >= 0 || src.indexOf("\\") >= 0) {
        var cPath = src.replace(/[\\]/g, '/');
        var colonIdx = cPath.indexOf(':');
        if (colonIdx >= 0) {
            return '/' + cPath[0].toLowerCase() + cPath.substring(colonIdx + 1);
        } else {
            return cPath;
        }
    } else {
        return src;
    }
}

module.exports.convertToMsysPath = convertToMsysPath;


module.exports.compareVersion = function (srcVer, tarVer) {
    var srcVerArray = srcVer.split('.');
    var tarVerArray = tarVer.split('.');

    var srcLen = srcVerArray.length;
    var tarLen = tarVerArray.length;
    var maxLen = srcLen;
    var gab = null;

    if (srcLen > tarLen) {
        gab = srcLen - tarLen;
        for (var i = 0; i < gab; i++) {
            tarVerArray.push('0');
        }
    } else if (tarLen > srcLen) {
        gab = tarLen - srcLen;
        for (var j = 0; j < gab; j++) {
            srcVerArray.push('0');
        }
        maxLen = tarLen;
    }

    for (var k = 0; k < maxLen; k++) {
        var srcVal = parseInt(srcVerArray[k], 10);
        var tarVal = parseInt(tarVerArray[k], 10);
        if (srcVal > tarVal) {
            return 1;
        } else if (tarVal > srcVal) {
            return -1;
        }
    }

    return 0;
};


module.exports.getTimeString = getTimeString;
function getTimeString(time, format) {
    var t;
    if (time) {
        t = new Date(time);
    } else {
        t = new Date();
    }

    if (format) {
        return t.format(format);
    } else {
        return t.format('YYYY-MM-DD hh:mm:ss.S');
    }
}

module.exports.objectToStringAndType = function (obj) {
    if (_.isBoolean(obj)) {
        return {
            string: obj.toString(),
            type: 'BOOLEAN'
        };
    } else if (_.isNumber(obj)) {
        return {
            string: obj.toString(),
            type: 'NUMBER'
        };
    } else if (_.isDate(obj)) {
        return {
            string: getTimeString(obj),
            type: 'DATE'
        };
    } else if (_.isString(obj)) {
        return {
            string: obj,
            type: 'STRING'
        };
    } else {
        return {
            string: JSON.stringify(obj),
            type: 'JSON'
        };
    }
};

module.exports.stringAndTypeToObj = function (string, type) {
    var obj;
    switch (type) {
    case 'BOOLEAN': obj = (string === 'true');
        break;
    case 'NUMBER': obj = Number(string).valueOf();
        break;
    case 'DATE': obj = new Date(string);
        break;
    case 'STRING': obj = string;
        break;
    case 'JSON': obj = JSON.parse(string.replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r").replace(/\f/g, "\\f"));
        break;
    }
    return obj;
};

module.exports.DBStr = function (string) {
    if (string) {
        if (_.isString(string)) {
            return '\'' + string.replace(/\\/g, '\\\\').replace(/\'/g, "\\\'").replace(/\\"/g, '\\\\"') + '\'';
        } else {
            return '\'' + string.toString().replace(/\\/g, '\\\\').replace(/\'/g, "\\\'").replace(/\\"/g, '\\\\"') + '\'';
        }
    } else {
        return '\'\'';
    }
};


module.exports.includesSpecialChars = function (str) {
    if (!str) {
        return false;
    }

    for (var i = 0; i < str.length; i++) {
        if (str.charCodeAt(i) > 127) {
            return true;
        }
    }

    return false;
};


module.exports.getEnvironmentIdFromName = function (envName, prjType) {
    var dibs = require('../core/dibs.js');
    if (!dibs.plugin) {
        dibs.initialize();
    }

    var exts = dibs.plugin.getExtensions('dibs.base.environment');
    var prjIdList = getEnvironmentIds(prjType);
    var projectEnvs = exts.filter(function (ext) {
        return _.indexOf(prjIdList, ext.id) > -1 && ext.name === envName;
    });

    if (_.size(projectEnvs) === 0) {
        return null;
    }

    return projectEnvs[0].id;
};


function getEnvironmentIds(prjType) {
    var dibs = require('../core/dibs.js');
    if (!dibs.plugin) {
        dibs.initialize();
    }

    var exts = dibs.plugin.getExtensions('dibs.base.projectType');
    var prjExt = _.find(exts, function (ext) {
        return ext.projectType === prjType;
    });
    if (prjExt) {
        return prjExt.environments;
    } else {
        return [];
    }
}


module.exports.getCurrentEnvironments = function (serverType, prjType, callback) {
    var dibs = require('../core/dibs.js');
    if (!dibs.plugin) {
        dibs.initialize();
    }

    var exts = dibs.plugin.getExtensions('dibs.base.environment');
    if (prjType) {
        var prjIdList = getEnvironmentIds(prjType);
        exts = exts.filter(function (ext) {
            return _.indexOf(prjIdList, ext.id) > -1;
        });
    }

    async.filter(exts,
        function (ext, fcb) {
            commonChecker(ext.spec, serverType, function (truth) {
                if (truth) {
                    ext.additionalCheck(null, fcb);
                } else {
                    fcb(false);
                }
            });
        },
        function (result) {
            callback(result);
        });
};


module.exports.getCurrentEnvironmentsByServer = function (server, callback) {
    var dibs = require('../core/dibs.js');
    if (!dibs.plugin) {
        dibs.initialize();
    }

    var exts = dibs.plugin.getExtensions('dibs.base.environment');

    async.filter(exts,
        function (ext, fcb) {
            commonChecker(ext.spec, server.type, function (truth) {
                if (truth) {
                    ext.additionalCheck(server, fcb);
                } else {
                    fcb(false);
                }
            });
        },
        function (result) {
            callback(result);
        });
};


// check dibs.base.environment -> attributes -> spec
function commonChecker(spec, serverType, callback) {
    var result = true;
    if (spec.platform && spec.platform !== os.platform()) {
        result = false;
    }
    if (spec.arch && spec.arch !== os.arch()) {
        result = false;
    }
    if (spec['server-type'] && spec['server-type'] !== serverType) {
        result = false;
    }
    if (result && (spec['os-name'] || spec['os-release'])) {
        getReleaseIdAndNum(function (idNname) {
            if (spec['os-name'] && spec['os-name'] !== idNname[0]) {
                result = false;
            }
            if (spec['os-release'] && spec['os-release'] !== idNname[1]) {
                result = false;
            }
            callback(result);
        });
    } else {
        callback(result);
    }
}


function getReleaseIdAndNum(callback) {
    switch (os.platform()) {
    case 'linux':
        exec("bash -c \"set +e ; . /etc/lsb-release ; . /etc/os-release ; if [ \\$NAME ] ; then echo \\\"\\$NAME|\\$VERSION_ID\\\" ; else echo \\\"\\$DISTRIB_ID|\\$DISTRIB_RELEASE\\\" ; fi \"",function(err,stdout, stderr){
            callback(stdout.trim().split('|'));
        });
        break;
    case 'win32':
        exec('ver', function (err, stdout, stderr) {
            var ver = stdout.split('[')[1].split(' ')[1].split('.');
            ver.pop();
            callback(['Windows', ver.join('.')]);
        });
        break;
    case 'darwin':
        exec('sw_vers -productVersion', function (err, stdout, stderr) {
            callback(['OSX', stdout.trim().split('.').slice(0, 2).join('.')]);
        });
        break;
    default:
        callback(false);
        break;
    }
}

/**
 * @function getDirSizeRecursive
 * @param {string} target directory path
 * @returns {number} size
 * @memberOf module:lib/utils
 */
module.exports.getDirSizeRecursive = getDirSizeRecursive;
function getDirSizeRecursive(target, callback) {
    fs.lstat(target, function (error, stats) {
        if (error) {
            return callback(error, 0);
        } else {
            var total = stats.size || 0;
            if (stats.isDirectory() && !stats.isSymbolicLink()) {
                fs.readdir(target, function (err, files) {
                    if (err) {
                        return callback(err);
                    }
                    async.forEach(files, function (file, cb) {
                        getDirSizeRecursive(path.join(target, file), function (err1, size) {
                            if (!err1) {
                                total += size;
                            }
                            cb(err1);
                        });
                    }, function (err2) {
                        callback(err2, total);
                    });
                });
            } else {
                return callback(error, total);
            }
        }
    });
}

module.exports.sizeof = sizeof;
function sizeof(object) {
    if (object !== null && typeof (object) === 'object') {
        if (Buffer.isBuffer(object)) {
            return object.length;
        }
        else {
            var bytes = 0;
            for (var key in object) {

                if (!Object.hasOwnProperty.call(object, key)) {
                    continue;
                }

                bytes += sizeof(key);
                try {
                    bytes += sizeof(object[key]);
                } catch (ex) {
                    if (ex instanceof RangeError) {
                        // circular reference detected, final result might be incorrect
                        // let's be nice and not throw an exception
                        bytes = 0;
                    }
                }
            }
            return bytes;
        }
    } else if (typeof (object) === 'string') {
        return object.length * 2;
    } else if (typeof (object) === 'boolean') {
        return 4;
    } else if (typeof (object) === 'number') {
        return 8;
    } else {
        return 0;
    }
}
