/*
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of SourcePawn.
 *
 * SourcePawn 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.
 * 
 * SourcePawn 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
 * SourcePawn. If not, see http://www.gnu.org/licenses/.
 */
#ifndef _include_sp2_hir_h_
#define _include_sp2_hir_h_

#include "../Types.h"
#include "../PoolAllocator.h"
#include "../Opcodes.h"
#include "Symbols.h"

namespace ke {

#define HIR_OPS(_)                      \
    _(Call)                             \
    _(Integer)                          \
    _(Float)                            \
    _(Store)                            \
    _(ToRef)                            \
    /* Special LValue types */          \
    _(Index)                            \
    _(Local)                            \
    _(Global)                           \
    /* End LValues */                   \
    _(Binary)                           \
    _(Convert)                          \
    _(PostIncDec)

// Forward declarations.
#define _(name)    class H##name;
HIR_OPS(_)
#undef _

class HIRVisitor;
class AstNode;
class HLValue;

class HIR : public PoolObject
{
  public:
    enum HOp {
#define _(name) k##name,
        HIR_OPS(_)
#undef _
        kTotalOps
    };

  public:
    HIR(AstNode *node, Type *type)
      : node_(node),
        type_(type)
    {
    }

    Handle<Type> type() {
        return type_;
    }
    AstNode *node() const {
        return node_;
    }

    virtual bool isLValue() const {
        return false;
    }

    virtual HOp op() const = 0;
    virtual void accept(HIRVisitor *visitor) = 0;

#define _(name)                                         \
    bool is##name() const {                             \
        return op() == k##name;                         \
    }                                                   \
    H##name *to##name() {                               \
        assert(is##name());                             \
        return reinterpret_cast<H##name *>(this);       \
    }
    HIR_OPS(_)
#undef _

    HLValue *toLValue() {
        assert(isLValue());
        return reinterpret_cast<HLValue *>(this);
    }

  private:
    AstNode *node_;
    ScopedRoot<Type> type_;
};

class BytecodeEmitter;

class HIRVisitor
{
  public:
#define _(name)                                 \
    virtual void visit##name(H##name *hir) {    \
        assert(false);                          \
    }
    HIR_OPS(_)
#undef _
};

typedef PoolList<HIR *> HIRList;

#define DEFINE_HIR(name)                        \
    virtual HOp op() const {                    \
        return k##name;                         \
    }                                           \
    virtual void accept(HIRVisitor *visitor) {  \
        visitor->visit##name(this);             \
    }

class HInteger : public HIR
{
  public:
    HInteger(AstNode *node, Type *type, int value)
      : HIR(node, type),
        value_(value)
    {
    }

    DEFINE_HIR(Integer);
    int value() const {
        return value_;
    }

  private:
    int value_;
};

class HConvert : public HIR
{
  public:
    HConvert(AstNode *node, Type *type, Opcode op, HIR *in)
      : HIR(node, type),
        op_(op),
        in_(in)
    {
    }

    DEFINE_HIR(Convert);
    Opcode opcode() const {
        return op_;
    }
    HIR *in() const {
        return in_;
    }

  private:
    Opcode op_;
    HIR *in_;
};

class HLValue : public HIR
{
  public:
    HLValue(AstNode *node, Type *type)
      : HIR(node, type)
    {
    }

    // Pushes a reference to the LValue on the stack.
    virtual void bind(HIRVisitor *visitor, BytecodeEmitter &emitter_) = 0;
    
    // Duplicates a stack reference.
    virtual void hold(BytecodeEmitter &emitter_) = 0;

    // Reorders the stack for a post-increment operation:
    //   REF VAL -> VAL REF VAL
    virtual void swapAndPick(BytecodeEmitter &emitter_) = 0;

    // Given a stack layout of the form:
    //  [-1]    value
    //  [-2..n] ref
    //
    // Emits a store to the reference.
    virtual void store(BytecodeEmitter &emitter_) = 0;
    virtual void load(BytecodeEmitter &emitter_) = 0;

    // Try to make a direct reference, if semantics allow it. False if the
    // caller must allocate a reference to the stack.
    virtual bool ref(BytecodeEmitter &emitter_) = 0;

    bool isLValue() const {
        return true;
    }
};

class HLocal : public HLValue
{
  public:
    HLocal(AstNode *node, VariableSymbol *sym);

    DEFINE_HIR(Local);

    // Pushes a reference to the LValue on the stack.
    void bind(HIRVisitor *visitor, BytecodeEmitter &emitter_);
    void hold(BytecodeEmitter &emitter_);
    void swapAndPick(BytecodeEmitter &emitter_);
    void store(BytecodeEmitter &emitter_);
    void load(BytecodeEmitter &emitter_);
    bool ref(BytecodeEmitter &emitter_);

