"use strict";

const lua      = require('./lua.js');
const lauxlib  = require('./lauxlib.js');
const llimit   = require('./llimit.js');

const strftime = require('strftime');

/* options for ANSI C 89 (only 1-char options) */
const L_STRFTIMEC89 = lua.to_luastring("aAbBcdHIjmMpSUwWxXyYZ%", true);

/* options for ISO C 99 and POSIX */
const L_STRFTIMEC99 = lua.to_luastring("aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%||EcECExEXEyEYOdOeOHOIOmOMOSOuOUOVOwOWOy", true);  /* two-char options */

/* options for Windows */
const L_STRFTIMEWIN = lua.to_luastring("aAbBcdHIjmMpSUwWxXyYzZ%||#c#x#d#H#I#j#m#M#S#U#w#W#y#Y", true);  /* two-char options */

// const LUA_STRFTIMEOPTIONS = L_STRFTIMEWIN;
const LUA_STRFTIMEOPTIONS = L_STRFTIMEC89;
// const LUA_STRFTIMEOPTIONS = L_STRFTIMEC99;

const setfield = function(L, key, value) {
    lua.lua_pushinteger(L, value);
    lua.lua_setfield(L, -2, lua.to_luastring(key, true));
};

const setallfields = function(L, time, utc) {
    setfield(L, "sec",   !utc ? time.getSeconds()  : time.getUTCSeconds());
    setfield(L, "min",   !utc ? time.getMinutes()  : time.getUTCMinutes());
    setfield(L, "hour",  !utc ? time.getHours()    : time.getUTCHours());
    setfield(L, "day",   !utc ? time.getDate()     : time.getUTCDate());
    setfield(L, "month", !utc ? time.getMonth()    : time.getUTCMonth());
    setfield(L, "year",  !utc ? time.getFullYear() : time.getUTCFullYear());
    setfield(L, "wday",  !utc ? time.getDay()      : time.getUTCDay());
    let now = new Date();
    setfield(L, "yday", Math.floor((now - (new Date(now.getFullYear(), 0, 0))) / (1000 * 60 * 60 * 24)));
    // setboolfield(L, "isdst", time.get);
};

const L_MAXDATEFIELD = (llimit.MAX_INT / 2);

const getfield = function(L, key, d, delta) {
    let t = lua.lua_getfield(L, -1, lua.to_luastring(key, true));  /* get field and its type */
    let res = lua.lua_tointegerx(L, -1);
    if (res === false) {  /* field is not an integer? */
        if (t !== lua.LUA_TNIL)  /* some other value? */
            return lauxlib.luaL_error(L, lua.to_luastring(`field '${key}' is not an integer`), true);
        else if (d < 0)  /* absent field; no default? */
            return lauxlib.luaL_error(L, lua.to_luastring(`field '${key}' missing in date table`), true);
        res = d;
    }
    else {
        if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD))
            return lauxlib.luaL_error(L, lua.to_luastring(`field '${key}' is out-of-bound`), true);
        res -= delta;
    }
    lua.lua_pop(L, 1);
    return res;
};

const checkoption = function(L, conv, buff) {
    let option = LUA_STRFTIMEOPTIONS;
    let oplen = 1;  /* length of options being checked */
    for (; option.length > 0 && oplen <= conv.length; option = option.slice(oplen)) {
        if (option[0] === '|'.charCodeAt(0))  /* next block? */
            oplen++;  /* will check options with next length (+1) */
        else if (lua.to_jsstring(conv.slice(0, oplen)) === lua.to_jsstring(option.slice(0, oplen))) {  /* match? */
            buff.push(...conv.slice(0, oplen)); /* copy valid option to buffer */
            return conv.slice(oplen);  /* return next item */
        }
    }
    lauxlib.luaL_argerror(L, 1, lua.lua_pushliteral(L, `invalid conversion specifier '%${conv}'`, conv));
};

/* maximum size for an individual 'strftime' item */
const SIZETIMEFMT = 250;


const os_date = function(L) {
    let s = lauxlib.luaL_optlstring(L, 1, "%c");
    let t = lauxlib.luaL_opt(L, l_checktime, 2, new Date().getTime() / 1000) * 1000;
    let stm = new Date(t);
    let utc = false;
    if (s[0] === '!'.charCodeAt(0)) {  /* UTC? */
        utc = true;
        s = s.slice(1);  /* skip '!' */
    }

    if (stm === null)  /* invalid date? */
        lauxlib.luaL_error(L, lua.to_luastring("time result cannot be represented in this installation", true));

    if (lua.to_jsstring(s) === "*t") {
        lua.lua_createtable(L, 0, 9);  /* 9 = number of fields */
        setallfields(L, stm, utc);
    } else {
        let cc;  /* buffer for individual conversion specifiers */
        let b = [];
        while (s.length > 0) {
            cc = ['%'.charCodeAt(0)];

            if (s[0] !== '%'.charCodeAt(0)) {  /* not a conversion specifier? */
                b.push(s[0]);
                s = s.slice(1);
            } else {
                s = s.slice(1);  /* skip '%' */
                s = checkoption(L, s, cc);  /* copy specifier to 'cc' */
                b.push(...(lua.to_luastring(strftime(lua.to_jsstring(cc), stm))));
            }
        }
        lua.lua_pushstring(L, b);
    }
    return 1;
};

