/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2013, 2014 Damien P. George
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include "py/compile.h"
#include "py/runtime.h"
#include "py/repl.h"
#include "py/gc.h"
#include "py/frozenmod.h"
#include "py/mphal.h"
#if MICROPY_HW_ENABLE_USB
#include "irq.h"
#include "usb.h"
#endif
#include "shared/readline/readline.h"
#include "shared/runtime/pyexec.h"
#include "genhdr/mpversion.h"

int pyexec_system_exit = 0;

#if MICROPY_REPL_INFO
STATIC bool repl_display_debugging_info = 0;
#endif

#define EXEC_FLAG_PRINT_EOF             (1 << 0)
#define EXEC_FLAG_ALLOW_DEBUGGING       (1 << 1)
#define EXEC_FLAG_IS_REPL               (1 << 2)
#define EXEC_FLAG_SOURCE_IS_RAW_CODE    (1 << 3)
#define EXEC_FLAG_SOURCE_IS_VSTR        (1 << 4)
#define EXEC_FLAG_SOURCE_IS_FILENAME    (1 << 5)
#define EXEC_FLAG_SOURCE_IS_READER      (1 << 6)
#define EXEC_FLAG_SOURCE_IS_STR         (1 << 7)

// parses, compiles and executes the code in the lexer
// frees the lexer before returning
// EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output
// EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code
// EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile)
STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, mp_uint_t exec_flags) {
    int ret = 0;
    #if MICROPY_REPL_INFO
    uint32_t start = 0;
    #endif

    #ifdef MICROPY_BOARD_BEFORE_PYTHON_EXEC
    MICROPY_BOARD_BEFORE_PYTHON_EXEC(input_kind, exec_flags);
    #endif

    // by default a SystemExit exception returns 0
    pyexec_system_exit = 0;

    nlr_buf_t nlr;
    nlr.ret_val = NULL;
    if (nlr_push(&nlr) == 0) {
        mp_obj_t module_fun;
        #if MICROPY_MODULE_FROZEN_MPY
        if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) {
            // source is a raw_code object, create the function
            const mp_frozen_module_t *frozen = source;
            mp_module_context_t *ctx = m_new_obj(mp_module_context_t);
            ctx->module.globals = mp_globals_get();
            ctx->constants = frozen->constants;
            module_fun = mp_make_function_from_raw_code(frozen->rc, ctx, NULL);
        } else
        #endif
        {
            #if MICROPY_ENABLE_COMPILER
            mp_lexer_t *lex;
            if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) {
                const vstr_t *vstr = source;
                lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0);
            } else if (exec_flags & EXEC_FLAG_SOURCE_IS_STR) {
                lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_,
                    source, strlen(source), 0);
            } else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
                lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source);
            } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) {
                lex = mp_lexer_new_from_file(source);
            } else {
                lex = (mp_lexer_t *)source;
            }
            // source is a lexer, parse and compile the script
            qstr source_name = lex->source_name;
            mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
            module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL);
            #else
            mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported"));
            #endif
        }

        // execute code
        mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us
        #if MICROPY_REPL_INFO
        start = mp_hal_ticks_ms();
        #endif
        mp_call_function_0(module_fun);
        mp_hal_set_interrupt_char(-1); // disable interrupt
        mp_handle_pending(true); // handle any pending exceptions (and any callbacks)
        nlr_pop();
        ret = 1;
        if (exec_flags & EXEC_FLAG_PRINT_EOF) {
            mp_hal_stdout_tx_strn("\x04", 1);
        }
    } else {
        // uncaught exception
        mp_hal_set_interrupt_char(-1); // disable interrupt
        mp_handle_pending(false); // clear any pending exceptions (and run any callbacks)

        if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
            const mp_reader_t *reader = source;
            reader->close(reader->data);
        }

        // print EOF after normal output
        if (exec_flags & EXEC_FLAG_PRINT_EOF) {
            mp_hal_stdout_tx_strn("\x04", 1);
        }

        // check for SystemExit
        if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
            // at the moment, the value of SystemExit is unused
            ret = pyexec_system_exit;
        } else {
            mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val));
            ret = 0;
        }
    }

    #if MICROPY_REPL_INFO
    // display debugging info if wanted
    if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) {
        mp_uint_t ticks = mp_hal_ticks_ms() - start; // TODO implement a function that does this properly
        printf("took " UINT_FMT " ms\n", ticks);
        // qstr info
        {
            size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes;
            qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes);
            printf("qstr:\n  n_pool=%u\n  n_qstr=%u\n  "
                "n_str_data_bytes=%u\n  n_total_bytes=%u\n",
                (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes);
        }

        #if MICROPY_ENABLE_GC
        // run collection and print GC info
        gc_collect();
        gc_dump_info();
        #endif
    }
    #endif

    if (exec_flags & EXEC_FLAG_PRINT_EOF) {
        mp_hal_stdout_tx_strn("\x04", 1);
    }

    #ifdef MICROPY_BOARD_AFTER_PYTHON_EXEC
    MICROPY_BOARD_AFTER_PYTHON_EXEC(input_kind, exec_flags, nlr.ret_val, &ret);
    #endif

    return ret;
}

