/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
 
/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

/*   PHBG 24/7/98: Initial version
 */

#include "MemCheck:MemCheck.h"
#include <string.h>
#include <stdio.h>
#include "nsetjmp.h"
#include "debug.h"
#include "error.h"
#include "memory.h"
#include "time.h"

#include "thread.h"

extern char *_get_stack(void);
extern void _set_stack(char *);
extern void BlockedWait(void);

typedef struct
{
    char *start;
    char data[4*1024];
} *stack_t;

struct thread_s
{
    int (*f)(void *);
    void *x;
    stack_t stack;
    _kernel_oserror error;
    int failed;
    int ended_in_failure;
    int timeout;
    int timeout_active;
    monitor_t monitor;
};

static char *stack_end = 0;
static stack_t old_stack;
static stack_t new_stack;
static stack_t starter_stack;

static struct thread_s dummy_thread = {0};

static void thread_swap(thread_t thread)
{
    jmpbuf_t label;

    if(SetJmp(label))
        return;
    old_stack->start = _get_stack();
    if(stack_end - old_stack->start > 4*1024)
        Error("Stack too deep for thread library");
    if(stack_end > old_stack->start)
        memcpy(old_stack->data, old_stack->start, stack_end - old_stack->start);
    new_stack = thread->stack;
    thread->stack = old_stack;
    if(new_stack->start < old_stack->start)
        _set_stack(new_stack->start);
    if(stack_end > new_stack->start)
        memcpy(new_stack->start, new_stack->data, stack_end - new_stack->start);
    _set_stack(new_stack->start);
    old_stack = new_stack;
    LongJmp(label);
}

static void thread_destruct(thread_t thread)
{
    if(thread)
    {
        MonitorDestruct(thread->monitor);
        Free(thread->stack);
        Free(thread);
    }
}

static thread_t thread_construct()
{
    thread_t thread;

    thread = Malloc(sizeof(*thread));
    memset(thread, 0, sizeof(*thread));
    ExceptTry
    {
        thread->monitor = Monitor();
        thread->stack = Malloc(sizeof(*thread->stack));
        memcpy(thread->stack, starter_stack, sizeof(int) + stack_end - starter_stack->start);
        thread->failed = 0;
        thread->timeout_active = 0;
    }
    ExceptCatch
    {
        thread_destruct(thread);
        ExceptRethrow();
    }
    return thread;
}


static void (*new_f)(void *);
static void *new_x;

static thread_t current_thread = NULL;

static int thread_kill = 0;

static void wrapper(void)
{
    MemCheck_SetChecking(1, 1);
    ExceptSetStack(NULL);
    ExceptTry
    {
        new_f(new_x);
        DEBUG("**Thread completed**");
    }
    ExceptCatch
    {
        current_thread->ended_in_failure = 1;
        current_thread->error = *ExceptCaught();
        DEBUG("**Thread died: %s", ExceptCaught()->errmess);
    }
    ThreadWait(NULL, NULL);
}

static void make_thread_starter(void)
{
    jmpbuf_t label;

    if(SetJmp(label))
        wrapper();
    MemCheck_SetChecking(0, 0);
    starter_stack = Malloc(sizeof(*starter_stack));
    starter_stack->start = _get_stack();
    memcpy(starter_stack->data, starter_stack->start, stack_end - starter_stack->start);
    MemCheck_SetChecking(1, 1);
}

void ThreadLibraryFinalise(void)
{
    if(stack_end)
    {
        stack_end = 0;
        Free(old_stack);
        Free(starter_stack);
    }
}

static int Kt(void *dummy)
{
    return 1;
}

thread_t Thread(void (*f)(void *), void *x)
{
    thread_t thread;

    if(stack_end == 0)
    {
        stack_end = _get_stack();
        old_stack = Malloc(sizeof(*old_stack));
        make_thread_starter();
    }
    thread = thread_construct();
    new_f = f;
    new_x = x;
    thread->f = Kt;
    ThreadService(thread);
    return thread;
}

void ThreadKill(thread_t thread)
{
    if(thread)
    {
        thread_kill = 1;
        ThreadService(thread);
        thread_kill = 0;
        if(thread->f != NULL)
            printf("Thread didn't die\n");
    }
}

void ThreadDestruct(thread_t thread)
{
    if(thread)
    {
        thread_kill = 1;
        ThreadService(thread);
        thread_kill = 0;
        if(thread->f != NULL)
            printf("Thread didn't die\n");
        thread_destruct(thread);
    }
}

int ThreadHasCompleted(thread_t thread)
{
    return thread->f == NULL;
}

void ThreadService(thread_t thread)
{
    thread_t my_thread;
    exception_stack_t my_exception_stack;

    if(thread->f == NULL)
        return;
    my_thread = current_thread;
    my_exception_stack = ExceptGetStack();
    current_thread = thread;
    MonitorChange(current_thread->monitor);
    MemCheck_SetChecking(0, 0);
    thread_swap(thread);
    MemCheck_SetChecking(1, 1);
    current_thread = my_thread;
    MonitorChange(current_thread ? current_thread->monitor : NULL);
    ExceptSetStack(my_exception_stack);
}

int ThreadRipe(thread_t thread)
{
    int ripe;

    ExceptTry
    {
        if(thread->timeout_active && Time() - thread->timeout >= 0)
            Error("Thread timed out");
        ripe = thread->f ? thread->f(thread->x) : 0;
    }
    ExceptCatch
    {
        thread->error = *ExceptCaught();
        thread->failed = 1;
        ripe = 1;
    }
    return ripe;
}

void ThreadWait(int (*f)(void *), void *x)
{
    exception_stack_t my_exception_stack;

    if(f != NULL)
        ThreadOkay();
    if(current_thread == NULL)
    {
        while(!f(x))
        {
            BlockedWait();
            if(dummy_thread.timeout_active && Time() - dummy_thread.timeout >= 0)
                Error("Thread timed out");
        }
    }
    else
    {
        current_thread->f = f;
        current_thread->x = x;
        my_exception_stack = ExceptGetStack();
        MemCheck_SetChecking(0, 0);
        thread_swap(current_thread);
        MemCheck_SetChecking(1, 1);
        ExceptSetStack(my_exception_stack);
        if(current_thread->failed)
        {
            current_thread->failed = 0;
            ErrorErr(&current_thread->error);
        }
        ThreadOkay();
    }
}

void ThreadOkay(void)
{
    if(thread_kill)
        Error("Thread Dying");
}

void ThreadSetTimeout(int t)
{
    thread_t thread;

    thread = current_thread ? current_thread : &dummy_thread;
    thread->timeout = Time() + t;
    thread->timeout_active = 1;
}

void ThreadResetTimeout(void)
{
    thread_t thread;

    thread = current_thread ? current_thread : &dummy_thread;
    thread->timeout_active = 0;
}

int ThreadTimedout(void)
{
    thread_t thread;

    thread = current_thread ? current_thread : &dummy_thread;
    return thread->timeout_active && Time() - thread->timeout >= 0;
}

void ThreadThrowError(thread_t thread)
{
    if(thread->ended_in_failure)
        ErrorErr(&thread->error);
}
