
var fs    = require("fs");
var util  = require("util");
var iconv = require('iconv-lite');

var CRLF = "\r\n";

var logLevel = {
    'L_noLog'   : -1,
    'L_Low'     : 0,
    'L_Normal'  : 1,
    'L_High'    : 2,
    'L_Extended': 3,
    'L_Full'    : 4
};

var ___logLevel = {
   "-1": 'L_noLog',
    "0": 'L_Low',
    "1": 'L_Normal',
    "2": 'L_High',
    "3": 'L_Extended',
    "4": 'L_Full'
};

var CurrentLogLevel = logLevel.L_Normal;
var ConsoleFile = '';
var colors = {
    default: '0',
    cyan   : '36',
    lcyan  : '1;36',
    red    : '31',
    lred   : '1;31',
    yellow : '33',
    bWhite : '1'
};

var statLogDir  = null;
var logBuffer   = [];
var logTimeOut  = null;
var logDropTime = 100;

Object.defineProperty(global, '__stack', {
    configurable: true,

    get: function () {
        var orig = Error.prepareStackTrace;

        Error.prepareStackTrace = function (_, stack) {
            return stack;
        };

        var err = new Error;

        if (Error.captureStackTrace) {
            Error.captureStackTrace(err, arguments.callee);
        }

        var stack = err.stack;

        Error.prepareStackTrace = orig;

        return stack;
    }
});

var traceStack = global.__stack;

function formatDate(formatDate, formatString) {
    var mcDate = Object.prototype.toString.call(formatDate) === '[object String]';

    if (mcDate) formatDate = formatDate.split('.');

    var yyyy = formatDate.getFullYear();
    var yy = yyyy.toString().substring(2);
    var m = formatDate.getMonth() + 1;
    var mm = m < 10 ? "0" + m : m;
    var d = formatDate.getDate();
    var dd = d < 10 ? "0" + d : d;

    var h = formatDate.getHours();
    var hh = h < 10 ? "0" + h : h;
    var n = formatDate.getMinutes();
    var nn = n < 10 ? "0" + n : n;
    var s = formatDate.getSeconds();
    var ss = s < 10 ? "0" + s : s;

    formatString = formatString.replace(/yyyy/i, yyyy);
    formatString = formatString.replace(/yy/i, yy);
    formatString = formatString.replace(/mm/i, mm);
    formatString = formatString.replace(/m/i, m);
    formatString = formatString.replace(/dd/i, dd);
    formatString = formatString.replace(/d/i, d);
    formatString = formatString.replace(/hh/i, hh);
    formatString = formatString.replace(/h/i, h);
    formatString = formatString.replace(/nn/i, nn);
    formatString = formatString.replace(/n/i, n);
    formatString = formatString.replace(/ss/i, ss);
    formatString = formatString.replace(/s/i, s);

    return formatString;
}

Date.prototype.myFormat = function (format) {
    return formatDate(this, format);
};

function PrintLine(source, title, _this, txt, enableLogWhere) {
    var trace  = getTrace(source[1]);
    var string = '[' + ((title && (typeof title === 'string') && (title.length > 0)) ? (title + ' ') : '');

    if (enableLogWhere == true) {
        var fullTrace = "";

        for (var tt = 1; tt < source.length; tt++) {
            var yy = getTrace(source[tt]);

            fullTrace += util.format("%s:%d\n", yy.file, yy.lineno);
        }

        string += util.format("%s]: %s\n", trace.timestamp, util.format.apply(_this, [txt])) + fullTrace;
    } else if ((enableLogWhere == false) || (enableLogWhere == undefined)) {
        string += util.format("%s]: %s", trace.timestamp, util.format.apply(_this, [txt]));
    }

    return string;
}

function getTrace(call) {
    var curDate = new Date();

    return {
        file: call.getFileName(),
        lineno: call.getLineNumber(),
        timestamp: curDate.myFormat('dd:mm:yyyy hh:nn:ss')
    };
}

function colourise(colourCode, string, background) {
    var backcolor = (!background) ? "\x1B[0m" : background;

    return "\x1B[" + colourCode + "m" + string + backcolor;
}

function ExtractPath(Path) {
    for (var i = Path.length - 1; i >= 0; i--) {
        if ((Path[i] === '\\') || (Path[i] === '/')) {
            return Path.slice(0, i);
        }
    }

    return Path;
}

function ExtractFileName(Path) {
    var count = 0;

    for (var i = Path.length - 1; i >= 0; i--) {
        count++;

        if ((Path[i] === '\\') || (Path[i] === '/')) {
            return Path.slice(i + 1, i + count);
        }
    }

    return Path;
}

