/*!A cross-platform build utility based on Lua
 *
 * 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.
 *
 * Copyright (C) 2015-present, TBOOX Open Source Group.
 *
 * @author      OpportunityLiu, ruki
 * @file        stdfile.c
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME    "stdfile"
#define TB_TRACE_MODULE_DEBUG   (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"
#ifdef TB_CONFIG_OS_WINDOWS
#   include <io.h>
#   include "iscygpty.c"
#else
#    include <stdio.h>
#    include <unistd.h>
#    include <sys/types.h>
#    include <sys/stat.h>
#endif

/* //////////////////////////////////////////////////////////////////////////////////////
 * macros
 */

// the singleton type of stdfile
#define XM_IO_STDFILE_STDIN      (TB_SINGLETON_TYPE_USER + 1)
#define XM_IO_STDFILE_STDOUT     (TB_SINGLETON_TYPE_USER + 2)
#define XM_IO_STDFILE_STDERR     (TB_SINGLETON_TYPE_USER + 3)

/* //////////////////////////////////////////////////////////////////////////////////////
 * private implementation
 */
static tb_size_t xm_io_stdfile_isatty(tb_size_t type)
{
    tb_bool_t answer = tb_false;
#if defined(TB_CONFIG_OS_WINDOWS)
    DWORD  mode;
    HANDLE console_handle;
    switch (type)
    {
    case XM_IO_FILE_TYPE_STDIN: console_handle = GetStdHandle(STD_INPUT_HANDLE); break;
    case XM_IO_FILE_TYPE_STDOUT: console_handle = GetStdHandle(STD_OUTPUT_HANDLE); break;
    case XM_IO_FILE_TYPE_STDERR: console_handle = GetStdHandle(STD_ERROR_HANDLE); break;
    }
    answer = GetConsoleMode(console_handle, &mode);
    /* we cannot call is_cygpty for stdin, because it will cause io.readable is always true
     * https://github.com/xmake-io/xmake/issues/2504#issuecomment-1170130756
     */
    if (!answer && type != XM_IO_FILE_TYPE_STDIN)
        answer = is_cygpty(console_handle);
#else
    switch (type)
    {
    case XM_IO_FILE_TYPE_STDIN: answer = isatty(fileno(stdin)); break;
    case XM_IO_FILE_TYPE_STDOUT: answer = isatty(fileno(stdout)); break;
    case XM_IO_FILE_TYPE_STDERR: answer = isatty(fileno(stderr)); break;
    }
#endif

    if (answer) type |= XM_IO_FILE_FLAG_TTY;
    return type;
}

// @see https://github.com/xmake-io/xmake/issues/2580
static tb_void_t xm_io_stdfile_init_buffer(tb_size_t type)
{
#if !defined(TB_CONFIG_OS_WINDOWS)
    struct stat stats;
    tb_int_t size = BUFSIZ;
    if (fstat(fileno(stdout), &stats) != -1)
        size = stats.st_blksize;
    setvbuf(stdout, tb_null, _IOLBF, size);
#endif
}

static xm_io_file_t* xm_io_stdfile_new(lua_State* lua, tb_size_t type)
{
    // init stdfile
    tb_stdfile_ref_t fp = tb_null;
    switch (type)
    {
    case XM_IO_FILE_TYPE_STDIN:
        fp = tb_stdfile_input();
        break;
    case XM_IO_FILE_TYPE_STDOUT:
        fp = tb_stdfile_output();
        break;
    case XM_IO_FILE_TYPE_STDERR:
        fp = tb_stdfile_error();
        break;
    }

    // new file
    xm_io_file_t* file = (xm_io_file_t*)lua_newuserdata(lua, sizeof(xm_io_file_t));
    tb_assert_and_check_return_val(file, tb_null);

    // init file
    file->u.std_ref  = fp;
    file->stream     = tb_null;
    file->fstream    = tb_null;
    file->type       = xm_io_stdfile_isatty(type);
    file->encoding   = TB_CHARSET_TYPE_UTF8;

    // init stdio buffer
    xm_io_stdfile_init_buffer(type);

    // init the read/write line cache buffer
    tb_buffer_init(&file->rcache);
    tb_buffer_init(&file->wcache);
    return file;
}

/* //////////////////////////////////////////////////////////////////////////////////////
 * interfaces
 */

// io.stdfile(stdin: 1, stdout: 2, stderr: 3)
tb_int_t xm_io_stdfile(lua_State* lua)
{
    // check
    tb_assert_and_check_return_val(lua, 0);

    // get std type
    tb_long_t type = (tb_long_t)lua_tointeger(lua, 1);

    /* push a new stdfile
     *
     * @note we need to ensure that it is a singleton in the external lua script, and will only be created once, e.g. io.stdin, io.stdout, io.stderr
     */
    xm_io_file_t* file = xm_io_stdfile_new(lua, type);
    if (file) return 1;
    else xm_io_return_error(lua, "invalid stdfile type!");
}


