/*
 * C part of minmal AQB runtime (debug purposes only)
 *
 * opens libraries, initializes other modules,
 * calls __aqbmain
 * and shuts down everything once __aqbmain returns
 */

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

#include <exec/execbase.h>
#include <exec/memory.h>

#include <clib/exec_protos.h>
#include <inline/exec.h>

#include <clib/dos_protos.h>
#include <inline/dos.h>

#include <clib/mathffp_protos.h>
#include <inline/mathffp.h>

// #define ENABLE_DEBUG
#define MAXBUF 40

#define ERR_OUT_OF_DATA              4
#define ERR_OUT_OF_MEMORY            7
#define ERR_SUBSCRIPT_OUT_OF_RANGE   9
#define ERR_INCOMPATIBLE_ARRAY      10

void _autil_exit(LONG return_code);
void _aqb_main(void);

struct ExecBase      *SysBase       = NULL;
struct DOSBase       *DOSBase       = NULL;
struct MathBase      *MathBase      = NULL;
struct MathTransBase *MathTransBase = NULL;

static FLOAT g_one_half, g_zero;

//static BOOL autil_init_done = FALSE;

/************************************************************************
 *
 * math
 *
 ************************************************************************/

/*
 * __pow_s4 / __pow_s2
 *
 * source: stackoverflow
 */

int32_t __pow_s4(int32_t base, int32_t exp)
{
    int32_t result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

uint32_t __pow_u4(uint32_t base, uint32_t exp)
{
    uint32_t result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

int16_t __pow_s2(int16_t base, int16_t exp)
{
    int16_t result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

uint16_t __pow_u2(uint16_t base, uint16_t exp)
{
    uint16_t result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

int8_t __pow_s1(int8_t base, int8_t exp)
{
    int8_t result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

int8_t __mul_s1(int8_t a, int8_t b)
{
    return a*b;
}

int8_t __div_s1(int8_t a, int8_t b)
{
    return a/b;
}

int8_t __mod_s1(int8_t a, int8_t b)
{
    return a%b;
}

uint8_t __pow_u1(uint8_t base, uint8_t exp)
{
    uint8_t result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

uint8_t __mul_u1(uint8_t a, uint8_t b)
{
    return a*b;
}

uint8_t __div_u1(uint8_t a, uint8_t b)
{
    return a/b;
}

uint8_t __mod_u1(uint8_t a, uint8_t b)
{
    return a%b;
}

SHORT fix_(FLOAT f)
{
    return SPFix(f);
}

SHORT int_(FLOAT f)
{
	if (SPCmp(f, g_zero)<0)
		return SPFix(SPSub(g_one_half, f));
    return SPFix(SPAdd(f, g_one_half));
}

SHORT cint_(FLOAT f)
{
	if (SPCmp(f, g_zero)<0)
		return SPFix(SPSub(g_one_half, f));
    return SPFix(SPAdd(f, g_one_half));
}

LONG clng_(FLOAT f)
{
	if (SPCmp(f, g_zero)<0)
		return SPFix(SPSub(g_one_half, f));
    return SPFix(SPAdd(f, g_one_half));
}

FLOAT __aqb_intdiv_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(a / b);
}

FLOAT __aqb_shl_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(a << b);
}

FLOAT __aqb_shr_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(a >> b);
}

FLOAT __aqb_xor_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(a ^ b);
}

FLOAT __aqb_eqv_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(~(a ^ b));
}

FLOAT __aqb_imp_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(~a | b);
}

FLOAT __aqb_not_single(FLOAT af)
{
    LONG a = clng_(af);

    return SPFlt(~a);
}

FLOAT __aqb_and_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(a & b);
}

FLOAT __aqb_or_single(FLOAT af, FLOAT bf)
{
    LONG a = clng_(af);
    LONG b = clng_(bf);

    return SPFlt(a | b);
}

/************************************************************************
 *
 * i/o
 *
 ************************************************************************/

static BPTR _debug_stdout = 0;

ULONG len_(const UBYTE *str)
{
    int l = 0;
    while (*str)
    {
        str++;
        l++;
    }
    return l;
}

#if 1
/* A utility function to reverse a string  */
static void reverse(char *str, int length)
{
    int start = 0;
    int end   = length -1;
    while (start < end)
    {
        char c = *(str+start);
        *(str+start) = *(str+end);
        *(str+end)   = c;
        start++;
        end--;
    }
}