function LogToFile(text, file) {
    function __log() {
        var out        = "";
        var _path      = ExtractPath(file);
        var _file      = ExtractFileName(file);
        var _logBuffer = logBuffer;

        logBuffer = [];

        fs.stat(checkDir(_path), function (err) {
            if (err) {
                process.stdout.write(colourise(colors.red, '  >> [err]: Path not found: ' + _path) + CRLF);
            }

            while (_logBuffer.length){
                out += _logBuffer.shift() + CRLF;
            }

            fs.appendFile(err ? './' + _file : ConsoleFile, out, function () {
                logTimeOut = null;

                out = "";
            });
        });
    }

    if (file) {
        if (text !== '' && ConsoleFile.length) {
            logBuffer.push(text);

            if (logDropTime !== -1){
                if (!logTimeOut) {
                    logTimeOut = setTimeout(__log, logDropTime);
                }
            } else {
                __log();
            }
        }
    }
}

function checkDir(path) {
    var _path  = ExtractPath(path);

    if (statLogDir !== _path){
        try {
            statLogDir = fs.statSync(_path).isDirectory() ? _path : "./";

            if (statLogDir !== './') {
                fs.mkdirSync(_path);
            }
        } catch (e){
            process.stdout.write(colourise(colors.red, '  >> [err]: Path not found: ' + _path) + CRLF);
            
            statLogDir = './';
        }
    }

    return statLogDir;
}

function writeLogToFile(fileName) {
    ConsoleFile = checkDir(fileName) + ExtractFileName(fileName);
}

function writeLog(stack, title, color, arg, immediately) {
    var _logLevel = CurrentLogLevel;
    var _txt = arg[0];
    var _logWhere = false;
    var _logFile = undefined;

    if (Object.prototype.toString.call(arg[1]) == '[object Object]') {
        if (arg[1].logLevel) _logLevel = arg[1].logLevel;
        if (arg[1].logWhere) _logWhere = arg[1].logWhere;
        if (arg[1].logFile)  _logFile  = arg[1].logFile;
    } else {
        if (arg.length > 1) {
            if (___logLevel[arg[arg.length - 1]]) {
                _logLevel = arg[arg.length - 1];
            }
        }

        if (arg[1] && (Object.prototype.toString.call(arg[1]) == '[object Boolean]')) {
            _logWhere = arg[1];
        }
    }

    if (!_logLevel || parseInt(_logLevel) <= parseInt(CurrentLogLevel)) {
        var text = PrintLine(stack, title, this, _txt, _logWhere);

        process.stdout.write(colourise(color, text) + CRLF);

        if (immediately) {
            try {
                fs.appendFileSync(ConsoleFile || './CRASH.log', text + CRLF);
            } catch (e){}
        } else
        if (_logFile) {
            LogToFile(text, _logFile)
        } else {
            LogToFile(text, ConsoleFile);
        }
    }
}

function AddCustomLogFile(param) {} // todo: make this?

module.exports = {
    log : function () { writeLog.apply(this, [traceStack, 'log ', colors.default, arguments]) },
    info: function () { writeLog.apply(this, [traceStack, 'info', colors.lcyan, arguments]) },
    err : function () { writeLog.apply(this, [traceStack, 'err ', colors.lred, arguments]) },
    error:function () { writeLog.apply(this, [traceStack, 'err ', colors.lred, arguments]) },
    warn: function () { writeLog.apply(this, [traceStack, 'warn', colors.yellow, arguments]) },
    important: function () { writeLog.apply(this, [traceStack, '  ! ', colors.bWhite, arguments]) },
    wrt: {
        write: function () {
            writeLog.apply(this, [traceStack, 'wrt ', colors.bWhite, arguments]);
        }
    },

    immediately: function () { writeLog.apply(this, [traceStack, 'ERR ', colors.lred, arguments, true]) },

    DublicateToFile: writeLogToFile,

    AddCustomLofFile: AddCustomLogFile,

    logLevel: logLevel,
    SetDropTime: function (time) { logDropTime = time; },
    SetLogLevel: function (ll) { CurrentLogLevel = ll; },
    GetLogLevel: function () { return CurrentLogLevel; }
};

// Colours
//
// \x1B[0m    все атрибуты по умолчанию
// \x1B[1m    жирный шрифт (интенсивный цвет)
// \x1B[2m    полу яркий цвет (тёмно-серый, независимо от цвета)
// \x1B[4m    подчеркивание
// \x1B[5m    мигающий
// \x1B[7m    реверсия (знаки приобретают цвет фона, а фон -- цвет знаков)
//
// \x1B[22m    установить нормальную интенсивность
// \x1B[24m    отменить подчеркивание
// \x1B[25m    отменить мигание
// \x1B[27m    отменить реверсию
//
// \x1B[30    чёрный цвет знаков
// \x1B[31    красный цвет знаков
// \x1B[32    зелёный цвет знаков
// \x1B[33    желтый цвет знаков
// \x1B[34    синий цвет знаков
// \x1B[35    фиолетовый цвет знаков
// \x1B[36    цвет морской волны знаков
// \x1B[37    серый цвет знаков
//
// \x1B[40    чёрный цвет фона
// \x1B[41    красный цвет фона
// \x1B[42    зелёный цвет фона
// \x1B[43    желтый цвет фона
// \x1B[44    синий цвет фона
// \x1B[45    фиолетовый цвет фона
// \x1B[46    цвет морской волны фона
// \x1B[47    серый цвет фона


