/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The Initial Developer of this code is David Baum.
 * Portions created by David Baum are Copyright (C) 1999 David Baum.
 * All Rights Reserved.
 *
 * Portions created by John Hansen are Copyright (C) 2005 John Hansen.
 * All Rights Reserved.
 *
 */
#include "Expr.h"
#include "RCX_Cmd.h"
#include "Bytecode.h"
#include "Error.h"
#include "RCX_Target.h"

#define MAX_INT 65535
#define MIN_INT -32768

// suitable sources for math operations (if target has constrained math)
#define MATH_MASK   (TYPEMASK(kRCX_VariableType) + \
                    TYPEMASK(kRCX_ConstantType))

static bool IsTypeInMask(long mask, RCX_ValueType type);


Expr::~Expr()
{
}


RCX_Value Expr::EmitAny(Bytecode &b) const
{
    int v;

    if (Evaluate(v)) {
        if (v < MIN_INT || v > MAX_INT)
            Error(kErr_NumberRange).Raise(&fLoc);

        return RCX_VALUE(kRCX_ConstantType, v);
    }
    else
        return EmitAny_(b);
}


bool Expr::EmitTo(Bytecode &b, int dst) const
{
    int v;

    if (Evaluate(v)) {
        b.AddMove(dst, RCX_VALUE(kRCX_ConstantType, v));
        return true;
    }
    else
        return EmitTo_(b, dst);
}


RCX_Value Expr::EmitConstrained(Bytecode &b, long mask, bool canUseLocals) const
{
    /*
     * There's one case that still isn't optimized...if locals cannot be used,
     * but EmitAny() wants a temp (such as for a math operation), then it is
     * possible for a local temp to be allocated, which just forces a move to
     * a global temp later on.  It would be better if EmitAny() could be told
     * not to use locals, but this could involve a lot of tweaking.  A possible
     * hack is to put the VarAllocator into kGlobal mode prior to an AsmField
     * calling EmitConstrained, then restoring the mode later on.
     */
    RCX_Value ea = EmitAny(b);
    if (ea == kIllegalEA) return ea;

    RCX_ValueType type = RCX_VALUE_TYPE(ea);

    // check ea was allowed
    if (IsTypeInMask(mask, type) && (canUseLocals || !b.IsLocalEA(ea))) {
            return ea;
    }

    // must move to another ea

    // if registers are not valid, then return illegal EA
    if (!IsTypeInMask(mask, kRCX_VariableType)) {
        Error(kErr_BadExpression).Raise(&GetLoc());
        return kIllegalEA;
    }

    // move into temp register
    int dst = GetTempVar(b, canUseLocals);
    if (dst == kIllegalVar) return kIllegalEA;

    b.AddMove(dst, ea);

    // when using arrays, there's a special case when the ea is
    // temp but not a variable.  Another case arises if the original
    // emit was to a temp local, but they aren't allowed.
    // call ReleaseTempEA just in case
    b.ReleaseTempEA(ea);

    ea = RCX_VALUE(kRCX_VariableType, dst);

    return ea;
}

bool IsTypeInMask(long mask, RCX_ValueType type)
{
    // special case, when mask==0 all types are allowed
    if (!mask) return true;

    return (1L << type) & mask;
}

RCX_Value Expr::EmitMath(Bytecode &b) const
{
    // emit expression to an ea that is suitable for math operations
    if (b.GetTarget()->fRestrictedMath)
        return EmitConstrained(b, MATH_MASK);
    else
        return EmitAny(b);
}


bool Expr::EmitSide(Bytecode &b) const
{
    return EmitSide_(b);
}


bool Expr::EmitBranch(Bytecode &b, int label, bool condition) const
{
    int v;

    if (Evaluate(v)) {
        if ((v && condition)
            || (!v && !condition))
        {
            b.AddJump(label);
        }

        return true;
    }

    return EmitBranch_(b, label, condition);
}


RCX_Value Expr::GetStaticEA() const
{
    int v;

    if (Evaluate(v)) {
        return RCX_VALUE(kRCX_ConstantType, v);
    }

    return GetStaticEA_();
}


RCX_Value Expr::GetStaticEA_() const
{
    return kIllegalEA;
}


bool Expr::EmitTo_(Bytecode &b, int dst) const
{
    RCX_Value ea = EmitAny_(b);

    if (ea == kIllegalEA) return false;

    b.AddMove(dst, ea);
    b.ReleaseTempEA(ea);

    return true;
}


bool Expr::EmitSide_(Bytecode &b) const
{
    vector<Expr *> v;
    GetExprs(v);
    for(int i=0; i<(int)v.size(); ++i)
        if (!v[i]->EmitSide(b)) return false;

    return true;
}


bool Expr::EmitBranch_(Bytecode &b, int label, bool condition) const
{
    RCX_Value ea = EmitConstrained(b, TEST_MASK);
    if (ea==kIllegalEA) return false;

    // compare ea to 0 and branch
    b.AddTest(
        RCX_VALUE(kRCX_ConstantType,0),
        condition ? kRCX_NotEqualTo : kRCX_EqualTo,
        ea, label);

    b.ReleaseTempEA(ea);

    return true;
}


RCX_Value Expr::EmitBoolAny(Bytecode &b) const
{
    int dst = GetTempVar(b);
    if (dst < 0)
        return kIllegalEA;

    RCX_Value dstEA = RCX_VALUE(kRCX_VariableType, dst);

    if (!EmitBoolTo(b, dst)) {
        b.ReleaseTempEA(dstEA);
        return kIllegalEA;
    }

    return dstEA;
}


bool Expr::EmitBoolTo(Bytecode &b, int dst) const
{
    int doneLabel = b.NewLabel();

    if (Contains(dst)) {
        /*
         *   if condition then goto 1
         *   dst = 0
         *   goto done
         * 1:
         *   dst = 1
         * done:
         */
        int setLabel = b.NewLabel();
        EmitBranch(b, setLabel, true);
        b.AddMove(dst, RCX_VALUE(kRCX_ConstantType, 0));
        b.AddJump(doneLabel);
        b.SetLabel(setLabel);
        b.AddMove(dst, RCX_VALUE(kRCX_ConstantType, 1));
    }
    else {
        /*   dst = 0
         *   if !condition then goto done
         *   dst = 1
         * done:
         */
        b.AddMove(dst, RCX_VALUE(kRCX_ConstantType, 0));
        EmitBranch(b, doneLabel, false);
        b.AddMove(dst, RCX_VALUE(kRCX_ConstantType, 1));
    }

    b.SetLabel(doneLabel);
    return true;
}


int Expr::GetTempVar(Bytecode &b, bool canUseLocals) const
{
    int var = b.GetTempVar(canUseLocals);

    if (var == kIllegalVar) {
        Error(kErr_NoMoreTemps).Raise(&GetLoc());
    }

    return var;
}