void _astr_itoa_ext(int num, char* str, int base, BOOL leading_space)
{
    int i = 0;
    BOOL isNegative = FALSE;

    /* Handle 0 explicitely, otherwise empty string is printed for 0 */
    if (num == 0)
    {
        if (leading_space)
            str[i++] = ' ';
        str[i++] = '0';
        str[i] = '\0';
        return;
    }

    // In standard itoa(), negative numbers are handled only with
    // base 10. Otherwise numbers are considered unsigned.
    if (num < 0 && base == 10)
    {
        isNegative = TRUE;
        num = -num;
    }

    // Process individual digits
    while (num != 0)
    {
        int rem = num % base;
        str[i++] = (rem > 9)? (rem-10) + 'a' : rem + '0';
        num = num/base;
    }

    // If number is negative, append '-', else ' '
    if (isNegative)
    {
        str[i++] = '-';
    }
    else
    {
        if (leading_space)
            str[i++] = ' ';
    }

    str[i] = '\0'; // Append string terminator

    // Reverse the string
    reverse(str, i);
}

void _astr_itoa(int num, char* str, int base)
{
    _astr_itoa_ext(num, str, base, /*leading_space=*/TRUE);
}
#else
void _astr_itoa(int num, char* str)
{
    str[0] = '1';
    str[1] = '2';
    str[2] = 0;
}
#endif

void _debug_puts(const UBYTE *s)
{
    if (_debug_stdout)
        Write(_debug_stdout, (CONST APTR) s, len_(s));
}

void _debug_puts2(SHORT s)
{
    char buf[MAXBUF];
    _astr_itoa(s, buf, 10);
    _debug_puts((STRPTR)buf);
}

void _debug_putu4(ULONG l)
{
    char buf[MAXBUF];
    _astr_itoa(l, buf, 10);
    _debug_puts((STRPTR)buf);
}

void _debug_putnl(void)
{
    if (_debug_stdout)
        Write(_debug_stdout, "\n", 1);
}

void _debug_cls(void)
{
    if (_debug_stdout)
        Write(_debug_stdout, "\f", 1);
}

/************************************************************************
 *
 * ALLOCATE / DEALLOCATE
 *
 ************************************************************************/

// not using Intuition's AllocRemember here because we want minimal dependencies for the AQB core module

typedef struct AQB_memrec_ *AQB_memrec;

struct AQB_memrec_
{
    AQB_memrec next;
    ULONG      size;
    APTR      *mem;
};

static AQB_memrec g_mem = NULL;

APTR ALLOCATE_(ULONG size, ULONG flags)
{
    AQB_memrec mem_prev = g_mem;

    //_debug_puts("ALLOCATE size=");
    //_debug_puts2(size);
    //_debug_puts(", flags=");
    //_debug_puts2(flags);
    //_debug_puts("\n");

    g_mem = (AQB_memrec) AllocMem (sizeof(*g_mem), 0);
    if (!g_mem)
    {
        g_mem = mem_prev;
        return NULL;
    }

    g_mem->mem = (APTR) AllocMem (size, flags);
    if (!g_mem->mem)
    {
        FreeMem(g_mem, sizeof (*g_mem));
        g_mem = mem_prev;
        return NULL;
    }

    g_mem->size = size;
    g_mem->next = mem_prev;

    return g_mem->mem;
}

void DEALLOCATE (APTR ptr, ULONG size)
{
    // FIXME: implement.
}

/************************************************************************
 *
 * string support
 *
 ************************************************************************/

char *_astr_dup(const STRPTR str)
{
    ULONG l = len_(str);
    char *str2 = ALLOCATE_(l+1, MEMF_ANY);
    CopyMem((APTR)str, (APTR)str2, l+1);
    return str2;
}

/************************************************************************
 *
 * ON ERROR ...
 *
 ************************************************************************/

static void (*error_handler)(void) = NULL;
static BOOL do_resume = FALSE;

SHORT ERR=0;

void ON_ERROR_CALL(void (*cb)(void))
{
    error_handler = cb;
}

void ERROR (SHORT errcode)
{
    do_resume = FALSE;
    ERR = errcode;

    if (error_handler)
    {
        error_handler();
    }
    else
    {
        _debug_puts((STRPTR)"*** unhandled runtime error code: "); _debug_puts2(errcode);
        _debug_puts((STRPTR)"\n");
    }

    if (!do_resume)
        _autil_exit(errcode);
    else
        ERR=0;
}

