/*
 * Copyright (C) 2018-2020 Western Digital Corporation or its affiliates
 * Copyright (C) 2017-2018 Wearable, Inc.
 * Copyright (C) 2000-2012 leJOS Contributors
 * Copyright (C) 2000 Jose H. Solorzano and TinyVM Contributors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is “Incompatible With Secondary Licenses”,
 * as defined by the Mozilla Public License, v. 2.0.
 */

/**
 * nativeemul.c
 * Native method handling for unix_impl (emulation).
 */



#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include "types.h"
#include "trace.h"
#include "constants.h"
#include "specialsignatures.h"
#include "specialclasses.h"
#include "stack.h"
#include "memory.h"
#include "threads.h"
#include "classes.h"
#include "language.h"
#include "configure.h"
#include "interpreter.h"
#include "exceptions.h"
#include "platform_config.h"
#include "platform_hooks.h"
#include "poll.h"

/**
 * NOTE: The technique is not the same as that used in TinyVM.
 * The return value indicates the impact of the call on the VM
 * system. EXEC_CONTINUE normal return the system should return to the return
 * address provided by the VM. EXEC_RUN The call has modified the value of
 * VM PC and this should be used to restart execution. EXEC_RETRY The call
 * needs to be re-tried (typically for a GC failure), all global state
 * should be left intact, the PC has been set appropriately.
 *
 */
