/* vim: set ts=4 sw=4 tw=99 et:
 *
 * 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 "AST.h"
#include "HIR.h"
#include "BytecodeEmitter.h"
#include "../Strings.h"
#include "../Heap-inl.h"
#include "../Handles-inl.h"

using namespace ke;

class HIRTranslator : public HIRVisitor
{
  public:
    HIRTranslator(Zone *zone, BytecodeEmitter &emitter)
      : zone_(zone),
        emitter_(emitter)
    {
    }

    void translate(HIR *hir) {
        hir->accept(this);
    }

    void visitBoolean(HBoolean *hir);
    void visitFloat(HFloat *hir);
    void visitInteger(HInteger *hir);
    void visitArray(HArray *hir);
    void visitCall(HCall *call);
    void visitStore(HStore *store);
    void visitUnary(HUnary *unary);
    void visitBinary(HBinary *binary);
    void visitLocal(HLocal *local);
    void visitGlobal(HGlobal *global);
    void visitConvert(HConvert *convert);
    void visitIndex(HIndex *index);
    void visitField(HField *field);
    void visitPostIncDec(HPostIncDec *hir);
    void visitToRef(HToRef *hir);
    void visitNewDependentArray(HNewDependentArray *hir);
    void visitImport(HImport *import);

  private:
    Zone *zone_;
    BytecodeEmitter &emitter_;
};

#define __ emitter_.

void
HIRTranslator::visitBoolean(HBoolean *hir)
{
    if (hir->value())
        __ true_();
    else
        __ false_();
}

void
HIRTranslator::visitFloat(HFloat *hir)
{
    __ float_(hir->value());
}

void
HIRTranslator::visitInteger(HInteger *hir)
{
    __ int_(hir->value());
}

void
HIRTranslator::visitArray(HArray *hir)
{
    __ object(hir->array());

    if (hir->duplicate())
        __ duparray(); 
}

void
HIRTranslator::visitCall(HCall *call)
{
    Local<FunctionType> fun(zone_, FunctionType::cast(call->callee()->type()));
    
    assert(call->args()->length() == fun->parameters()->length());

    __ note_position(call->node()->pos());

    unsigned argc = call->args()->length();
    for (size_t i = call->args()->length() - 1; i < call->args()->length(); i--) {
        HIR *hir = call->args()->at(i);
        hir->accept(this);
    }

    call->callee()->accept(this);
    __ call(fun, argc);
}

void
HGlobal::bind(HIRVisitor *visitor, BytecodeEmitter &emitter_)
{
}

void
HGlobal::hold(BytecodeEmitter &emitter_)
{
}

void
HGlobal::swapAndPick(BytecodeEmitter &emitter_)
{
    __ dup();
}

void
HGlobal::store(BytecodeEmitter &emitter_)
{
    __ setglobal(sym_);
}

void
HGlobal::load(BytecodeEmitter &emitter_)
{
    __ getglobal(sym_);
}

bool
HGlobal::ref(BytecodeEmitter &emitter_)
{
    return false;
}

static inline Type *
MaybeDereference(Type *type)
{
    if (type->isReference())
        return ReferenceType::cast(type)->contained();
    return type;
}

HLocal::HLocal(AstNode *node, VariableSymbol *sym)
  : HLValue(node, MaybeDereference(sym->type())),
    sym_(sym)
{
}

void
HLocal::bind(HIRVisitor *visitor, BytecodeEmitter &emitter_)
{
    if (sym_->type()->isReference()) {
        if (sym_->storage() == VariableSymbol::Local)
            __ getlocal(sym_);
        else if (sym_->storage() == VariableSymbol::Arg)
            __ getarg(sym_);
        else
            assert(false);
    }
}

void
HLocal::hold(BytecodeEmitter &emitter_)
{
    if (sym_->type()->isReference())
        __ dup();
}

void
HLocal::swapAndPick(BytecodeEmitter &emitter_)
{
    if (sym_->type()->isReference()) {
        __ swap();
        __ pick2();
    } else {
        __ dup();
    }
}

void
HLocal::store(BytecodeEmitter &emitter_)
{
    if (sym_->type()->isReference()) {
        __ setref(ReferenceType::cast(sym_->type())->contained());
    } else {
        if (sym_->storage() == VariableSymbol::Local)
            __ setlocal(sym_);
        else if (sym_->storage() == VariableSymbol::Arg)
            __ setarg(sym_);
        else
            assert(false);
    }
}

void
HLocal::load(BytecodeEmitter &emitter_)
{
    if (sym_->type()->isReference())
        __ getref(ReferenceType::cast(sym_->type())->contained());
    else if (sym_->storage() == VariableSymbol::Local)
        __ getlocal(sym_);
    else if (sym_->storage() == VariableSymbol::Arg)
        __ getarg(sym_);
    else
        assert(false);
}

bool
HLocal::ref(BytecodeEmitter &emitter_)
{
    if (sym_->storage() == VariableSymbol::Heap) {
        // Closing over a reference should never pass SemA.
        assert(!sym_->type()->isReference());

        // We don't support direct references to heap-allocated locals. This
        // should probably be banned in SemA? But for now, just assert. :TODO:
        assert(false);
        return false;
    }
        
    if (sym_->type()->isReference()) {
        if (sym_->storage() == VariableSymbol::Local)
            __ getlocal(sym_);
        else if (sym_->storage() == VariableSymbol::Arg)
            __ getarg(sym_);
        else
            assert(false);
    } else {
        __ newref(sym_);
    }

    return true;
}

void
HIndex::bind(HIRVisitor *visitor, BytecodeEmitter &emitter_)
{
    __ note_position(node()->pos());
    left_->accept(visitor);
    right_->accept(visitor);
}

void
HIndex::hold(BytecodeEmitter &emitter_)
{
    __ dup2();
}

void
HIndex::swapAndPick(BytecodeEmitter &emitter_)
{
    // BASE INDEX VALUE
    __ roll3();         // INDEX VALUE BASE
    __ roll3();         // VALUE BASE INDEX
    __ pick3();         // VALUE BASE INDEX VALUE
}

void
HIndex::store(BytecodeEmitter &emitter_)
{
    __ setelem(type());
}

void
HIndex::load(BytecodeEmitter &emitter_)
{
    __ getelem(type());
}

bool
HIndex::ref(BytecodeEmitter &emitter_)
{
    return false;
}

void
HField::bind(HIRVisitor *visitor, BytecodeEmitter &emitter_)
{
    __ note_position(node()->pos());
    base_->accept(visitor);
}

void
HField::hold(BytecodeEmitter &emitter_)
{
    __ dup();
}

void
HField::swapAndPick(BytecodeEmitter &emitter_)
{
    // BASE VALUE
    __ swap();      // VALUE BASE
    __ pick2();     // VALUE BASE VALUE
}

void
HField::store(BytecodeEmitter &emitter_)
{
    __ setfield(index_, type());
}

void
HField::load(BytecodeEmitter &emitter_)
{
    __ getfield(index_, type());
}

bool
HField::ref(BytecodeEmitter &emitter_)
{
    return false;
}

void
HIRTranslator::visitLocal(HLocal *local)
{
    local->bind(this, emitter_);
    local->load(emitter_);
}

void
HIRTranslator::visitIndex(HIndex *index)
{
    index->bind(this, emitter_);
    index->load(emitter_);
}

void
HIRTranslator::visitField(HField *field)
{
    field->bind(this, emitter_);
    field->load(emitter_);
}

void
HIRTranslator::visitGlobal(HGlobal *global)
{
    global->bind(this, emitter_);
    global->load(emitter_);
}

static inline Opcode
TokenToOpcode(TokenKind kind)
{
    switch (kind) {
      case TOK_PLUS:
      case TOK_ASSIGN_ADD:
      case TOK_INCREMENT:
        return OP_ADD;
      case TOK_MINUS:
      case TOK_ASSIGN_SUB:
      case TOK_DECREMENT:
        return OP_SUB;
      case TOK_STAR:
      case TOK_ASSIGN_MUL:
        return OP_MUL;
      case TOK_SLASH:
      case TOK_ASSIGN_DIV:
        return OP_DIV;
      case TOK_PERCENT:
      case TOK_ASSIGN_MOD:
        return OP_MOD;
      case TOK_SHR:
      case TOK_ASSIGN_SHR:
        return OP_SHR;
      case TOK_USHR:
      case TOK_ASSIGN_USHR:
        return OP_USHR;
      case TOK_SHL:
      case TOK_ASSIGN_SHL:
        return OP_SHL;
      case TOK_BITAND:
      case TOK_ASSIGN_BITAND:
        return OP_BITAND;
      case TOK_BITOR:
      case TOK_ASSIGN_BITOR:
        return OP_BITOR;
      case TOK_GT:
          return OP_GT;
      case TOK_GE:
          return OP_GE;
      case TOK_LT:
          return OP_LT;
      case TOK_LE:
          return OP_LE;
      case TOK_EQUALS:
          return OP_EQ;
      case TOK_NOTEQUALS:
          return OP_NE;
      default:
        assert(kind == TOK_BITXOR);
        return OP_BITXOR;
    }
}

void
HIRTranslator::visitStore(HStore *store)
{
    store->lval()->bind(this, emitter_);

    if (store->kind() == TOK_ASSIGN) {
        // Fixed-length arrays and structs have copy semantics.
        Local<Type> type(zone_, store->lval()->type());
        if (type->isArray() && ArrayType::cast(type)->isFixedLength()) {
            store->lval()->load(emitter_);
            store->rval()->accept(this);
            __ copyarray();
        } else if (type->isStruct()) {
            store->lval()->load(emitter_);
            store->rval()->accept(this);
            __ copystruct();
        } else {
            store->rval()->accept(this);
            store->lval()->store(emitter_);
        }
    } else {
        // Advanced case: dup the ref, load, op, store.
        store->lval()->hold(emitter_);
        store->lval()->load(emitter_);
        store->rval()->accept(this);
        __ binary(TokenToOpcode(store->kind()));
        store->lval()->store(emitter_);
    }
}

void
HIRTranslator::visitPostIncDec(HPostIncDec *hir)
{
    hir->lval()->bind(this, emitter_);
    hir->lval()->hold(emitter_);
    hir->lval()->load(emitter_);
    hir->lval()->swapAndPick(emitter_);
    hir->rval()->accept(this);
    __ binary(TokenToOpcode(hir->kind()));
    hir->lval()->store(emitter_);
    __ pop();
}

void
HIRTranslator::visitBinary(HBinary *binary)
{
    binary->left()->accept(this);
    binary->right()->accept(this);

    // We define three sets of math operators:
    //
    //  (1) ADD, SUB, MUL, DIV, MOD, which take two equal types and produce
    //      the same type.
    //  (2) LT, LE, GE, GT, EQ, NE, which take two equal types and produce
    //      a boolean.
    //  (3) SHL, SR, USHR, BITAND, BITOR, BITXOR, BITNOT, which take two
    //      equal integer types and return that integer type.
    //
    Opcode op = TokenToOpcode(binary->token());

    if (op == OP_DIV)
        __ note_position(binary->node()->pos());

    if (op >= OP_ADD && op <= OP_NE) {
        if (binary->type()->isFloat())
            op = Opcode(int(op) + int(OP_ADD_F - OP_ADD));
    } else {
        assert(binary->type()->isInt32());
    }

    __ binary(op);
}

void
HIRTranslator::visitUnary(HUnary *unary)
{
    unary->in()->accept(this);

    switch (unary->token()) {
      case TOK_TILDE:
        __ bitnot();
        break;

      case TOK_MINUS:
        if (unary->type()->isFloat())
            __ neg_f();
        else
            __ neg();
        break;

      default:
        assert(unary->token() == TOK_LABEL);
        __ bitcast(unary->type());
        break;
    }
}

void
HIRTranslator::visitConvert(HConvert *convert)
{
    convert->in()->accept(this);
    __ unary(convert->opcode());
}

void
HIRTranslator::visitToRef(HToRef *hir)
{
    HIR *in = hir->in();

    // See if we can bind a direct reference.
    if (in->isLValue() && in->toLValue()->ref(emitter_))
        return;

    // No, we can't. So instead we create an on-stack reference.
    in->accept(this);
    __ slotref_lifo();
}

void
HIRTranslator::visitNewDependentArray(HNewDependentArray *hir)
{
    hir->base()->accept(this);
    hir->index()->accept(this);

    Local<ArrayType> type(zone_, ArrayType::cast(hir->type()));

    // The dependent map is used for the opcode, but is not attached.
    Local<ArrayMap> map(zone_, ArrayMap::NewDependent(zone_, type));
    if (!map)
        return;

    __ deparray(map);
}

void
HIRTranslator::visitImport(HImport *import)
{
    __ import(import->index());
}

void
ke::EmitHIR(Zone *zone, BytecodeEmitter &emitter, HIR *hir)
{
    HIRTranslator translator(zone, emitter);
    translator.translate(hir);
}
