/*
 * QEMU System Emulator
 *
 * Copyright (c) 2003-2008 Fabrice Bellard
 *
 * 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 "sysemu/tcg.h"
#include "sysemu/cpus.h"
#include "qemu/bitmap.h"
#include "tcg/tcg.h"
#include "exec/tb-hash.h"
#include "accel/tcg/translate-all.h"

#include "uc_priv.h"


int64_t cpu_icount_to_ns(int64_t icount)
{
    // return icount << atomic_read(&timers_state.icount_time_shift);
    // from configure_icount(QemuOpts *opts, Error **errp)
    /* 125MIPS seems a reasonable initial guess at the guest speed.
       It will be corrected fairly quickly anyway.  */
    // timers_state.icount_time_shift = 3;

    return icount << 3;
}

bool cpu_is_stopped(CPUState *cpu)
{
    return cpu->stopped;
}

/* return the time elapsed in VM between vm_start and vm_stop.  Unless
 * icount is active, cpu_get_ticks() uses units of the host CPU cycle
 * counter.
 */
int64_t cpu_get_ticks(void)
{
    return cpu_get_host_ticks();
}

/* Return the monotonic time elapsed in VM, i.e.,
 * the time between vm_start and vm_stop
 */
int64_t cpu_get_clock(void)
{
    return get_clock();
}

static bool cpu_can_run(CPUState *cpu)
{
    if (cpu->stop) {
        return false;
    }
    if (cpu_is_stopped(cpu)) {
        return false;
    }
    return true;
}

static void cpu_handle_guest_debug(CPUState *cpu)
{
    cpu->stopped = true;
}

static int tcg_cpu_exec(struct uc_struct *uc)
{
    int r;
    bool finish = false;

    while (!uc->exit_request) {
        CPUState *cpu = uc->cpu;

        //qemu_clock_enable(QEMU_CLOCK_VIRTUAL,
        //                  (cpu->singlestep_enabled & SSTEP_NOTIMER) == 0);
        if (cpu_can_run(cpu)) {
            uc->quit_request = false;
            uc->size_recur_mem = 0;
            r = cpu_exec(uc, cpu);

            // quit current TB but continue emulating?
            if (uc->quit_request && !uc->stop_request) {
                // reset stop_request
                uc->stop_request = false;

                // resume cpu
                cpu->halted = 0;
                cpu->exit_request = 0;
                cpu->exception_index = -1;
                cpu_resume(cpu);
            } else if (uc->stop_request) {
                //printf(">>> got STOP request!!!\n");
                finish = true;
                break;
            }

            // save invalid memory access error & quit
            if (uc->invalid_error) {
                // printf(">>> invalid memory accessed, STOP = %u!!!\n", env->invalid_error);
                finish = true;
                break;
            }

            // printf(">>> stop with r = %x, HLT=%x\n", r, EXCP_HLT);
            if (r == EXCP_DEBUG) {
                cpu_handle_guest_debug(cpu);
                break;
            }
            if (r == EXCP_HLT) {
                //printf(">>> got HLT!!!\n");
                finish = true;
                break;
            }
        } else if (cpu->stop || cpu->stopped) {
            // printf(">>> got stopped!!!\n");
            break;
        }
    }
    uc->exit_request = 0;
    uc->cpu->exit_request = 0;
    uc->cpu->icount_decr_ptr->u16.high = 0;
    uc->cpu->tcg_exit_req = 0;

    return finish;
}

void cpu_resume(CPUState *cpu)
{
    cpu->stop = false;
    cpu->stopped = false;
}

static void qemu_tcg_init_vcpu(CPUState *cpu)
{
    /*
     * Initialize TCG regions--once. Now is a good time, because:
     * (1) TCG's init context, prologue and target globals have been set up.
     * (2) qemu_tcg_mttcg_enabled() works now (TCG init code runs before the
     *     -accel flag is processed, so the check doesn't work then).
     */
    tcg_region_init(cpu->uc->tcg_ctx);

    cpu->created = true;
}

void qemu_init_vcpu(CPUState *cpu)
{
    cpu->nr_cores = 1;
    cpu->nr_threads = 1;
    cpu->stopped = true;

    qemu_tcg_init_vcpu(cpu);

    return;
}

void cpu_stop_current(struct uc_struct *uc)
{
    if (uc->cpu) {
        uc->cpu->stop = false;
        uc->cpu->stopped = true;
        cpu_exit(uc->cpu);
    }
}



static inline gboolean uc_exit_invalidate_iter(gpointer key, gpointer val, gpointer data) 
{
    uint64_t exit = *((uint64_t*)key);
    uc_engine *uc = (uc_engine*)data;
    
    if (exit != 0) {
        // Unicorn: Why addr - 1?
        // 
        // 0: INC ecx
        // 1: DEC edx <--- We put exit here, then the range of TB is [0, 1)
        //
        // While tb_invalidate_phys_range invalides [start, end)
        //
        // This function is designed to used with g_tree_foreach
        uc->uc_invalidate_tb(uc, exit - 1, 1);
    }

    return false;
}

void resume_all_vcpus(struct uc_struct* uc)
{
    CPUState *cpu = uc->cpu;
    cpu->halted = 0;
    cpu->exit_request = 0;
    cpu->exception_index = -1;
    cpu_resume(cpu);
    /* static void qemu_tcg_cpu_loop(struct uc_struct *uc) */
    cpu->created = true;
    while (true) {
        if (tcg_cpu_exec(uc)) {
            break;
        }
    }

    // clear the cache of the exits address, since the generated code
    // at that address is to exit emulation, but not for the instruction there.
    // if we dont do this, next time we cannot emulate at that address
    if (uc->use_exits) {
        g_tree_foreach(uc->ctl_exits, uc_exit_invalidate_iter, (void*)uc);
    } else {
        uc_exit_invalidate_iter((gpointer)&uc->exits[uc->nested_level - 1], NULL, (gpointer)uc);
    }

    cpu->created = false;
}

void vm_start(struct uc_struct* uc)
{
    resume_all_vcpus(uc);
}