int dispatch_native(nvm_state_t* pState, uint16_t signature, STACKWORD * paramBase)
{
    STACKWORD p0 = paramBase[0];
    
    switch (signature) {
            // org.naniteproject.VM
        case isAssignableFrom_4Ljava_3lang_3Class_2_5Z:
        {
            ClassRecord* src = (ClassRecord*) word2obj(paramBase[1]);
            ClassRecord* dst = (ClassRecord*) word2obj(p0);

            if (src == NULL) {
                return nvm_throw_new_exception(pState, JAVA_LANG_NULLPOINTEREXCEPTION);
            }

            push_word_cur(pState, is_assignable(pState, get_class_number(pState, src), get_class_number(pState, dst)));
            break;
        }
        case getClassNumber_4Ljava_3lang_3Class_2_5I:
        {
            if (word2obj(p0) == NULL) {
                return nvm_throw_new_exception(pState, JAVA_LANG_NULLPOINTEREXCEPTION);
            }

            push_word_cur(pState, get_class_number(pState, (ClassRecord*) word2obj(p0)));
            break;
        }
        case isInterface_4_5Z:
            push_word_cur(pState, is_interface((ClassRecord*) word2obj(p0)));
            break;
        case isArray_4_5Z:
            push_word_cur(pState, is_array_class((ClassRecord*) word2obj(p0)));
            break;
        case isPrimitive_4_5Z:
            push_word_cur(pState, is_primitive(get_class_number(pState, (ClassRecord*) word2obj(p0))));
            break;
        case getClass_4_5Ljava_3lang_3Class_2:
            push_ref_cur(pState, ptr2ref(get_class_record(pState, nvm_object_class_index(word2obj(p0)))));
            break;
        case getComponentType_4_5Ljava_3lang_3Class_2:
        {
            ClassRecord* rec = (ClassRecord*) word2obj(p0);
            if (is_array_class(rec)) {
                push_ref_cur(pState, ptr2ref(get_class_record(pState, get_element_class(rec))));
            } else {
                push_ref_cur(pState, ptr2ref(NULL));
            }
            break;
        }
        case getSuperclass_4_5Ljava_3lang_3Class_2:
        {
            ClassRecord* rec = (ClassRecord*) word2obj(p0);
            if (is_interface(rec) || is_primitive(get_class_number(pState, rec))) {
                push_ref_cur(pState, ptr2ref(NULL));
            } else {
                push_ref_cur(pState, ptr2ref(get_class_record(pState, rec->parentClass)));
            }
            break;
        }
        case firmwareExceptionHandler_4Ljava_3lang_3Throwable_2II_5V:
            firmware_exception_handler((Throwable *)word2obj(p0), paramBase[1], paramBase[2]);
            break;
        case createStackTrace_4Ljava_3lang_3Thread_2Ljava_3lang_3Object_2_5_1I:
        {
            Object *trace = create_stack_trace(pState, (Thread*)word2obj(p0), word2obj(paramBase[1]));
            if (trace == NULL) {
                return EXEC_RETRY;
            }
            push_ref_cur(pState, obj2ref(trace));
        }
            break;
        case suspendThread_4Ljava_3lang_3Object_2_5V:
            suspend_thread(pState, (Thread*) word2obj(p0));
            break;
        case resumeThread_4Ljava_3lang_3Object_2_5V:
            resume_thread(pState, (Thread*) word2obj(p0));
            break;
        case newArrayFromInstance_4Ljava_3lang_3Object_2I_5Ljava_3lang_3Object_2:
        {
            if (word2obj(p0) == NULL) {
                return nvm_throw_new_exception(pState, JAVA_LANG_NULLPOINTEREXCEPTION);
            }
            if (!nvm_object_is_array(word2obj(p0))) {
                return nvm_throw_new_exception(pState, JAVA_LANG_CLASSCASTEXCEPTION);
            }
            if ((int) paramBase[1] < 0) {
                return nvm_throw_new_exception(pState, JAVA_LANG_NEGATIVEARRAYSIZEEXCEPTION);
            }
            push_ref_cur(pState,
                          obj2ref(new_single_array(pState, nvm_object_class_index(word2obj(p0)), (int) paramBase[1], false)));
            break;
        }

            // org.naniteproject.NaniteEvent
        case registerEvent_4Ljava_3lang_3Object_2I_5Z:
            if (word2obj(p0) == NULL) {
                return nvm_throw_new_exception(pState, JAVA_LANG_NULLPOINTEREXCEPTION);
            }
            if (paramBase[1] >= MAX_EVENTS) {
                return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
            }
            push_word_cur(pState, register_event(pState, word2obj(p0), paramBase[1]));
            break;
        case pollEvent_4I_5Z:
            if (p0 >= MAX_EVENTS) {
                return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
            }
            push_word_cur(pState, poll_event(pState, p0));
            break;
        case unregisterEvent_4I_5Z:
            if (p0 >= MAX_EVENTS) {
                return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
            }
            push_word_cur(pState, unregister_event(pState, p0));
            break;
            
            // org.naniteproject.NaniteOutputStream
        case writeChar_4C_5V: {
            // Allow platforms to override the definition of putchar without involving a macro mess
#undef putchar
            putchar((char) p0);
            fflush(stdout);
            break;
        }
        case flushOutputStream_4_5V: {
            extern void realFlush(void);
            realFlush();
            break;
        }
            
            // java.lang.System
        case arraycopy_4Ljava_3lang_3Object_2ILjava_3lang_3Object_2II_5V:
            return arraycopy(pState, word2obj(p0), (int)paramBase[1], word2obj(paramBase[2]), (int)paramBase[3], (int)paramBase[4]);
        case currentTimeMillis_4_5J: {
            TIMESTAMP now = atomic_time_get(pState->sys_time);
            push_word_cur(pState, now >> 32);
            push_word_cur(pState, (now & 0xFFFFFFFF));
            break;
        }
        case nanoTime_4_5J: // FIXME: this isn't nanotime!
        {
            TIMESTAMP ns = atomic_time_get(pState->sys_time);
            push_word_cur(pState, ns >> 32);
            push_word_cur(pState, (STACKWORD)ns);
        }
            break;
        case getDataAddress_4Ljava_3lang_3Object_2_5I:
            if (nvm_object_is_array(word2obj(p0))) {
                push_ref_cur (pState, ptr2ref((byte *) array_start(word2obj(p0))));
            } else {
                push_ref_cur (pState, ptr2ref((byte *) fields_start(word2obj(p0))));
            }
            break;
        case gc_4_5V:
            // Restartable garbage collection
            return garbage_collect(pState);
            
            // java.lang.Runtime
        case version_4_5I:
            push_word_cur(pState, NVM_VERSION);
            break;
        case freeMemory_4_5J:
            push_word_cur(pState, 0);
            push_word_cur(pState, getHeapFree(pState));
            break;
        case totalMemory_4_5J:
            push_word_cur(pState, 0);
            push_word_cur(pState, getHeapSize(pState));
            break;
            
            // java.lang.Shutdown
        case halt0_4I_5V:
            schedule_request(pState, REQUEST_EXIT);
            break;

            // java.lang.Throwable
        case addTraceCause_4II_5V:
            if (p0 < sizeof(pState->trace.cause)/sizeof(pState->trace.cause[0])) {
                STACKWORD p1 = paramBase[1];
                pState->trace.cause[p0].class = p1;
            }
            break;
        case addTraceFrame_4IIII_5V:
            if (p0 < sizeof(pState->trace.cause)/sizeof(pState->trace.cause[0])) {
                STACKWORD p1 = paramBase[1];
                if (p1 < sizeof(pState->trace.cause[0].frame)/sizeof(STACKWORD)) {
                    STACKWORD p2 = paramBase[2];
                    STACKWORD p3 = paramBase[3];
                    pState->trace.cause[p0].frame[p1] = (p2 << 16) | (p3 & 0xFFFF);
                }
            }
            break;

            // java.lang.Thread
        case start_4_5V:
            // Create thread, allow for instruction restart
            return init_thread(pState, (Thread *) word2obj(p0));
        case yield_4_5V:
            schedule_request(pState, REQUEST_SWITCH_THREAD);
            break;
        case sleep_4J_5V: {
            if ((int) p0 < 0) {
                return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
            }
            TIMESTAMP sleepTime = (((TIMESTAMP) p0) << 32) | (TIMESTAMP) paramBase[1];
            sleep_thread(pState, sleepTime, 0);
            schedule_request(pState, REQUEST_SWITCH_THREAD);
            break;
        }
        case sleepUntil_4J_5V: {
            if ((int) p0 < 0) {
                return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
            }
            TIMESTAMP sleepTime = (((TIMESTAMP) p0) << 32) | (TIMESTAMP) paramBase[1];
            sleep_thread(pState, sleepTime, 1);
            schedule_request(pState, REQUEST_SWITCH_THREAD);
            break;
        }
        case currentThread_4_5Ljava_3lang_3Thread_2:
            push_ref_cur(pState, ptr2ref(pState->current_thread));
            break;
        case getPriority_4_5I:
            push_word_cur(pState, get_thread_priority((Thread *) word2obj(p0)));
            break;
        case setPriority_4I_5V:
        {
            STACKWORD p = (STACKWORD) paramBase[1];
            
            if (p > MAX_PRIORITY || p < MIN_PRIORITY) {
                return nvm_throw_new_exception(pState, JAVA_LANG_ILLEGALARGUMENTEXCEPTION);
            } else {
                set_thread_priority(pState, (Thread *) word2obj(p0), p);
            }
        }
            break;
        case interrupt_4_5V:
            interrupt_thread(pState, (Thread *) word2obj(p0));
            break;
        case interrupted_4_5Z:
        {
            JBYTE i = pState->current_thread->interruptState != INTERRUPT_CLEARED;
            
            pState->current_thread->interruptState = INTERRUPT_CLEARED;
            push_word_cur(pState, i);
        }
            break;
        case isInterrupted_4_5Z:
            push_word_cur(pState, ((Thread *) word2obj(p0))->interruptState
                      != INTERRUPT_CLEARED);
            break;
        case setDaemon_4Z_5V:
            ((Thread *) word2obj(p0))->daemon = (JBYTE) paramBase[1];
            break;
        case isDaemon_4_5Z:
            push_word_cur(pState, ((Thread *) word2obj(p0))->daemon);
            break;
        case join_4_5V:
            join_thread(pState, (Thread *) word2obj(p0), 0);
            break;
        case join_4J_5V:
            join_thread(pState, (Thread*)(void*)word2obj(p0), paramBase[2]);
            break;
        case exitThread_4_5V:
            pState->current_thread->state = DEAD;
            schedule_request(pState, REQUEST_SWITCH_THREAD);
            break;

            // java.lang.Object
        case cloneObject_4Ljava_3lang_3Object_2_5Ljava_3lang_3Object_2:
        {
            Object *newObj = clone(pState, word2obj(p0));
            if (newObj == NULL) {
                return EXEC_RETRY;
            }
            push_ref_cur(pState, obj2ref(newObj));
        }
            break;
        case notify_4_5V:
            return monitor_notify(pState, word2obj(p0), false);
        case notifyAll_4_5V:
            return monitor_notify(pState, word2obj(p0), true);
        case wait_4J_5V: {
            TIMESTAMP sleepTime = (((TIMESTAMP) paramBase[1]) << 32) | (TIMESTAMP) paramBase[2];
            return monitor_wait(pState, word2obj(p0), sleepTime);
        }
            // java.lang.Double
        case doubleToRawLongBits_4D_5J:	// Fall through
        case longBitsToDouble_4J_5D:
            push_word_cur(pState, p0);
            push_word_cur(pState, paramBase[1]);
            break;
            // java.lang.Float
        case floatToRawIntBits_4F_5I:	// Fall through
        case intBitsToFloat_4I_5F:
            push_word_cur(pState, p0);
            break;
            
        default:
            return dispatch_platform(pState, signature, paramBase);
    }
    return EXEC_CONTINUE;
}


