/*
 * Copyright 2013, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jf.dexlib2.analysis;


import org.jf.util.ExceptionWithContext;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Writer;

public class RegisterType {
    public final byte category;
    @Nullable public final TypeProto type;

    private RegisterType(byte category, @Nullable TypeProto type) {
        assert ((category == REFERENCE || category == UNINIT_REF || category == UNINIT_THIS) && type != null) ||
               ((category != REFERENCE && category != UNINIT_REF && category != UNINIT_THIS) && type == null);

        this.category = category;
        this.type = type;
    }

    @Override
    public String toString() {
        return "(" + CATEGORY_NAMES[category] + (type==null?"":("," + type)) + ")";
    }

    public void writeTo(Writer writer) throws IOException {
        writer.write('(');
        writer.write(CATEGORY_NAMES[category]);
        if (type != null) {
            writer.write(',');
            writer.write(type.getType());
        }
        writer.write(')');
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        RegisterType that = (RegisterType) o;

        if (category != that.category) {
            return false;
        }

        // These require strict reference equality. Every instance represents a unique
        // reference that can't be merged with a different one, even if they have the same type.
        if (category == UNINIT_REF || category == UNINIT_THIS) {
            return false;
        }
        return (type != null ? type.equals(that.type) : that.type == null);
    }

    @Override
    public int hashCode() {
        int result = category;
        result = 31 * result + (type != null ? type.hashCode() : 0);
        return result;
    }
    
    // The Unknown category denotes a register type that hasn't been determined yet
    public static final byte UNKNOWN = 0;
    // The Uninit category is for registers that haven't been set yet. e.g. the non-parameter registers in a method
    // start out as unint
    public static final byte UNINIT = 1;
    public static final byte NULL = 2;
    public static final byte ONE = 3;
    public static final byte BOOLEAN = 4;
    public static final byte BYTE = 5;
    public static final byte POS_BYTE = 6;
    public static final byte SHORT = 7;
    public static final byte POS_SHORT = 8;
    public static final byte CHAR = 9;
    public static final byte INTEGER = 10;
    public static final byte FLOAT = 11;
    public static final byte LONG_LO = 12;
    public static final byte LONG_HI = 13;
    public static final byte DOUBLE_LO = 14;
    public static final byte DOUBLE_HI = 15;
    // The UninitRef category is used after a new-instance operation, and before the corresponding <init> is called
    public static final byte UNINIT_REF = 16;
    // The UninitThis category is used the "this" register inside an <init> method, before the superclass' <init>
    // method is called
    public static final byte UNINIT_THIS = 17;
    public static final byte REFERENCE = 18;
    // This is used when there are multiple incoming execution paths that have incompatible register types. For
    // example if the register's type is an Integer on one incoming code path, but is a Reference type on another
    // incomming code path. There is no register type that can hold either an Integer or a Reference.
    public static final byte CONFLICTED = 19;

    public static final String[] CATEGORY_NAMES = new String[] {
            "Unknown",
            "Uninit",
            "Null",
            "One",
            "Boolean",
            "Byte",
            "PosByte",
            "Short",
            "PosShort",
            "Char",
            "Integer",
            "Float",
            "LongLo",
            "LongHi",
            "DoubleLo",
            "DoubleHi",
            "UninitRef",
            "UninitThis",
            "Reference",
            "Conflicted"
    };

    //this table is used when merging register types. For example, if a particular register can be either a BYTE
    //or a Char, then the "merged" type of that register would be Integer, because it is the "smallest" type can
    //could hold either type of value.
    protected static byte[][] mergeTable  =
    {
            /*              UNKNOWN      UNINIT      NULL        ONE,        BOOLEAN     BYTE        POS_BYTE    SHORT       POS_SHORT   CHAR        INTEGER,    FLOAT,      LONG_LO     LONG_HI     DOUBLE_LO   DOUBLE_HI   UNINIT_REF  UNINIT_THIS REFERENCE   CONFLICTED*/
            /*UNKNOWN*/    {UNKNOWN,     UNINIT,     NULL,       ONE,        BOOLEAN,    BYTE,       POS_BYTE,   SHORT,      POS_SHORT,  CHAR,       INTEGER,    FLOAT,      LONG_LO,    LONG_HI,    DOUBLE_LO,  DOUBLE_HI,  UNINIT_REF, UNINIT_THIS,REFERENCE,  CONFLICTED},
            /*UNINIT*/     {UNINIT,      UNINIT,     CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*NULL*/       {NULL,        CONFLICTED, NULL,       BOOLEAN,    BOOLEAN,    BYTE,       POS_BYTE,   SHORT,      POS_SHORT,  CHAR,       INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, REFERENCE,  CONFLICTED},
            /*ONE*/        {ONE,         CONFLICTED, BOOLEAN,    ONE,        BOOLEAN,    BYTE,       POS_BYTE,   SHORT,      POS_SHORT,  CHAR,       INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*BOOLEAN*/    {BOOLEAN,     CONFLICTED, BOOLEAN,    BOOLEAN,    BOOLEAN,    BYTE,       POS_BYTE,   SHORT,      POS_SHORT,  CHAR,       INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*BYTE*/       {BYTE,        CONFLICTED, BYTE,       BYTE,       BYTE,       BYTE,       BYTE,       SHORT,      SHORT,      INTEGER,    INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*POS_BYTE*/   {POS_BYTE,    CONFLICTED, POS_BYTE,   POS_BYTE,   POS_BYTE,   BYTE,       POS_BYTE,   SHORT,      POS_SHORT,  CHAR,       INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*SHORT*/      {SHORT,       CONFLICTED, SHORT,      SHORT,      SHORT,      SHORT,      SHORT,      SHORT,      SHORT,      INTEGER,    INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*POS_SHORT*/  {POS_SHORT,   CONFLICTED, POS_SHORT,  POS_SHORT,  POS_SHORT,  SHORT,      POS_SHORT,  SHORT,      POS_SHORT,  CHAR,       INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*CHAR*/       {CHAR,        CONFLICTED, CHAR,       CHAR,       CHAR,       INTEGER,    CHAR,       INTEGER,    CHAR,       CHAR,       INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*INTEGER*/    {INTEGER,     CONFLICTED, INTEGER,    INTEGER,    INTEGER,    INTEGER,    INTEGER,    INTEGER,    INTEGER,    INTEGER,    INTEGER,    INTEGER,    CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*FLOAT*/      {FLOAT,       CONFLICTED, FLOAT,      FLOAT,      FLOAT,      FLOAT,      FLOAT,      FLOAT,      FLOAT,      FLOAT,      INTEGER,    FLOAT,      CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*LONG_LO*/    {LONG_LO,     CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, LONG_LO,    CONFLICTED, LONG_LO,    CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*LONG_HI*/    {LONG_HI,     CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, LONG_HI,    CONFLICTED, LONG_HI,    CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*DOUBLE_LO*/  {DOUBLE_LO,   CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, LONG_LO,    CONFLICTED, DOUBLE_LO,  CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*DOUBLE_HI*/  {DOUBLE_HI,   CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, LONG_HI,    CONFLICTED, DOUBLE_HI,  CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*UNINIT_REF*/ {UNINIT_REF,  CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED},
            /*UNINIT_THIS*/{UNINIT_THIS, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, UNINIT_THIS,CONFLICTED, CONFLICTED},
            /*REFERENCE*/  {REFERENCE,   CONFLICTED, REFERENCE,  CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, REFERENCE,  CONFLICTED},
            /*CONFLICTED*/ {CONFLICTED,  CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED, CONFLICTED}
    };


    public static final RegisterType UNKNOWN_TYPE = new RegisterType(UNKNOWN, null);
    public static final RegisterType UNINIT_TYPE = new RegisterType(UNINIT, null);
    public static final RegisterType NULL_TYPE = new RegisterType(NULL, null);
    public static final RegisterType ONE_TYPE = new RegisterType(ONE, null);
    public static final RegisterType BOOLEAN_TYPE = new RegisterType(BOOLEAN, null);
    public static final RegisterType BYTE_TYPE = new RegisterType(BYTE, null);
    public static final RegisterType POS_BYTE_TYPE = new RegisterType(POS_BYTE, null);
    public static final RegisterType SHORT_TYPE = new RegisterType(SHORT, null);
    public static final RegisterType POS_SHORT_TYPE = new RegisterType(POS_SHORT, null);
    public static final RegisterType CHAR_TYPE = new RegisterType(CHAR, null);
    public static final RegisterType INTEGER_TYPE = new RegisterType(INTEGER, null);
    public static final RegisterType FLOAT_TYPE = new RegisterType(FLOAT, null);
    public static final RegisterType LONG_LO_TYPE = new RegisterType(LONG_LO, null);
    public static final RegisterType LONG_HI_TYPE = new RegisterType(LONG_HI, null);
    public static final RegisterType DOUBLE_LO_TYPE = new RegisterType(DOUBLE_LO, null);
    public static final RegisterType DOUBLE_HI_TYPE = new RegisterType(DOUBLE_HI, null);
    public static final RegisterType CONFLICTED_TYPE = new RegisterType(CONFLICTED, null);

    @Nonnull
    public static RegisterType getWideRegisterType(@Nonnull CharSequence type, boolean firstRegister) {
        switch (type.charAt(0)) {
            case 'J':
                if (firstRegister) {
                    return getRegisterType(LONG_LO, null);
                } else {
                    return getRegisterType(LONG_HI, null);
                }
            case 'D':
                if (firstRegister) {
                    return getRegisterType(DOUBLE_LO, null);
                } else {
                    return getRegisterType(DOUBLE_HI, null);
                }
            default:
                throw new ExceptionWithContext("Cannot use this method for narrow register type: %s", type);
        }
    }

    @Nonnull
    public static RegisterType getRegisterType(@Nonnull ClassPath classPath, @Nonnull CharSequence type) {
        switch (type.charAt(0)) {
            case 'Z':
                return BOOLEAN_TYPE;
            case 'B':
                return BYTE_TYPE;
            case 'S':
                return SHORT_TYPE;
            case 'C':
                return CHAR_TYPE;
            case 'I':
                return INTEGER_TYPE;
            case 'F':
                return FLOAT_TYPE;
            case 'J':
                return LONG_LO_TYPE;
            case 'D':
                return DOUBLE_LO_TYPE;
            case 'L':
            case '[':
                return getRegisterType(REFERENCE, classPath.getClass(type));
            default:
                throw new AnalysisException("Invalid type: " + type);
        }
    }

    @Nonnull
    public static RegisterType getRegisterTypeForLiteral(int literalValue) {
        if (literalValue < -32768) {
            return INTEGER_TYPE;
        }
        if (literalValue < -128) {
            return SHORT_TYPE;
        }
        if (literalValue < 0) {
            return BYTE_TYPE;
        }
        if (literalValue == 0) {
            return NULL_TYPE;
        }
        if (literalValue == 1) {
            return ONE_TYPE;
        }
        if (literalValue < 128) {
            return POS_BYTE_TYPE;
        }
        if (literalValue < 32768) {
            return POS_SHORT_TYPE;
        }
        if (literalValue < 65536) {
            return CHAR_TYPE;
        }
        return INTEGER_TYPE;
    }

    @Nonnull
    public RegisterType merge(@Nonnull RegisterType other) {
        if (other.equals(this)) {
            return this;
        }

        byte mergedCategory = mergeTable[this.category][other.category];

        TypeProto mergedType = null;
        if (mergedCategory == REFERENCE) {
            TypeProto type = this.type;
            if (type != null) {
                if (other.type != null) {
                    mergedType = type.getCommonSuperclass(other.type);
                } else {
                    mergedType = type;
                }
            } else {
                mergedType = other.type;
            }
        } else if (mergedCategory == UNINIT_REF || mergedCategory == UNINIT_THIS) {
            if (this.category == UNKNOWN) {
                return other;
            }
            assert other.category == UNKNOWN;
            return this;
        }

        if (mergedType != null) {
            if (mergedType.equals(this.type)) {
                return this;
            }
            if (mergedType.equals(other.type)) {
                return other;
            }
        }
        return RegisterType.getRegisterType(mergedCategory, mergedType);
    }

    @Nonnull
    public static RegisterType getRegisterType(byte category, @Nullable TypeProto typeProto) {
        switch (category) {
            case UNKNOWN:
                return UNKNOWN_TYPE;
            case UNINIT:
                return UNINIT_TYPE;
            case NULL:
                return NULL_TYPE;
            case ONE:
                return ONE_TYPE;
            case BOOLEAN:
                return BOOLEAN_TYPE;
            case BYTE:
                return BYTE_TYPE;
            case POS_BYTE:
                return POS_BYTE_TYPE;
            case SHORT:
                return SHORT_TYPE;
            case POS_SHORT:
                return POS_SHORT_TYPE;
            case CHAR:
                return CHAR_TYPE;
            case INTEGER:
                return INTEGER_TYPE;
            case FLOAT:
                return FLOAT_TYPE;
            case LONG_LO:
                return LONG_LO_TYPE;
            case LONG_HI:
                return LONG_HI_TYPE;
            case DOUBLE_LO:
                return DOUBLE_LO_TYPE;
            case DOUBLE_HI:
                return DOUBLE_HI_TYPE;
            case CONFLICTED:
                return CONFLICTED_TYPE;
        }

        return new RegisterType(category, typeProto);
    }
}