#include <inttypes.h>
#define BYTES_PER_BOOL 8

WPtr c_malloc(Word words) // A C function: units are in 8 byte words
{
    return (WPtr)malloc((int)(words * BYTES_PER_BOOL));
}

void c_list_n_cpy(WPtr dst, WPtr src, Word n) // A C function: copy n Words from src to dst
{
    while (n-- > 0)
        *dst++ = *src++;
}

void py_malloc() // A Mini-Python function: units are in 8 byte words
// py_malloc(size: int)
{
    pushp(FP);
    FP = SP;
    Word words = *(WPtr)((FP+1));

    // printf("py_malloc: words = %lld\n", words);

    R0 = (Word)c_malloc(words);

    popp(FP);
    SP += 1;
}

int len()
// len(L: [any]) -> int
{
    pushp(FP);
    FP = SP;
    WPtr O = (WPtr)*(WPtr)((FP+1));
    Word max = O[0];
    Word len = O[1];

    R0 = len;

    popp(FP);
    SP += 1;
}

int max()
// max(L: [any]) -> int
{
    pushp(FP);
    FP = SP;
    WPtr O = (WPtr)*(WPtr)((FP+1));
    Word max = O[0];
    Word len = O[1];

    R0 = max;

    popp(FP);
    SP += 1;
}

Word c_makeRangeList(int L, int U) // C function used by range()
{
    Word N = U - L;
    Word S = N + 2; // for max, cur
    // printf("L is %d  U is %d\n", L, U);
    // printf("N is %lld  S is %lld\n", N, S);
    WPtr O = c_malloc(S);
    WPtr P = &(O[2]);
    O[0] = O[1] = N;
    for (Word i=L, v=0; i<U; ++i, ++v)
    {
        P[v] = i;
    }
    return (Word)O;
}

int range()
// range(L:int, U:int)->[int]
{
    pushp(FP);
    FP = SP;

    Word L = *(WPtr)((FP+1));
    Word U = *(WPtr)((FP+2));

    R0 = c_makeRangeList(L,U);

    popp(FP);
    SP += 2;
}

int make_list()
// make_list(capacity:int)->[any]
{
    pushp(FP);
    FP = SP;
    Word N = *(WPtr)((FP+1));
    Word S = N + 2; // for max, cur
    WPtr O = c_malloc(S);
    O[0] = N;
    O[1] = 0;

    R0 = (Word)O;

    popp(FP);
    SP += 1;
}

void newline()
{
    putchar('\n');
}

void put_bool(bool NL = true)
// put_bool(b: bool)
{
    pushp(FP);
    FP = SP;
    Word V = *(WPtr)((FP+1));

    char c = V ? 'T' : 'F';
    putchar(c);
    if (NL) newline();

    popp(FP);
    SP += 1;
}

void Put_bool()
{
    put_bool(false);
}

void put_char()
// put_char(c: char)
{
    pushp(FP);
    FP = SP;
    Word V = *(WPtr)((FP+1));

    putchar((char)V);

    popp(FP);
    SP += 1;
}

void print(const char * s, WPtr p)
{
    printf("%s %lld\n", s, p);
}

void put_int(bool NL = true)
// put_int(i: int)
{
    // newline();
    // print(">put_int: FP =", FP);
    // print(">put_int: SP =", SP);
    pushp(FP);
    FP = SP;
    Word V = *(WPtr)((FP+1));

    printf("%lld", V);
    if (NL) newline();

    popp(FP);
    SP += 1;
    // print("<put_int: SP =", SP);
    // print("<put_int: FP =", FP);
}

void Put_int()
{
    put_int(false);
}


void put_str(bool NL = true)
// put_str(s: str)
{
    pushp(FP);
    FP = SP;
    WPtr O = (WPtr)*(WPtr)((FP+1));

    Word max = O[0];
    Word len = O[1];
    WPtr A = &(O[2]);
    for (int i=0; i<len; ++i)
    {
        pushw(A[i]);
        put_char();
    }
    if (NL) newline();

    popp(FP);
    SP += 1;
}

void Put_str()
{
    put_str(false);
}


