/*
 * 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"

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)
{
}

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() && expression->type()->isArray())
        expression->setType(expression->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();
        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->declaration())
        node->declaration()->accept(this);
    if (node->condition())
        visitForLoad(node->condition());
    if (node->update())
        node->update()->accept(this);
    node->body()->accept(this);
    loopDepth_--;
}

void 
SemanticAnalysis::visit(WhileStatement *node)
{
    loopDepth_++;
    node->condition()->accept(this);
    node->body()->accept(this);
    loopDepth_--;
}

void
SemanticAnalysis::visit(BlockStatement *node)
{
    for (size_t i = 0; i < node->statements()->length(); i++) {
        Statement *statement = node->statements()->at(i);
        statement->accept(this);
    }
}

void
SemanticAnalysis::visit(VariableDeclaration *node)
{
    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);
        }
    } 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);
}

void
SemanticAnalysis::visit(FunctionStatement *node)
{
    node->body()->accept(this);
}

void
SemanticAnalysis::visit(CallExpression *node)
{
}

void
SemanticAnalysis::visit(IfStatement *node)
{
    visitForLoad(node->condition());
    node->ifTrue()->accept(this);
    if (node->ifFalse())
        node->ifFalse()->accept(this);
}

void
SemanticAnalysis::visit(IndexExpression *node)
{
    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);
}
