/*
 * Android "Almost" C Compiler.
 * This is a compiler for a small subset of the C language, intended for use
 * in scripting environments where speed and memory footprint are important.
 *
 * This code is based upon the "unobfuscated" version of the
 * Obfuscated Tiny C compiler, see the file LICENSE for details.
 *
 */

#include <ctype.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(__arm__)
#include <unistd.h>
#endif

#if defined(__arm__)
#define PROVIDE_ARM_DISASSEMBLY
#endif

#ifdef PROVIDE_ARM_DISASSEMBLY
#include "disassem.h"
#endif

#include <acc/acc.h>


typedef int (*MainPtr)(int, char**);
// This is a separate function so it can easily be set by breakpoint in gdb.
int run(MainPtr mainFunc, int argc, char** argv) {
    return mainFunc(argc, argv);
}

ACCvoid* symbolLookup(ACCvoid* pContext, const ACCchar* name) {
    return (ACCvoid*) dlsym(RTLD_DEFAULT, name);
}

#ifdef PROVIDE_ARM_DISASSEMBLY

static FILE* disasmOut;

static u_int
disassemble_readword(u_int address)
{
    return(*((u_int *)address));
}

static void
disassemble_printaddr(u_int address)
{
    fprintf(disasmOut, "0x%08x", address);
}

static void
disassemble_printf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vfprintf(disasmOut, fmt, ap);
    va_end(ap);
}

static int disassemble(ACCscript* script, FILE* out) {
    disasmOut = out;
    disasm_interface_t  di;
    di.di_readword = disassemble_readword;
    di.di_printaddr = disassemble_printaddr;
    di.di_printf = disassemble_printf;

    ACCvoid* base;
    ACCsizei length;

    accGetProgramBinary(script, &base, &length);
    unsigned long* pBase = (unsigned long*) base;
    unsigned long* pEnd = (unsigned long*) (((unsigned char*) base) + length);

    for(unsigned long* pInstruction = pBase; pInstruction < pEnd; pInstruction++) {
        fprintf(out, "%08x: %08x  ", (int) pInstruction, (int) *pInstruction);
        ::disasm(&di, (uint) pInstruction, 0);
    }
    return 0;
}

#endif // PROVIDE_ARM_DISASSEMBLY

int main(int argc, char** argv) {
    const char* inFile = NULL;
    bool printListing;
    bool runResults = false;
    FILE* in = stdin;
    int i;
    for (i = 1; i < argc; i++) {
        char* arg = argv[i];
        if (arg[0] == '-') {
            switch (arg[1]) {
                case 'S':
                    printListing = true;
                    break;
                case 'R':
                    runResults = true;
                    break;
            default:
                fprintf(stderr, "Unrecognized flag %s\n", arg);
                return 3;
            }
        } else if (inFile == NULL) {
            inFile = arg;
        } else {
            break;
        }
    }

    if (! inFile) {
        fprintf(stderr, "input file required\n");
        return 2;
    }

    if (inFile) {
        in = fopen(inFile, "r");
        if (!in) {
            fprintf(stderr, "Could not open input file %s\n", inFile);
            return 1;
        }
    }

    fseek(in, 0, SEEK_END);
    size_t fileSize = (size_t) ftell(in);
    rewind(in);
    ACCchar* text = new ACCchar[fileSize + 1];
    size_t bytesRead = fread(text, 1, fileSize, in);
    if (bytesRead != fileSize) {
        fprintf(stderr, "Could not read all of file %s\n", inFile);
    }

    text[fileSize] = '\0';

    ACCscript* script = accCreateScript();

    const ACCchar* scriptSource[] = {text};
    accScriptSource(script, 1, scriptSource, NULL);
    delete[] text;

    accRegisterSymbolCallback(script, symbolLookup, NULL);

    accCompileScript(script);
    int result = accGetError(script);
    MainPtr mainPointer = 0;
    if (result != 0) {
        ACCsizei bufferLength;
        accGetScriptInfoLog(script, 0, &bufferLength, NULL);
        char* buf = (char*) malloc(bufferLength + 1);
        if (buf != NULL) {
            accGetScriptInfoLog(script, bufferLength + 1, NULL, buf);
            fprintf(stderr, "%s", buf);
            free(buf);
        } else {
            fprintf(stderr, "Out of memory.\n");
        }
        goto exit;
    }

    {
        ACCsizei numPragmaStrings;
        accGetPragmas(script, &numPragmaStrings, 0, NULL);
        if (numPragmaStrings) {
            char** strings = new char*[numPragmaStrings];
            accGetPragmas(script, NULL, numPragmaStrings, strings);
            for(ACCsizei i = 0; i < numPragmaStrings; i += 2) {
                fprintf(stderr, "#pragma %s(%s)\n", strings[i], strings[i+1]);
            }
            delete[] strings;
        }
    }

    if (printListing) {
#ifdef PROVIDE_ARM_DISASSEMBLY
        disassemble(script, stderr);
#endif
    }

    if (runResults) {
        accGetScriptLabel(script, "main", (ACCvoid**) & mainPointer);

        result = accGetError(script);
        if (result != ACC_NO_ERROR) {
            fprintf(stderr, "Could not find main: %d\n", result);
        } else {
            fprintf(stderr, "Executing compiled code:\n");
            int codeArgc = argc - i + 1;
            char** codeArgv = argv + i - 1;
            codeArgv[0] = (char*) (inFile ? inFile : "stdin");
            result = run(mainPointer, codeArgc, codeArgv);
            fprintf(stderr, "result: %d\n", result);
        }
    }

exit:

    accDeleteScript(script);

    return result;
}
