/*
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of JITCraft.
 *
 * JITCraft is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * JITCraft. If not, see http://www.gnu.org/licenses/.
 */
#include <stdio.h>
#include "../Zone.h"
#include "../Interpreter.h"
#include "Scanner.h"
#include "CompileContext.h"
#include "Parser.h"
#include "NameBinding.h"
#include "SemanticAnalysis.h"
#include "../Spaces-inl.h"
#include "../Heap-inl.h"

using namespace ke;

CompileContext::CompileContext(Zone *zone, const char *file)
  : zone_(zone),
    file_(file),
    active_(NULL)
{
}

CompileContext::~CompileContext()
{
    for (size_t i = 0; i < units_.length(); i++)
        delete units_[i];
}

bool
CompileContext::addTranslationUnit(TranslationUnit *unit)
{
    if (!units_.append(unit))
        return false;

    if (!unparsed_.append(unit))
        return false;

    return true;
}

char *
CompileContext::loadSource(const char *file, size_t *lengthp)
{
    char *bytes = NULL;
    size_t length;

    FILE *fp = fopen(file, "rb");
    if (!fp) {
        reportError(SourcePosition(), Message_CantOpenFile);
        return NULL;
    }

    if (fseek(fp, 0, SEEK_END)) {
        reportError(SourcePosition(), Message_ErrorSeekingFile);
        fclose(fp);
        return NULL;
    }
    long size = ftell(fp);
    if (size < 0) {
        reportError(SourcePosition(), Message_ErrorSeekingFile);
        fclose(fp);
        return NULL;
    }
    rewind(fp);
    length = size_t(size);
    bytes = new char[length + 1];
    if (fread(bytes, 1, length, fp) != length) {
        reportError(SourcePosition(), Message_ErrorReadingFile);
        fclose(fp);
        delete [] bytes;
        return NULL;
    }
    fclose(fp);

    *lengthp = length;
    return bytes;
}

bool
CompileContext::parse(TranslationUnit *unit)
{
    AutoEnterCompileContext enter(*this, unit);

    assert(!unit->tree_);

    size_t length;
    char *source = loadSource(unit->path_, &length);
    if (!source)
        return false;

    Parser parser(ZONE(), *this, source, length);
    if ((unit->tree_ = parser.parse()) == NULL)
        return false;

    return true;
}

void
CompileContext::evaluateImports(TranslationUnit *unit)
{
    if (units_.length() > 1)
        return;

#if 0
    // For now we hardcode this...
    TranslationUnit *u2 = new (ZONE()->pool()) TranslationUnit();
    u2->path_ = "C:\\Users\\dvander\\jitcraft\\jitcraft-2\\src\\Debug\\wizard.sp";

    ImportStatement *stmt = unit->tree()->imports()->at(0)->toImportStatement();
    stmt->setImportSource(u2);

    if (!u2->dependents_.append(unit))
        return false;

    if (!addTranslationUnit(u2))
        return false;
#endif
}

bool
CompileContext::propagateFailure()
{
    Vector<TranslationUnit *> worklist;
    for (size_t i = 0; i < units_.length(); i++) {
        if (units_[i]->failed() && !worklist.append(units_[i]))
            return false;
    }

    while (!worklist.empty()) {
        TranslationUnit *unit = worklist.popCopy();
        for (size_t i = 0; i < unit->dependents().length(); i++) {
            TranslationUnit *other = unit->dependents().at(i);
            if (other->failed())
                continue;

            other->setDependencyFailed();
            if (!worklist.append(other))
                return false;
        }
    }

    return true;
}

bool
CompileContext::compile()
{
    // Phase 1. Parse all modules we're importing. This process may load new
    // modules off disk, and add them to the parse queue.
    while (!unparsed_.empty()) {
        TranslationUnit *unit = unparsed_.popCopy();

        if (!parse(unit))
            continue;

        evaluateImports(unit);
    }

    // Phase 2. Create scopes and symbol tables for every lexical region with
    // a declaration. This phase excludes declarations created via imports.
    for (size_t i = 0; i < units_.length(); i++) {
        TranslationUnit *unit = units_[i];
        if (unit->failed())
            continue;

        PopulateNamesAndTypes(zone_, *this, unit);
    }

    // Phase 3. Complete imports and bind all names.
    for (size_t i = 0; i < units_.length(); i++) {
        TranslationUnit *unit = units_[i];
        if (unit->failed())
            continue;

        BindNamesAndTypes(zone_, *this, unit);
    }

    // Phase 4. Assign types to every public name in the module.
    for (size_t i = 0; i < units_.length(); i++) {
        TranslationUnit *unit = units_[i];
        if (unit->failed())
            continue;

    }

    // Intermediate phase. Decide which modules we should not perform
    // semantic analysis on.
    if (!propagateFailure())
        return false;

    // Phase 4. Now we can finally perform SemA+bytecode compilation.
    for (size_t i = 0; i < units_.length(); i++) {
        TranslationUnit *unit = units_[i];
        if (unit->failed())
            continue;

        AutoEnterCompileContext enter(*this, unit);
        SemanticAnalysis sema(zone_, *this, unit);
        sema.analyze();
    }

    // Phase 5. Run modules.
    for (size_t i = 0; i < units_.length(); i++) {
        TranslationUnit *unit = units_[i];
        if (unit->failed())
            break;

        InvokeArgs args;
        if (!zone_->stack().pushModuleFrame(unit->module_, args))
            assert(false);

        ke::LocalSlot slot;
        if (!Interpret(zone_, NULL, &slot))
            assert(false);
    }

    if (Ke::IMessageReporter *reporter = zone_->errorReporter()) {
        for (size_t i = 0; i < units_.length(); i++) {
            TranslationUnit *unit = units_[i];

            // We don't explicitly report errors for dependency failures or OOM
            // (OOM is reported separately (:TODO:) and dep failures are redundant.
            if (!unit->errors()->length())
                continue;

            reporter->beginCompilationErrors(unit->path_);
            for (size_t j = 0; j < unit->errors()->length(); j++) {
                const CompileError &error = unit->errors()->at(j);
                reporter->reportCompilationError(error.message, error.line, error.col);
            }
            reporter->endCompilationErrors();
        }
    }

    return !units_[0]->failed();
}

void
CompileContext::enter(TranslationUnit *unit)
{
    assert(!active_);
    active_ = unit;
}

void
CompileContext::leave()
{
    assert(active_);
    active_ = NULL;
}

void
CompileContext::reportErrorVa(const SourcePosition &pos, Message msg, va_list ap)
{
    const char *report = BuildErrorMessage(Messages[msg].format, ap);
    if (!report) {
        active_->setOutOfMemory();
        return;
    }

    active_->appendError(report, pos.line, pos.col);
}

void
CompileContext::reportError(const SourcePosition &pos, Message msg, ...)
{
    va_list ap;
    va_start(ap, msg);
    reportErrorVa(pos, msg, ap);
    va_end(ap);
}

TranslationUnit::~TranslationUnit()
{
    for (size_t i = 0; i < errors_.length(); i++)
        FreeErrorMessage(errors_[i].message);
}