const os_time = function(L) {
    let t = new Date();
    if (!lua.lua_isnoneornil(L, 1))  /* called with arg */{
        lauxlib.luaL_checktype(L, 1, lua.LUA_TTABLE);  /* make sure table is at the top */
        lua.lua_settop(L, 1);
        t.setSeconds(getfield(L, "sec", 0, 0));
        t.setMinutes(getfield(L, "min", 0, 0));
        t.setHours(getfield(L, "hour", 12, 0));
        t.setDate(getfield(L, "day", -1, 0));
        t.setMonth(getfield(L, "month", -1, 1));
        t.setFullYear(getfield(L, "year", -1, 0));
        setallfields(L, t);
    }

    lua.lua_pushinteger(L, Math.floor(t / 1000));
    return 1;
};

const l_checktime = function(L, arg) {
    let t = lauxlib.luaL_checkinteger(L, arg);
    // lauxlib.luaL_argcheck(L, t, arg, lua.to_luastring("time out-of-bounds"));
    return t;
};

const os_difftime = function(L) {
    let t1 = l_checktime(L, 1);
    let t2 = l_checktime(L, 2);
    lua.lua_pushnumber(L, new Date(t1) - new Date(t2));
    return 1;
};

const syslib = {
    "date": os_date,
    "difftime": os_difftime,
    "time": os_time
};

// Only with Node
if (process && process.exit && process.env && process.uptime) {
    const os_exit = function(L) {
        let status;
        if (lua.lua_isboolean(L, 1))
            status = (lua.lua_toboolean(L, 1) ? 0 : 1);
        else
            status = lauxlib.luaL_optinteger(L, 1, 0);
        if (lua.lua_toboolean(L, 2))
            lua.lua_close(L);
        if (L) process.exit(status);  /* 'if' to avoid warnings for unreachable 'return' */
        return 0;
    };

    const os_getenv = function(L) {
        lua.lua_pushliteral(L, process.env[lua.to_jsstring(lauxlib.luaL_checkstring(L, 1))]);  /* if NULL push nil */
        return 1;
    };

    const os_clock = function(L) {
        lua.lua_pushnumber(L, process.uptime());
        return 1;
    };

    syslib.clock = os_clock;
    syslib.exit = os_exit;
    syslib.getenv = os_getenv;
}


// Only with Node
if (!WEB) {

    const fs = require('fs');
    const tmp = require('tmp');
    const child_process = require('child_process');

    // TODO: on POSIX system, should create the file
    const lua_tmpname = function() {
        return tmp.tmpNameSync();
    };

    const os_remove = function(L) {
        let filename = lauxlib.luaL_checkstring(L, 1);
        try {
            if (fs.lstatSync(lua.to_jsstring(filename)).isDirectory()) {
                fs.rmdirSync(lua.to_jsstring(filename));
            } else {
                fs.unlinkSync(lua.to_jsstring(filename));
            }
        } catch (e) {
            return lauxlib.luaL_fileresult(L, false, filename, e);
        }
        return lauxlib.luaL_fileresult(L, true);
    };

    const os_rename = function(L) {
        let fromname = lua.to_jsstring(lauxlib.luaL_checkstring(L, 1));
        let toname = lua.to_jsstring(lauxlib.luaL_checkstring(L, 2));
        try {
            fs.renameSync(fromname, toname);
        } catch (e) {
            return lauxlib.luaL_fileresult(L, false, false, e);
        }
        return lauxlib.luaL_fileresult(L, true);
    };

    const os_tmpname = function(L) {
        let name = lua_tmpname();
        if (!name)
            return lauxlib.luaL_error(L, lua.to_luastring("unable to generate a unique filename"));
        lua.lua_pushstring(L, lua.to_luastring(name));
        return 1;
    };

    syslib.remove = os_remove;
    syslib.rename = os_rename;
    syslib.tmpname = os_tmpname;

    const os_execute = function(L) {
        let cmd = lauxlib.luaL_optstring(L, 1, null);
        if (cmd !== null) {
            try {
                child_process.execSync(
                    lua.to_jsstring(cmd),
                    {
                        stdio: [process.stdin, process.stdout, process.stderr]
                    }
                );
            } catch (e) {
                return lauxlib.luaL_execresult(L, e);
            }

            return lauxlib.luaL_execresult(L, null);
        } else {
            try {
                child_process.execSync(
                    lua.to_jsstring(cmd),
                    {
                        stdio: [process.stdin, process.stdout, process.stderr]
                    }
                );
                lua.lua_pushboolean(L, 1);
            } catch (e) {
                lua.lua_pushboolean(L, 0);
            }

            return 1;
        }
    };

    syslib.execute = os_execute;

}

const luaopen_os = function(L) {
    lauxlib.luaL_newlib(L, syslib);
    return 1;
};

module.exports.luaopen_os = luaopen_os;