void put_bool_list()
// put_bool_list(L: [bool])
{
    pushp(FP);
    FP = SP;
    WPtr O = (WPtr)*(WPtr)((FP+1));

    Word max = O[0];
    Word len = O[1];
    WPtr A = &(O[2]);
    for (int i=0; i<len; ++i)
    {
        pushw(A[i]);
        put_bool(false);
        if (i<len-1) putchar(' ');
    }
    newline();

    popp(FP);
    SP += 1;
}

void put_int_list()
// put_int_list(L: [int])
{
    pushp(FP);
    FP = SP;
    WPtr O = (WPtr)*(WPtr)((FP+1));

    Word max = O[0];
    Word len = O[1];
    WPtr A = &(O[2]);
    for (int i=0; i<len; ++i)
    {
        pushw(A[i]);
        put_int(false);
        if (i<len-1) putchar(' ');
    }
    newline();

    popp(FP);
    SP += 1;
}

void put_str_list()
// put_str_list(L: [str])
{
    pushp(FP);
    FP = SP;
    WPtr O = (WPtr)*(WPtr)((FP+1));

    Word max = O[0];
    Word len = O[1];
    WPtr A = &(O[2]);
    for (int i=0; i<len; ++i)
    {
        pushp(A[i]);
        put_str(false);
        if (i<len-1) putchar(' ');
    }
    newline();

    popp(FP);
    SP += 1;
}


void int_in_list()
// int_in_list(i: int, L: [int]) -> bool
{
    pushp(FP);
    FP = SP;
    Word e = *((WPtr)((FP+1)));
    WPtr O = (WPtr)*((WPtr)((FP+2)));
    Word len = O[1];
    WPtr L = &(O[2]);

    for (int i=0; i<len; ++i)
        if ( e  == L[i] )
        {
            R0 = 1;
            goto RET;
        }
    R0 = 0;

RET:
    popp(FP);
    SP += 2;
}

int c_str_n_cmp(Word S1[], Word S2[], int n) // this is C function, not Mini-Python
{
    for (int i=0; i<n; ++i)
    {
        // printf("STR_N_CMP: Checking %lld in %lld\n", S1[i], S2[i]);
        if ( S1[i] != S2[i] )
            return S1[i] - S2[i];
    }
    return 0;
}

void str_in_str()
// str_in_str(needle: str, haystack: str)
{
    pushp(FP);
    FP = SP;

    WPtr SO = (WPtr)*((WPtr)((FP+1)));
    WPtr LO = (WPtr)*((WPtr)((FP+2)));
    Word Slen = SO[1];
    Word Llen = LO[1];
    WPtr S = &(SO[2]);
    WPtr L = &(LO[2]);
    int compareLen = Llen-Slen+1;

    for (int i=0; i<compareLen; ++i)
    {
        // printf("STR_IN_STR: Checking %lld in %lld\n", S[0], L[i]);
        if ( c_str_n_cmp(S, &L[i], Slen) == 0 )
        {
            R0 = 1;
            goto RET;
        }
    }
    R0 = 0;

RET:
    popp(FP);
    SP += 2;
}

void append_with_swapped_args() // useful for building dynamic list literals
// append(I: any, L: [any]) -> [any]
{
    pushp(FP);
    FP = SP;

    Word I = *(WPtr)((FP+1));
    WPtr O = (WPtr)*(WPtr)((FP+2));
    Word max = O[0];
    Word & len = O[1];
    WPtr L = &(O[2]);

    if ( len < max )
        L[len++] = I;
    else
        printf("Error: attempt to append to a full list ignored");
        
    R0 = (Word)O;

    popp(FP);
    SP += 2;
}

void append()
// append(L: [any], I: any) -> [any]
{
    pushp(FP);
    FP = SP;

    WPtr O = (WPtr)*(WPtr)((FP+1));
    Word I = *(WPtr)((FP+2));
    Word max = O[0];
    Word & len = O[1];
    WPtr L = &(O[2]);

    if ( len < max )
        L[len++] = I;
    else
        printf("Error: attempt to append to a full list ignored");
        
    R0 = (Word)O;

    popp(FP);
    SP += 2;
}

