/*
 * 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 "SemanticAnalysis.h"
#include "Types.h"
#include "Zone.h"
#include "TypeManager.h"
#include "Scopes.h"

using namespace jc;

static inline bool
IsPrimitiveType(Type *type, PrimitiveType primitive)
{
    if (!type)
        return false;

    if (!type->isPrimitive())
        return false;

    return primitive == type->primitive();
}

static inline bool
EvaluateBinaryOperation(TokenKind kind, const BoxedPrimitive &left, const BoxedPrimitive &right,
                        BoxedPrimitive *out)
{
    switch (kind) {
      case TOK_PLUS:
        *out = BoxedPrimitive::Int(left.toInt() + right.toInt());
        return true;

      case TOK_SHL:
        *out = BoxedPrimitive::Int(left.toInt() << right.toInt());
        return true;
    }

    return true;
}

static bool
EvaluateForConstant(Expression *expression, BoxedPrimitive *out)
{
    switch (expression->kind()) {
      case AstNode::kBinaryExpression:
      {
        BoxedPrimitive left;
        BoxedPrimitive right;
        BinaryExpression *b = expression->toBinaryExpression();
        if (!EvaluateForConstant(b->left(), &left) ||
            !EvaluateForConstant(b->right(), &right))
        {
            return false;
        }
        return EvaluateBinaryOperation(b->token(), left, right, out);
      }

      case AstNode::kNameProxy:
      {
        NameProxy *proxy = expression->toNameProxy();
        if (proxy->binding()->kind() != BoundName::CONSTANT)
            return false;

        NamedConstant *c = proxy->binding()->toNamedConstant();
        if (!IsPrimitiveType(c->type(), PrimitiveType_Int32))
            return false;

        *out = c->value();
        return true;
      }

      case AstNode::kIntegerLiteral:
        *out = BoxedPrimitive::Int(expression->toIntegerLiteral()->value());
        return true;

      default:
        return false;
    }
}

SemanticAnalysis::SemanticAnalysis(ParseTree *tree)
  : tree_(tree),
    loopDepth_(0),
    scope_(NULL)
{
}

bool
SemanticAnalysis::analyze()
{
    for (size_t i = 0; i < tree_->statements()->length(); i++) {
        Statement *statement = tree_->statements()->at(i);
        statement->accept(this);
    }

    return true;
}

void
SemanticAnalysis::visit(NameProxy *name)
{
    name->setType(name->binding()->type());
}

Type *
SemanticAnalysis::visitForLoad(Expression *expression)
{
    expression->accept(this);
    if (expression->type()) {
        // :TODO: use Local
        Type *type = expression->type();
        if (type->isArray() || type->isReference())
            expression->setType(type->contained());
    }
    return expression->type();
}

Type *
SemanticAnalysis::visitForRef(Expression *expression)
{
    expression->accept(this);
    return expression->type();
}

static inline bool
TypeCheck(Type *left, Type *right)
{
    if (!left || !right)
        return false;

    if (left->isPrimitive()) {
        if (!right->isPrimitive())
            return false;
        return left->primitive() == right->primitive();
    }

    return false;
}

void
SemanticAnalysis::visit(Assignment *node)
{
    visitForLoad(node->expression());

    if (node->lvalue()->isIndexExpression()) {
        node->lvalue()->accept(this);
        node->setType(node->lvalue()->type()->contained());
    } else {
        NameProxy *proxy = node->lvalue()->toNameProxy();
        if (!proxy->binding()->kind() == BoundName::VARIABLE) {
            fprintf(stderr, "error\n");
            return;
        }

        Variable *var = proxy->binding()->toVariable();
        if  (var->type()->isReference())
            node->setType(var->type()->contained());
        else
            node->setType(var->type());
    }

    assert(TypeCheck(node->type(), node->expression()->type()));
}


void
SemanticAnalysis::visit(BinaryExpression *node)
{
    Type *left = visitForLoad(node->left());
    Type *right = visitForLoad(node->right());

    if (node->token() >= TOK_PLUS && node->token() <= TOK_SLASH) {
        // Both sides must be primitive.
        if (!left || !right)
            return;

        // If either side is a float, the other side must be a float or int.
        if (left->isFloat() || right->isFloat()) {
            assert(left->isFloat() || left->isInt());
            assert(right->isFloat() || right->isInt());
            node->setType(left->isFloat() ? left : right);
            return;
        }
    }

    // For now, assert everything else is integer and untagged.
    assert(left->isInt() && right->isInt());
    assert(!left->tag() && !right->tag());
    node->setType(left);
}

void
SemanticAnalysis::visit(ReturnStatement *node)
{
    visitForLoad(node->expression());

    // :TODO: type check
}

void
SemanticAnalysis::visit(ForStatement *node)
{
    loopDepth_++;
    if (node->scope())
        scope_ = node->scope();
    if (node->declaration())
        node->declaration()->accept(this);
    if (node->condition())
        visitForLoad(node->condition());
    if (node->update())
        node->update()->accept(this);
    node->body()->accept(this);
    if (node->scope())
        scope_ = scope_->enclosing();
    loopDepth_--;
}

void 
SemanticAnalysis::visit(WhileStatement *node)
{
    loopDepth_++;
    node->condition()->accept(this);
    node->body()->accept(this);
    loopDepth_--;
}

void
SemanticAnalysis::visit(BlockStatement *node)
{
    if (node->scope())
        scope_ = node->scope();
    for (size_t i = 0; i < node->statements()->length(); i++) {
        Statement *statement = node->statements()->at(i);
        statement->accept(this);
    }
    if (node->scope())
        scope_ = scope_->enclosing();
}

void
SemanticAnalysis::visit(VariableDeclaration *node)
{
    // :TODO: use locals
    Type *type = ZONE()->types()->getPrimitiveForTag(node->tag());
    if (!type)
        return;

    if (node->postDimensions()) {
        if (node->postDimensions()->size() > Type::MAX_UNSAFE_ARRAY_DEPTH) {
            error();
            return;
        }

        for (size_t i = node->postDimensions()->size() - 1; i < node->postDimensions()->size(); i--) {
            Expression *expr = node->postDimensions()->at(i);
            visitForLoad(expr);

            if (!IsPrimitiveType(expr->type(), PrimitiveType_Int32)) {
                typeError(expr, "integer");
                return;
            }

            int size = -1;
            if (expr->isIntegerLiteral()) {
                size = expr->toIntegerLiteral()->value();
                if (size <= 0) {
                    error();
                    return;
                }
                if (size >= INT_MAX / sizeof(int)) {
                    error();
                    return;
                }
            }

            type = ZONE()->types()->unsafeArray(type, size);
        }

        // Right now, any array is allowed on the LIFO heap. In case the
        // runtime actually decides to use the LIFO heap, we must reserve
        // a slot so we can unwind the heap when the current scope leaves.
        scope_->setUsesLifoHeap();
    } else {
        if (node->initialization()) {
            Type *right = visitForLoad(node->initialization());
            assert(TypeCheck(type, right));
        }
    }

    node->variable()->setType(type);
}

void
SemanticAnalysis::visit(IntegerLiteral *node)
{
    node->setType(ZONE()->types()->getPrimitive(PrimitiveType_Int32));
}

void
SemanticAnalysis::visit(ExpressionStatement *node)
{
    node->expression()->accept(this);
}

bool
SemanticAnalysis::preanalyze(FunctionStatement *node)
{
    // :TODO: use Local

    FixedArray *args = FixedArray::New(ZONE(), node->parameters()->size(), Heap::Tenure_Old);
    if (!args)
        return false;

    // Apply types to arguments.
    for (size_t i = 0; i < node->parameters()->size(); i++) {
        Parameter *parameter = node->parameters()->at(i);
        
        Type *type = ZONE()->types()->getPrimitiveForTag(parameter->tag());
        if (!type)
            return false;

        if (parameter->reference()) {
            type = ZONE()->types()->getReference(type);
            if (!type)
                return false;
        }

        args->set(ZONE(), i, type);
        parameter->variable()->setType(type);
    }

    Type *returnType = ZONE()->types()->getPrimitive(PrimitiveType_Int32);
    if (!returnType)
        return false;

    Type *funtype = ZONE()->types()->newFunction(returnType, args);
    if (!funtype)
        return false;

    node->fun()->setType(funtype);
    return true;
}

void
SemanticAnalysis::visit(FunctionStatement *node)
{
    scope_ = node->scope();

    // :TODO: call this ahead of time.
    if (!preanalyze(node))
        return;

    node->body()->accept(this);
    scope_ = NULL;
}

// :TODO: use handles
static inline bool
TypeCheckParameter(Type *given, Type *actual)
{
    if (actual->isReference()) {
        if (given->isReference())
            given = given->contained();
        return TypeCheck(given, actual->contained());
    }

    return TypeCheck(given, actual);
}

void
SemanticAnalysis::visit(CallExpression *node)
{
    visitForLoad(node->callee());

    // :TODO: use locals
    Type *type = node->callee()->type();
    if (!type)
        return;

    assert(type->isFunction());
    assert(node->arguments()->size() == type->parameters()->length());

    for (unsigned i = 0; i < node->arguments()->size(); i++) {
        Expression *expression = node->arguments()->at(i);
        expression->accept(this);

        Type *given = expression->type();
        if (!given)
            return;

        Type *actual = Type::cast(type->parameters()->at(i));
        assert(TypeCheckParameter(given, actual));

        // If the actual is a reference, and the given is not, we'll need to
        // compute a reference, which may be allocated on the stack.
        if (actual->isReference() && !given->isReference())
            scope_->setUsesLifoHeap();
    }
}

void
SemanticAnalysis::visit(IfStatement *node)
{
    visitForLoad(node->condition());
    node->ifTrue()->accept(this);
    if (node->ifFalse())
        node->ifFalse()->accept(this);
}

void
SemanticAnalysis::visit(IndexExpression *node)
{
    // :TODO: use locals
    Type *left = visitForRef(node->left());
    Type *right = visitForLoad(node->right());

    if (!right || !right->isInt()) {
        typeError(node->right(), "integer");
        return;
    }
    if (!left || !left->isArray() || !left->contained()->isPrimitive()) {
        typeError(node->left(), "array");
        return;
    }
    node->setType(left);
}

void
SemanticAnalysis::visit(EnumStatement *node)
{
    // :TODO: test "enum Float { ..."
    Name *tag = node->name();

    Type *type = ZONE()->types()->getPrimitiveForTag(tag);
    if (!type)
        return;

    int value = 0;
    for (size_t i = 0; i < node->entries()->size(); i++) {
        EnumStatement::Entry &entry = node->entries()->at(i);

        BoxedPrimitive box = BoxedPrimitive::Int(value);
        if (entry.expr) {
            entry.expr->accept(this);
            if (!EvaluateForConstant(entry.expr, &box))
                fprintf(stderr, "expected constant integer expression\n");

            assert(box.type() == PrimitiveType_Int32);
            value = box.toInt();
        }

        entry.name->set(type, box);
        value++;
    }
}

void
SemanticAnalysis::visit(FloatLiteral *node)
{
    Type *type = ZONE()->types()->getPrimitive(PrimitiveType_Float);
    if (!type)
        return;

    node->setType(type);
}

void
SemanticAnalysis::visit(BreakStatement *node)
{
    assert(loopDepth_ > 0);
}

void
SemanticAnalysis::visit(ContinueStatement *node)
{
    assert(loopDepth_ > 0);
}
