/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.code;

import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstUtf8;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeBearer;
import com.android.dx.util.ToHuman;

import java.util.HashMap;

/**
 * Combination of a register number and a type, used as the sources and
 * destinations of register-based operations.
 */
public final class RegisterSpec
        implements TypeBearer, ToHuman, Comparable<RegisterSpec> {
    /** {@code non-null;} string to prefix register numbers with */
    public static final String PREFIX = "v";

    /** {@code non-null;} intern table for instances */
    private static final HashMap<Object, RegisterSpec> theInterns =
        new HashMap<Object, RegisterSpec>(1000);

    /** {@code non-null;} common comparison instance used while interning */
    private static final ForComparison theInterningItem = new ForComparison();

    /** {@code >= 0;} register number */
    private final int reg;

    /** {@code non-null;} type loaded or stored */
    private final TypeBearer type;

    /** {@code null-ok;} local variable info associated with this register, if any */
    private final LocalItem local;

    /**
     * Intern the given triple as an instance of this class.
     *
     * @param reg {@code >= 0;} the register number
     * @param type {@code non-null;} the type (or possibly actual value) which
     * is loaded from or stored to the indicated register
     * @param local {@code null-ok;} the associated local variable, if any
     * @return {@code non-null;} an appropriately-constructed instance
     */
    private static RegisterSpec intern(int reg, TypeBearer type,
            LocalItem local) {
        theInterningItem.set(reg, type, local);
        RegisterSpec found = theInterns.get(theInterningItem);

        if (found != null) {
            return found;
        }

        found = theInterningItem.toRegisterSpec();
        theInterns.put(found, found);
        return found;
    }

    /**
     * Returns an instance for the given register number and type, with
     * no variable info. This method is allowed to return shared
     * instances (but doesn't necessarily do so).
     *
     * @param reg {@code >= 0;} the register number
     * @param type {@code non-null;} the type (or possibly actual value) which
     * is loaded from or stored to the indicated register
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public static RegisterSpec make(int reg, TypeBearer type) {
        return intern(reg, type, null);
    }

    /**
     * Returns an instance for the given register number, type, and
     * variable info. This method is allowed to return shared
     * instances (but doesn't necessarily do so).
     *
     * @param reg {@code >= 0;} the register number
     * @param type {@code non-null;} the type (or possibly actual value) which
     * is loaded from or stored to the indicated register
     * @param local {@code non-null;} the associated local variable
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public static RegisterSpec make(int reg, TypeBearer type,
            LocalItem local) {
        if (local == null) {
            throw new NullPointerException("local  == null");
        }

        return intern(reg, type, local);
    }

    /**
     * Returns an instance for the given register number, type, and
     * variable info. This method is allowed to return shared
     * instances (but doesn't necessarily do so).
     *
     * @param reg {@code >= 0;} the register number
     * @param type {@code non-null;} the type (or possibly actual value) which
     * is loaded from or stored to the indicated register
     * @param local {@code null-ok;} the associated variable info or null for
     * none
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public static RegisterSpec makeLocalOptional(
            int reg, TypeBearer type, LocalItem local) {

        return intern(reg, type, local);
    }

    /**
     * Gets the string form for the given register number.
     *
     * @param reg {@code >= 0;} the register number
     * @return {@code non-null;} the string form
     */
    public static String regString(int reg) {
        return PREFIX + reg;
    }

    /**
     * Constructs an instance. This constructor is private. Use
     * {@link #make}.
     *
     * @param reg {@code >= 0;} the register number
     * @param type {@code non-null;} the type (or possibly actual value) which
     * is loaded from or stored to the indicated register
     * @param local {@code null-ok;} the associated local variable, if any
     */
    private RegisterSpec(int reg, TypeBearer type, LocalItem local) {
        if (reg < 0) {
            throw new IllegalArgumentException("reg < 0");
        }

        if (type == null) {
            throw new NullPointerException("type == null");
        }

        this.reg = reg;
        this.type = type;
        this.local = local;
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof RegisterSpec)) {
            if (other instanceof ForComparison) {
                ForComparison fc = (ForComparison) other;
                return equals(fc.reg, fc.type, fc.local);
            }
            return false;
        }

        RegisterSpec spec = (RegisterSpec) other;
        return equals(spec.reg, spec.type, spec.local);
    }

    /**
     * Like {@code equals}, but only consider the simple types of the
     * registers. That is, this compares {@code getType()} on the types
     * to ignore whatever arbitrary extra stuff might be carried around
     * by an outer {@link TypeBearer}.
     *
     * @param other {@code null-ok;} spec to compare to
     * @return {@code true} iff {@code this} and {@code other} are equal
     * in the stated way
     */
    public boolean equalsUsingSimpleType(RegisterSpec other) {
        if (!matchesVariable(other)) {
            return false;
        }

        return (reg == other.reg);
    }

    /**
     * Like {@link #equalsUsingSimpleType} but ignoring the register number.
     * This is useful to determine if two instances refer to the "same"
     * local variable.
     *
     * @param other {@code null-ok;} spec to compare to
     * @return {@code true} iff {@code this} and {@code other} are equal
     * in the stated way
     */
    public boolean matchesVariable(RegisterSpec other) {
        if (other == null) {
            return false;
        }

        return type.getType().equals(other.type.getType())
            && ((local == other.local)
                    || ((local != null) && local.equals(other.local)));
    }

    /**
     * Helper for {@link #equals} and {@link #ForComparison.equals},
     * which actually does the test.
     *
     * @param reg value of the instance variable, for another instance
     * @param type value of the instance variable, for another instance
     * @param local value of the instance variable, for another instance
     * @return whether this instance is equal to one with the given
     * values
     */
    private boolean equals(int reg, TypeBearer type, LocalItem local) {
        return (this.reg == reg)
            && this.type.equals(type)
            && ((this.local == local)
                    || ((this.local != null) && this.local.equals(local)));
    }

    /**
     * Compares by (in priority order) register number, unwrapped type
     * (that is types not {@link TypeBearer}s, and local info.
     *
     * @param other {@code non-null;} spec to compare to
     * @return {@code -1..1;} standard result of comparison
     */
    public int compareTo(RegisterSpec other) {
        if (this.reg < other.reg) {
            return -1;
        } else if (this.reg > other.reg) {
            return 1;
        }

        int compare = type.getType().compareTo(other.type.getType());

        if (compare != 0) {
            return compare;
        }

        if (this.local == null) {
            return (other.local == null) ? 0 : -1;
        } else if (other.local == null) {
            return 1;
        }

        return this.local.compareTo(other.local);
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        return hashCodeOf(reg, type, local);
    }

    /**
     * Helper for {@link #hashCode} and {@link #ForComparison.hashCode},
     * which actually does the calculation.
     *
     * @param reg value of the instance variable
     * @param type value of the instance variable
     * @param local value of the instance variable
     * @return the hash code
     */
    private static int hashCodeOf(int reg, TypeBearer type, LocalItem local) {
        int hash = (local != null) ? local.hashCode() : 0;

        hash = (hash * 31 + type.hashCode()) * 31 + reg;
        return hash;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return toString0(false);
    }

    /** {@inheritDoc} */
    public String toHuman() {
        return toString0(true);
    }

    /** {@inheritDoc} */
    public Type getType() {
        return type.getType();
    }

    /** {@inheritDoc} */
    public TypeBearer getFrameType() {
        return type.getFrameType();
    }

    /** {@inheritDoc} */
    public final int getBasicType() {
        return type.getBasicType();
    }

    /** {@inheritDoc} */
    public final int getBasicFrameType() {
        return type.getBasicFrameType();
    }

    /** {@inheritDoc} */
    public final boolean isConstant() {
        return false;
    }

    /**
     * Gets the register number.
     *
     * @return {@code >= 0;} the register number
     */
    public int getReg() {
        return reg;
    }

    /**
     * Gets the type (or actual value) which is loaded from or stored
     * to the register associated with this instance.
     *
     * @return {@code non-null;} the type
     */
    public TypeBearer getTypeBearer() {
        return type;
    }

    /**
     * Gets the variable info associated with this instance, if any.
     *
     * @return {@code null-ok;} the variable info, or {@code null} if this
     * instance has none
     */
    public LocalItem getLocalItem() {
        return local;
    }

    /**
     * Gets the next available register number after the one in this
     * instance. This is equal to the register number plus the width
     * (category) of the type used. Among other things, this may also
     * be used to determine the minimum required register count
     * implied by this instance.
     *
     * @return {@code >= 0;} the required registers size
     */
    public int getNextReg() {
        return reg + getCategory();
    }

    /**
     * Gets the category of this instance's type. This is just a convenient
     * shorthand for {@code getType().getCategory()}.
     *
     * @see #isCategory1
     * @see #isCategory2
     * @return {@code 1..2;} the category of this instance's type
     */
    public int getCategory() {
        return type.getType().getCategory();
    }

    /**
     * Gets whether this instance's type is category 1. This is just a
     * convenient shorthand for {@code getType().isCategory1()}.
     *
     * @see #getCategory
     * @see #isCategory2
     * @return whether or not this instance's type is of category 1
     */
    public boolean isCategory1() {
        return type.getType().isCategory1();
    }

    /**
     * Gets whether this instance's type is category 2. This is just a
     * convenient shorthand for {@code getType().isCategory2()}.
     *
     * @see #getCategory
     * @see #isCategory1
     * @return whether or not this instance's type is of category 2
     */
    public boolean isCategory2() {
        return type.getType().isCategory2();
    }

    /**
     * Gets the string form for just the register number of this instance.
     *
     * @return {@code non-null;} the register string form
     */
    public String regString() {
        return regString(reg);
    }

    /**
     * Returns an instance that is the intersection between this instance
     * and the given one, if any. The intersection is defined as follows:
     *
     * <ul>
     *   <li>If {@code other} is {@code null}, then the result
     *     is {@code null}.
     *   <li>If the register numbers don't match, then the intersection
     *     is {@code null}. Otherwise, the register number of the
     *     intersection is the same as the one in the two instances.</li>
     *   <li>If the types returned by {@code getType()} are not
     *     {@code equals()}, then the intersection is null.</li>
     *   <li>If the type bearers returned by {@code getTypeBearer()}
     *     are {@code equals()}, then the intersection's type bearer
     *     is the one from this instance. Otherwise, the intersection's
     *     type bearer is the {@code getType()} of this instance.</li>
     *   <li>If the locals are {@code equals()}, then the local info
     *     of the intersection is the local info of this instance. Otherwise,
     *     the local info of the intersection is {@code null}.</li>
     * </ul>
     *
     * @param other {@code null-ok;} instance to intersect with (or {@code null})
     * @param localPrimary whether local variables are primary to the
     * intersection; if {@code true}, then the only non-null
     * results occur when registers being intersected have equal local
     * infos (or both have {@code null} local infos)
     * @return {@code null-ok;} the intersection
     */
    public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) {
        if (this == other) {
            // Easy out.
            return this;
        }

        if ((other == null) || (reg != other.getReg())) {
            return null;
        }

        LocalItem resultLocal =
            ((local == null) || !local.equals(other.getLocalItem()))
            ? null : local;
        boolean sameName = (resultLocal == local);

        if (localPrimary && !sameName) {
            return null;
        }

        Type thisType = getType();
        Type otherType = other.getType();

        // Note: Types are always interned.
        if (thisType != otherType) {
            return null;
        }

        TypeBearer resultTypeBearer =
            type.equals(other.getTypeBearer()) ? type : thisType;

        if ((resultTypeBearer == type) && sameName) {
            // It turns out that the intersection is "this" after all.
            return this;
        }

        return (resultLocal == null) ? make(reg, resultTypeBearer) :
            make(reg, resultTypeBearer, resultLocal);
    }

    /**
     * Returns an instance that is identical to this one, except that the
     * register number is replaced by the given one.
     *
     * @param newReg {@code >= 0;} the new register number
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public RegisterSpec withReg(int newReg) {
        if (reg == newReg) {
            return this;
        }

        return makeLocalOptional(newReg, type, local);
    }

    /**
     * Returns an instance that is identical to this one, except that
     * the type is replaced by the given one.
     *
     * @param newType {@code non-null;} the new type
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public RegisterSpec withType(TypeBearer newType) {
        return makeLocalOptional(reg, newType, local);
    }

    /**
     * Returns an instance that is identical to this one, except that the
     * register number is offset by the given amount.
     *
     * @param delta the amount to offset the register number by
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public RegisterSpec withOffset(int delta) {
        if (delta == 0) {
            return this;
        }

        return withReg(reg + delta);
    }

    /**
     * Returns an instance that is identical to this one, except that
     * the type bearer is replaced by the actual underlying type
     * (thereby stripping off non-type information) with any
     * initialization information stripped away as well.
     *
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public RegisterSpec withSimpleType() {
        TypeBearer orig = type;
        Type newType;

        if (orig instanceof Type) {
            newType = (Type) orig;
        } else {
            newType = orig.getType();
        }

        if (newType.isUninitialized()) {
            newType = newType.getInitializedType();
        }

        if (newType == orig) {
            return this;
        }

        return makeLocalOptional(reg, newType, local);
    }

    /**
     * Returns an instance that is identical to this one except that the
     * local variable is as specified in the parameter.
     *
     * @param local {@code null-ok;} the local item or null for none
     * @return an appropriate instance
     */
    public RegisterSpec withLocalItem(LocalItem local) {
        if ((this.local== local)
                    || ((this.local != null) && this.local.equals(local))) {

            return this;
        }

        return makeLocalOptional(reg, type, local);
    }


    /**
     * Helper for {@link #toString} and {@link #toHuman}.
     *
     * @param human whether to be human-oriented
     * @return {@code non-null;} the string form
     */
    private String toString0(boolean human) {
        StringBuffer sb = new StringBuffer(40);

        sb.append(regString());
        sb.append(":");

        if (local != null) {
            sb.append(local.toString());
        }

        Type justType = type.getType();
        sb.append(justType);

        if (justType != type) {
            sb.append("=");
            if (human && (type instanceof Constant)) {
                sb.append(((Constant) type).toHuman());
            } else {
                sb.append(type);
            }
        }

        return sb.toString();
    }

    /**
     * Holder of register spec data for the purposes of comparison (so that
     * {@code RegisterSpec} itself can still keep {@code final}
     * instance variables.
     */
    private static class ForComparison {
        /** {@code >= 0;} register number */
        private int reg;

        /** {@code non-null;} type loaded or stored */
        private TypeBearer type;

        /**
         * {@code null-ok;} local variable associated with this
         * register, if any
         */
        private LocalItem local;

        /**
         * Set all the instance variables.
         *
         * @param reg {@code >= 0;} the register number
         * @param type {@code non-null;} the type (or possibly actual
         * value) which is loaded from or stored to the indicated
         * register
         * @param local {@code null-ok;} the associated local variable, if any
         * @return {@code non-null;} an appropriately-constructed instance
         */
        public void set(int reg, TypeBearer type, LocalItem local) {
            this.reg = reg;
            this.type = type;
            this.local = local;
        }

        /**
         * Construct a {@code RegisterSpec} of this instance's
         * contents.
         *
         * @return {@code non-null;} an appropriately-constructed instance
         */
        public RegisterSpec toRegisterSpec() {
            return new RegisterSpec(reg, type, local);
        }

        /** {@inheritDoc} */
        @Override
        public boolean equals(Object other) {
            if (!(other instanceof RegisterSpec)) {
                return false;
            }

            RegisterSpec spec = (RegisterSpec) other;
            return spec.equals(reg, type, local);
        }

        /** {@inheritDoc} */
        @Override
        public int hashCode() {
            return hashCodeOf(reg, type, local);
        }
    }
}