void RESUME_NEXT(void)
{
    do_resume = TRUE;
}

/************************************************************************
 *
 * DATA / READ / RESTORE support
 *
 ************************************************************************/

static void *g_data_ptr=NULL;

void _aqb_restore (void *p)
{
    g_data_ptr = p;
}

void _aqb_read1 (void *v)
{
    if (!g_data_ptr)
    {
        ERROR (ERR_OUT_OF_DATA);
        return;
    }

    *((BYTE*) v) = *((BYTE *)g_data_ptr);

    g_data_ptr += 1;
}

void _aqb_read2 (void *v)
{
    if (!g_data_ptr)
    {
        ERROR (ERR_OUT_OF_DATA);
        return;
    }

    *((SHORT*) v) = *((SHORT *)g_data_ptr);

    g_data_ptr += 2;
}

void _aqb_read4 (void *v)
{
    if (!g_data_ptr)
    {
        ERROR (ERR_OUT_OF_DATA);
        return;
    }

    *((LONG*) v) = *((LONG *)g_data_ptr);

    g_data_ptr += 4;
}

#define MAX_STRING_LEN 1024

void _aqb_readStr (void *v)
{
    char buf[MAX_STRING_LEN];
    if (!g_data_ptr)
    {
        ERROR (ERR_OUT_OF_DATA);
        return;
    }

    char c = 0xff;
    int l = 0;
    while (c && (l<MAX_STRING_LEN-1))
    {
        c = buf[l] = *((char *)g_data_ptr);
        // _debug_puts("_aqb_readStr: c="); _debug_puts2(c); _debug_putnl();
        g_data_ptr += 1;
        l++;
    }
    buf[l] = 0;
    *((char **)v) = _astr_dup((STRPTR)buf);
}

/************************************************************************
 *
 * darray support
 *
 ************************************************************************/

typedef struct
{
    ULONG   lbound, ubound, numElements;
} _DARRAY_BOUNDS_T;

typedef struct
{
    APTR              data;
    UWORD             numDims;
    ULONG             elementSize;
    ULONG             dataSize;
    _DARRAY_BOUNDS_T *bounds;
} _DARRAY_T;

void __DARRAY_T___init__ (_DARRAY_T *self, ULONG elementSize)
{
    // _debug_puts ("__DARRAY_T___init__: elementSize="); _debug_puts2(elementSize); _debug_putnl();
    self->data        = NULL;
    self->numDims     = 0;
    self->elementSize = elementSize;
    self->dataSize    = 0;
    self->bounds      = NULL;
}

void __DARRAY_T_REDIM (_DARRAY_T *self, BOOL preserve, UWORD numDims, ...)
{
    va_list valist;

    self->numDims     = numDims;

    self->bounds = ALLOCATE_ (sizeof (_DARRAY_BOUNDS_T) * numDims, 0);

    va_start (valist, numDims);
    ULONG dataSize = self->elementSize;
    for (UWORD iDim=0; iDim<numDims; iDim++)
    {
        ULONG start = va_arg(valist, ULONG);
        ULONG end   = va_arg(valist, ULONG);
        dataSize *= end - start + 1;
        //_debug_puts ("_dyna_redim: dim: start="); _debug_puts2(start); _debug_puts(", end="); _debug_puts2(end); _debug_putnl();
        self->bounds[iDim].lbound      = start;
        self->bounds[iDim].ubound      = end;
        self->bounds[iDim].numElements = end-start+1;
    }
    va_end(valist);

    APTR oData      = self->data;
    ULONG oDataSize = self->dataSize;

    self->data     = ALLOCATE_ (dataSize, 0);
    self->dataSize = dataSize;

    if (oData)
    {
        if (preserve)
        {
            ULONG toCopy = dataSize < oDataSize ? dataSize : oDataSize;
            CopyMem (oData, self->data, toCopy);
            //_debug_puts ("_dyna_redim: preserve, toCopy="); _debug_puts2(toCopy); _debug_putnl();
        }
        DEALLOCATE (oData, oDataSize);
    }

    //_debug_puts ("_dyna_redim: self="); _debug_putu4((ULONG) self); _debug_puts(", data="); _debug_putu4((ULONG)self->data); _debug_puts (", dataSize="); _debug_puts2(dataSize); _debug_puts (", numDims="); _debug_puts2(numDims); _debug_putnl();
}