#if MICROPY_ENABLE_COMPILER

void pyexec_event_repl_init(void) {
    MP_STATE_VM(repl_line) = vstr_new(32);

    mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION);
    mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE);
    mp_hal_stdout_tx_str("\r\n");
    #if MICROPY_PY_BUILTINS_HELP
    mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n");
    #endif
    vstr_reset(MP_STATE_VM(repl_line));
}

STATIC int pyexec_raw_repl_process_char(int c) {
    if(c != CHAR_CTRL_D && c != '\r') {
        vstr_add_byte(MP_STATE_VM(repl_line), c);
        return 0;
    }

    int ret = parse_compile_execute(MP_STATE_VM(repl_line),
        MP_PARSE_SINGLE_INPUT,
        EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL |
            EXEC_FLAG_SOURCE_IS_VSTR);
    if (ret & PYEXEC_FORCED_EXIT) {
        return ret;
    }

    vstr_reset(MP_STATE_VM(repl_line));
    return 0;
}

uint8_t pyexec_repl_active;
int pyexec_event_repl_process_char(int c) {
    pyexec_repl_active = 1;
    int res = pyexec_raw_repl_process_char(c);
    pyexec_repl_active = 0;
    return res;
}

int pyexec_repl_execute(char const *line) {
    pyexec_repl_active = 1;

    int ret = parse_compile_execute(line,
        MP_PARSE_SINGLE_INPUT,
        EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL |
            EXEC_FLAG_SOURCE_IS_STR);
    if (ret & PYEXEC_FORCED_EXIT) {
        return ret;
    }

    pyexec_repl_active = 0;
    return 0;
}

MP_REGISTER_ROOT_POINTER(vstr_t * repl_line);

#endif // MICROPY_ENABLE_COMPILER

int pyexec_file(const char *filename) {
    return parse_compile_execute(filename, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_FILENAME);
}

int pyexec_file_if_exists(const char *filename) {
    #if MICROPY_MODULE_FROZEN
    if (mp_find_frozen_module(filename, NULL, NULL) == MP_IMPORT_STAT_FILE) {
        return pyexec_frozen_module(filename);
    }
    #endif
    if (mp_import_stat(filename) != MP_IMPORT_STAT_FILE) {
        return 1; // success (no file is the same as an empty file executing without fail)
    }
    return pyexec_file(filename);
}

#if MICROPY_MODULE_FROZEN
int pyexec_frozen_module(const char *name) {
    void *frozen_data;
    int frozen_type;
    mp_find_frozen_module(name, &frozen_type, &frozen_data);

    switch (frozen_type) {
        #if MICROPY_MODULE_FROZEN_STR
        case MP_FROZEN_STR:
            return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, 0);
        #endif

        #if MICROPY_MODULE_FROZEN_MPY
        case MP_FROZEN_MPY:
            return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_RAW_CODE);
        #endif

        default:
            printf("could not find module '%s'\n", name);
            return false;
    }
}
#endif

#if MICROPY_REPL_INFO
mp_obj_t pyb_set_repl_info(mp_obj_t o_value) {
    repl_display_debugging_info = mp_obj_get_int(o_value);
    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj, pyb_set_repl_info);
#endif