    VariableSymbol *sym() const {
        return sym_;
    }

  private:
    VariableSymbol *sym_;
};

class HIndex : public HLValue
{
  public:
    HIndex(AstNode *node, Type *type, HIR *left, HIR *right)
      : HLValue(node, type),
        left_(left),
        right_(right)
    {
    }

    DEFINE_HIR(Index);

    void bind(HIRVisitor *visitor, BytecodeEmitter &emitter_);
    void hold(BytecodeEmitter &emitter_);
    void swapAndPick(BytecodeEmitter &emitter_);
    void store(BytecodeEmitter &emitter_);
    void load(BytecodeEmitter &emitter_);
    bool ref(BytecodeEmitter &emitter_);

  private:
    HIR *left_;
    HIR *right_;
};

class HGlobal : public HLValue
{
  public:
    HGlobal(AstNode *node, Symbol *sym)
      : HLValue(node, sym->type()),
        sym_(sym)
    {
    }

    DEFINE_HIR(Global);

    void bind(HIRVisitor *visitor, BytecodeEmitter &emitter_);
    void hold(BytecodeEmitter &emitter_);
    void swapAndPick(BytecodeEmitter &emitter_);
    void store(BytecodeEmitter &emitter_);
    void load(BytecodeEmitter &emitter_);
    bool ref(BytecodeEmitter &emitter_);

    Symbol *sym() const {
        return sym_;
    }

  private:
    Symbol *sym_;
};

class HBinary : public HIR
{
  public:
    HBinary(AstNode *node, Type *type, TokenKind token, HIR *left, HIR *right)
      : HIR(node, type),
        token_(token),
        left_(left),
        right_(right)
    {
    }

    DEFINE_HIR(Binary);
    TokenKind token() const {
        return token_;
    }
    HIR *left() const {
        return left_;
    }
    HIR *right() const {
        return right_;
    }

  private:
    TokenKind token_;
    HIR *left_;
    HIR *right_;
};

class HFloat : public HIR
{
  public:
    HFloat(AstNode *node, Type *type, float value)
      : HIR(node, type),
        value_(value)
    {
    }

    DEFINE_HIR(Float);
    float value() const {
        return value_;
    }

  private:
    float value_;
};

class HStore : public HIR
{
  public:
    HStore(AstNode *node, HLValue *lval, HIR *rval)
      : HIR(node, lval->type()),
        kind_(TOK_ASSIGN),
        lval_(lval),
        rval_(rval)
    {
    }

    HStore(AstNode *node, TokenKind kind, HLValue *lval, HIR *rval)
      : HIR(node, lval->type()),
        kind_(kind),
        lval_(lval),
        rval_(rval)
    {
    }

    DEFINE_HIR(Store);

    HLValue *lval() const {
        return lval_;
    }
    HIR *rval() const {
        return rval_;
    }
    TokenKind kind() const {
        return kind_;
    }

  private:
    TokenKind kind_;
    HLValue *lval_;
    HIR *rval_;
};

class HPostIncDec : public HIR
{
  public:
    HPostIncDec(AstNode *node, TokenKind kind, HLValue *lval, HIR *rval)
      : HIR(node, lval->type()),
        kind_(kind),
        lval_(lval),
        rval_(rval)
    {
    }

    DEFINE_HIR(PostIncDec);

    HLValue *lval() const {
        return lval_;
    }
    HIR *rval() const {
        return rval_;
    }
    TokenKind kind() const {
        return kind_;
    }

  private:
    TokenKind kind_;
    HLValue *lval_;
    HIR *rval_;
};

class HCall : public HIR
{
  public:
    HCall(AstNode *node, Type *type, HIR *callee, HIRList *args)
      : HIR(node, type),
        callee_(callee),
        args_(args)
    {
    }

    DEFINE_HIR(Call);

    HIR *callee() const {
        return callee_;
    }
    HIRList *args() const {
        return args_;
    }

  private:
    HIR *callee_;
    HIRList *args_;
};

class HToRef : public HIR
{
  public:
    HToRef(AstNode *node, Type *type, HIR *in)
      : HIR(node, type),
        in_(in)
    {
    }

    DEFINE_HIR(ToRef);

    HIR *in() const {
        return in_;
    }

  private:
    HIR *in_;
};

#undef DEFINE_HIR

void EmitHIR(Zone *zone, BytecodeEmitter &emitter, HIR *hir);

}

#endif // _include_sp2_hir_h_