/*
 * elements are in row-major order
 * array dimensions: a(N0, N1, N2, ...), lement size E
 * e.g. DIM a(4, 3), E=4          / e_0_0[ 0]  e_0_1[ 4]  e_0_2[ 8] \
 *                               /  e_1_0[12]  e_1_1[16]  e_1_2[20]  \
 *                               \  e_2_0[24]  e_2_1[28]  e_2_2[32]  /
 *                                \ e_3_0[36]  e_3_1[40]  e_3_2[44] /
 *
 * offset calc for a(i0, i1)        , element size E : o = E*i1 + E*N1*i0
 *                 a(i0, i1, i2, i3)                   o = E*i3 + E*N3*i2 + E*N2*N3*i1 + E*N1*N2*N3*i0
 *
 *
 *
 */

void *__DARRAY_T_IDXPTR_ (_DARRAY_T *self, UWORD dimCnt, ...)
{
    //int i = foobar(dimCnt);

    //_dyna_create_ (dimCnt, i, 1, 2);

    //_debug_puts ("_dyna_idx: dimCnt="); _debug_puts2(dimCnt); _debug_putnl();

    if (!self->data)
        ERROR (ERR_SUBSCRIPT_OUT_OF_RANGE);

    if (dimCnt != self->numDims)
        ERROR (ERR_SUBSCRIPT_OUT_OF_RANGE);

    va_list valist;
    va_start (valist, dimCnt);
    ULONG offset = 0;
    ULONG es     = self->elementSize;
    for (WORD iDim=self->numDims-1; iDim>=0; iDim--)
    {
        ULONG lbound = self->bounds[iDim].lbound;
        ULONG ubound = self->bounds[iDim].ubound;
        ULONG n     = self->bounds[iDim].numElements;

        //_debug_puts ("_dyna_idx: dim: iDim="); _debug_puts2(iDim); _debug_puts(", lbound="); _debug_puts2(lbound); _debug_putnl();

        ULONG idx = va_arg(valist, ULONG);

        if ((idx<lbound) || (idx>ubound))
            ERROR (ERR_SUBSCRIPT_OUT_OF_RANGE);

        offset += es * (idx - lbound);
        es *= n;
        //_debug_puts ("_dyna_idx: dim: idx="); _debug_puts2(idx); _debug_puts(", offset="); _debug_puts2(offset); _debug_putnl();
    }
    va_end(valist);

    void *ptr = self->data+offset;
    //_debug_puts ("_dyna_idx: self="); _debug_putu4((ULONG) self); _debug_puts(", data="); _debug_putu4((ULONG)self->data); _debug_puts (" -> ptr="); _debug_putu4((ULONG)ptr); _debug_putnl();

    return ptr;
}

WORD  __DARRAY_T_LBOUND_  (_DARRAY_T *self, WORD d)
{
    if (d<=0)
        return 1;

    if (!self->data)
        return 0;

    if (d>self->numDims)
        return 0;

    return self->bounds[d-1].lbound;
}

WORD  __DARRAY_T_UBOUND_  (_DARRAY_T *self, WORD d)
{
    if (d<=0)
        return self->numDims;

    if (!self->data)
        return -1;

    if (d>self->numDims)
        return 0;

    return self->bounds[d-1].ubound;
}

void __DARRAY_T_COPY (_DARRAY_T *self, _DARRAY_T *a)
{
    if (a->numDims != self->numDims)
        ERROR (ERR_INCOMPATIBLE_ARRAY);

    ULONG toCopy = a->dataSize < self->dataSize ? a->dataSize : self->dataSize;

    //_debug_puts ("__DARRAY_T_COPY: toCopy="); _debug_puts2(toCopy); _debug_putnl();
    CopyMem(a->data, self->data, toCopy);
}

void __DARRAY_T_ERASE (_DARRAY_T *self)
{
    if (self->data)
    {
        DEALLOCATE (self->data, self->dataSize);
    }
    // FIXME: free bounds!

    self->data        = NULL;
    self->numDims     = 0;
    self->dataSize    = 0;
    self->bounds      = NULL;
}


/************************************************************************
 *
 * fake _aqb routines
 *
 ************************************************************************/