void append_lists()
// append_lists(L1:[any], L2:[any])->[any]
{
    pushp(FP);
    FP = SP;

    WPtr L1O = (WPtr)*((WPtr)((FP+1)));
    WPtr L2O = (WPtr)*((WPtr)((FP+2)));
    Word L1Len = L1O[1];
    Word L2Len = L2O[1];
    WPtr L1 = &(L1O[2]);
    WPtr L2 = &(L2O[2]);
    Word logicalLen = L1Len + L2Len;
    Word physicalLen = logicalLen + 2;

    WPtr p = c_malloc(physicalLen);
    p[0] = p[1] = logicalLen;
    c_list_n_cpy(p+2, L1, L1Len);
    c_list_n_cpy(p+L1Len+2, L2, L2Len);
    R0 = (Word)p;
RET:
    popp(FP);
    SP += 2;
}


void copy_list()
// copy_list(L:[any])->[any]
{
    pushp(FP);
    FP = SP;

    WPtr L1O = (WPtr)*((WPtr)((FP+1)));
    Word L1Len = L1O[1];
    WPtr L1 = &(L1O[2]);
    Word logicalLen = L1Len;
    Word physicalLen = logicalLen + 2;

    WPtr p = c_malloc(physicalLen);
    p[0] = p[1] = logicalLen;
    c_list_n_cpy(p+2, L1, L1Len);
    R0 = (Word)p;
RET:
    popp(FP);
    SP += 1;
}


void str_in_list()
// str_in_list(s:str, L:[str])->bool
{
    pushp(FP);
    FP = SP;

    WPtr SO = (WPtr)*((WPtr)((FP+1)));
    WPtr LO = (WPtr)*((WPtr)((FP+2)));
    Word Slen = SO[1];
    Word Llen = LO[1];
    WPtr S = &(SO[2]);
    WPtr L = &(LO[2]);

    for (int i=0; i<Llen; ++i)
    {
        // printf("STR_IN_LIST: Checking %lld in %lld\n", S[0], ((WPtr)L[i])[2]);
        if ( c_str_n_cmp(S, &(((WPtr)L[i])[2]), Slen) == 0 )
        {
            R0 = 1;
            goto RET;
        }
    }
    R0 = 0;

RET:
    popp(FP);
    SP += 2;
}

// TBD everything below this line

Word STRCPY_WC(WPtr A, char *buf) // WC means Word Char
{
    Word len = 0;
    while (*A++ = *buf++)
        ++len;
    return len;
}

void int_to_str()
// int_to_str(i:int)->str
{
    pushp(FP);
    FP = SP;
    Word I = (Word)*((WPtr)((FP+1)));

    Word N = 28; // max is 20
    Word S = N + 2; // for max, cur
    WPtr O = c_malloc(S);
    O[0] = N;
    O[1] = 0;
    WPtr A = &(O[2]);
    
    char buf[40];
    
    if (sprintf(buf, "%" PRIu64, I) <= 0)
        printf("Error: int_to_str unable to convert");
    O[1] = STRCPY_WC(A, buf);

    if (0) printf("int_to_str: I = %lld  S = '%s'\n", I, buf);

    R0 = (Word)O;
    popp(FP);
    SP += 1;
}

void STRNCPY_CW(char *buf, WPtr S, Word len) // CW means Char Word
{
    int i;
    for (i=0; i<len; ++i)
        buf[i] = (char)S[i];
    buf[i] = 0;
}

void str_to_int()
// str_to_int(s:str)->int
{
    pushp(FP);
    FP = SP;
    WPtr SO = (WPtr)*((WPtr)((FP+1)));
    Word len = SO[1];
    WPtr S = &(SO[2]);

    char buf[40];
    STRNCPY_CW(buf, S, len);


    Word W = 0;
    if (sscanf(buf, "%" PRIu64, &W) <= 0)
        printf("Error: str_to_int unable to convert");

    if (0) printf("str_to_int: S = '%s'  I = %lld\n", buf, W);


    R0 = W;
    popp(FP);
    SP += 1;
}
