/*
 * 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.
 */

#include "classes.h"
#include "language.h"
#include "constants.h"
#include "trace.h"
#include "memory.h"
#include "platform_hooks.h"
#ifndef _THREADS_H
#define _THREADS_H

#define NEW              0 // Just been created
#define DEAD             1 // run() has exited
#define STARTED          2 // start() has been called but we haven't run yet
#define RUNNING          3 // We're running!
#define MON_WAITING      4 // Trying to enter a synchronized block
#define CONDVAR_WAITING  5 // Someone called wait() on us in a synchronized block.
#define SLEEPING         6 // ZZZZZzzzzzzzz
#define JOIN             7 // Waiting for another thread to exit
#define SYSTEM_WAITING   8 // Waiting on a system var
#define SUSPENDED        0x80 // Or with the above to suspend

#define INTERRUPT_CLEARED    0
#define INTERRUPT_REQUESTED  1
#define INTERRUPT_GRANTED    2

// These values must match the statics in Thread.java
#define MIN_PRIORITY  1
#define NORM_PRIORITY 5
#define MAX_PRIORITY  10

#define SF_SIZE (sizeof(StackFrame))

#define stackframe_array_ptr(state)   (ref2ptr(state->current_thread->stackFrameArray))
#define stack_array_ptr(state)        (ref2ptr(state->current_thread->stackArray))
#define is_reference_array_ptr(state) (ref2ptr(state->current_thread->isReferenceArray))
#define stackframe_array(state)       ((StackFrame*)(void*)array_start(stackframe_array_ptr(state)))
#define current_stackframe(state)     (stackframe_array(state) + (byte)(state->current_thread->stackFrameIndex))
#define stack_array(state)            ((STACKWORD *)(void*)(array_start(stack_array_ptr(state))))
#define is_reference_array(state)     ((JBYTE *) (array_start(is_reference_array_ptr(state)))
#define get_sync(state, obj) (nvm_object_class_index((Object *)(obj)) == JAVA_LANG_CLASS ? state->static_sync_base + ((ClassRecord *)(void*)(obj) - get_class_base(state)) : &(((Object *)(obj))->sync))

/**
 * A stack frame record
 */
typedef struct S_StackFrame
{
    REFERENCE methodRecord;
    // Object's monitor if method is synchronized
    REFERENCE monitor;
    // The following field is constant for a given stack frame.
    REFERENCE localsBase;
    // The following fields only need to be assigned to on switch_thread.
    REFERENCE stackTop;
    REFERENCE pc;
} StackFrame;

/**
 * Sets thread state to SLEEPING.
 * Thread should be switched immediately after calling this method.
 */
static inline void sleep_thread (nvm_state_t* pState, TIMESTAMP time, _Bool absolute)
{
    if (pState->current_thread) {
#ifdef VERIFY
        assert (pState->current_thread != NULL, THREADS3);
        assert (pState->current_thread->state != MON_WAITING, THREADS9);
#endif
    
        pState->current_thread->state = SLEEPING;
        TIMESTAMP now = atomic_time_get(pState->sys_time);
        if (absolute) {
            if (time <= now) {
                time = now + 1;
            }
            pState->current_thread->sleepUntil = time;
        } else {
            pState->current_thread->sleepUntil = now + time;
        }
    }
}

static inline int get_thread_priority(Thread *thread)
{
#if NVM_DEBUG_THREADS
    printf("Thread priority is %d\n", thread->priority);
#endif
    return thread->priority;
}

/**
 * Mark thread as interrupted.
 */
static inline void interrupt_thread(nvm_state_t* pState, Thread *thread)
{
    thread->interruptState = INTERRUPT_REQUESTED;

    // Invoke the scheduler if the interrupted thread is higher priority and should run.
    if (thread->priority > pState->current_thread->priority) {
        schedule_request(pState, REQUEST_SWITCH_THREAD);
    }
}

void init_threads(nvm_state_t* pState);
int init_thread(nvm_state_t* pState, Thread *thread);
void enter_monitor(nvm_state_t* pState, Thread *pThread, Object* obj);
void exit_monitor(nvm_state_t* pState, Thread *pThread, Object* obj);
boolean switch_thread(nvm_state_t* pState, TIMESTAMP now);
void join_thread(nvm_state_t* pState, Thread *thread, const TIMESTAMP time);
void dequeue_thread(nvm_state_t* pState, Thread *thread);
void enqueue_thread(nvm_state_t* pState, Thread *thread);
void system_wait(nvm_state_t* pState, Object *obj);
void system_notify(nvm_state_t* pState, Object *obj, const boolean all);
int monitor_wait(nvm_state_t* pState, Object *obj, const TIMESTAMP time);
int monitor_notify(nvm_state_t* pState, Object *obj, const boolean all);
boolean monitor_notify_unchecked(nvm_state_t* pState, Object *obj, const boolean all); // returns true iff some thread was woken
void suspend_thread(nvm_state_t* pState, Thread *thread);
void resume_thread(nvm_state_t* pState, Thread *thread);
void set_thread_priority(nvm_state_t* pState, Thread *thread, const uint32_t priority);

#endif