void LINE(BOOL s1, short x1, short y1, BOOL s2, short x2, short y2, short c, short bf)
{
    _debug_puts((STRPTR)"s1: ")  ; _debug_puts2(s1);
    _debug_puts((STRPTR)", x1: "); _debug_puts2(x1);
    _debug_puts((STRPTR)", y1: "); _debug_puts2(y1);
    _debug_puts((STRPTR)", s2: "); _debug_puts2(s2);
    _debug_puts((STRPTR)", x2: "); _debug_puts2(x2);
    _debug_puts((STRPTR)", y2: "); _debug_puts2(y2);
    _debug_puts((STRPTR)", c: ") ; _debug_puts2(c);
    _debug_puts((STRPTR)", bf: "); _debug_puts2(bf);
    _debug_putnl();
}

/************************************************************************
 *
 * startup / exit
 *
 ************************************************************************/

void __handle_break(void)
{
}


void _aqb_assert (BOOL b, const char *msg)
{
    if (b)
        return;

    _debug_puts((STRPTR)msg);
    _debug_puts((STRPTR)"\n");

    _autil_exit(20);
}

void SYSTEM(void)
{
    _autil_exit(0);
}

ULONG FRE_(SHORT x)
{

    if (x==-2)          // stack
    {
        struct Process *Process;
        struct CommandLineInterface *CLI;
        ULONG stack;

        Process = (struct Process *) FindTask (0L);
        if ( (CLI = (struct CommandLineInterface *) (Process -> pr_CLI << 2)) )
        {
            stack = CLI -> cli_DefaultStack << 2;
        }
        else
        {
            stack = Process -> pr_StackSize;
        }
        return stack;
    }
#if 0
    switch (x)
    {
        case -2:        // stack
        {
            struct Process *Process;
            struct CommandLineInterface *CLI;
            ULONG stack;

            Process = (struct Process *) FindTask (0L);
            if ( (CLI = (struct CommandLineInterface *) (Process -> pr_CLI << 2)) )
            {
                stack = CLI -> cli_DefaultStack << 2;
            }
            else
            {
                stack = Process -> pr_StackSize;
            }
            return stack;
        }
        case -1:        // chip + fast
            return AvailMem(MEMF_CHIP) + AvailMem(MEMF_FAST);
        case 0:         // chip
            return AvailMem(MEMF_CHIP);
        case 1:         // fast
            return AvailMem(MEMF_FAST);
        case 2:         // largest chip
            return AvailMem(MEMF_CHIP|MEMF_LARGEST);
        case 3:         // largest fast
            return AvailMem(MEMF_FAST|MEMF_LARGEST);
        default:
            return 0;
    }
#endif
    return 0;
}

// gets called by _autil_exit
void _minbrt_exit(void)
{

    while (g_mem)
    {
        AQB_memrec mem_next = g_mem->next;
        FreeMem(g_mem->mem, g_mem->size);
        FreeMem(g_mem, sizeof (*g_mem));
        g_mem = mem_next;
    }

#ifdef ENABLE_DEBUG
    if (DOSBase)
        _debug_puts("_c_atexit...\n");
#endif

#ifdef ENABLE_DEBUG
    if (DOSBase)
        _debug_puts("_c_atexit... finishing.\n");
#endif

    if (DOSBase)
        CloseLibrary( (struct Library *)DOSBase);
}

void _cshutdown (LONG return_code, STRPTR msg)
{
    if (msg && DOSBase)
        _debug_puts(msg);

    _autil_exit(return_code);
}

// called from aqb main, unused here
void __aqb_init(void)
{
}

void __brt_init(void)
{
}

static void _minbrt_init (void)
{
    SysBase = (*((struct ExecBase **) 4));

    if (!(DOSBase = (struct DOSBase *)OpenLibrary((CONST_STRPTR) "dos.library", 0)))
        _cshutdown(20, (STRPTR)"*** error: failed to open dos.library!\n");

    _debug_stdout = Output();

    if (!(MathBase = (struct MathBase *)OpenLibrary((CONST_STRPTR) "mathffp.library", 0)))
        _cshutdown(20, (STRPTR)"*** error: failed to open mathffp.library!\n");

    if (!(MathTransBase = (struct MathTransBase *)OpenLibrary((CONST_STRPTR) "mathtrans.library", 0)))
        _cshutdown(20, (STRPTR)"*** error: failed to open mathtrans.library!\n");

    g_one_half = SPDiv(SPFlt(2), SPFlt(1));
    g_zero     = SPFlt(0);
}


void _minbrt_startup (void)
{
    _minbrt_init();

    _aqb_main();

    _autil_exit(0);
}


