/*
 * Copyright 2012, 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.util;

import org.jf.dexlib2.iface.instruction.Instruction;
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction;
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction;
import org.jf.dexlib2.Opcodes;

import java.util.List;

public class SyntheticAccessorFSM {
    %% machine SyntheticAccessorFSM;
    %% write data;

    // math type constants
    public static final int ADD = SyntheticAccessorResolver.ADD_ASSIGNMENT;
    public static final int SUB = SyntheticAccessorResolver.SUB_ASSIGNMENT;
    public static final int MUL = SyntheticAccessorResolver.MUL_ASSIGNMENT;
    public static final int DIV = SyntheticAccessorResolver.DIV_ASSIGNMENT;
    public static final int REM = SyntheticAccessorResolver.REM_ASSIGNMENT;
    public static final int AND = SyntheticAccessorResolver.AND_ASSIGNMENT;
    public static final int OR = SyntheticAccessorResolver.OR_ASSIGNMENT;
    public static final int XOR = SyntheticAccessorResolver.XOR_ASSIGNMENT;
    public static final int SHL = SyntheticAccessorResolver.SHL_ASSIGNMENT;
    public static final int SHR = SyntheticAccessorResolver.SHR_ASSIGNMENT;
    public static final int USHR = SyntheticAccessorResolver.USHR_ASSIGNMENT;

    public static final int INT = 0;
    public static final int LONG = 1;
    public static final int FLOAT = 2;
    public static final int DOUBLE = 3;

    public static final int POSITIVE_ONE = 1;
    public static final int NEGATIVE_ONE = -1;
    public static final int OTHER = 0;

    @Nonnull private final Opcodes opcodes;

    public SyntheticAccessorFSM(@Nonnull Opcodes opcodes) {
        this.opcodes = opcodes;
    }

    public int test(List<? extends Instruction> instructions) {
        int accessorType = -1;
        int cs, p = 0;
        int pe = instructions.size();

        // one of the math type constants representing the type of math operation being performed
        int mathOp = -1;

        // for increments an decrements, the type of value the math operation is on
        int mathType = -1;

        // for increments and decrements, the value of the constant that is used
        long constantValue = 0;

        // The source register for the put instruction
        int putRegister = -1;
        // The return register;
        int returnRegister = -1;

        %%{
            import "Opcodes.rl";
            alphtype short;
            getkey opcodes.getOpcodeValue(instructions.get(p).getOpcode());

            get = (0x52 .. 0x58) | (0x60 .. 0x66); # all igets/sgets

            # all iputs/sputs
            put = ((0x59 .. 0x5f) | (0x67 .. 0x6d)) @ {
                putRegister = ((OneRegisterInstruction)instructions.get(p)).getRegisterA();
            };

            invoke = (0x6e .. 0x72) | (0x74 .. 0x78); # all invokes

            # all numeric const instructions
            const_literal = (0x12 .. 0x19) @ {
                constantValue = ((WideLiteralInstruction)instructions.get(p)).getWideLiteral();
            };

            add_const = (add_int_lit8 | add_int_lit16) @ {
                mathType = INT;
                mathOp = ADD;
                constantValue = ((WideLiteralInstruction)instructions.get(p)).getWideLiteral();
            };

            arbitrary_add = (((add_int | add_int_2addr) @ { mathType = INT; }) |
                             ((add_long | add_long_2addr) @ { mathType = LONG; }) |
                             ((add_float | add_float_2addr) @ { mathType = FLOAT; }) |
                             ((add_double | add_double_2addr) @ {mathType = DOUBLE; })) @ {
                mathOp = ADD;
            };
            arbitrary_sub = (((sub_int | sub_int_2addr) @ { mathType = INT; }) |
                             ((sub_long | sub_long_2addr) @ { mathType = LONG; }) |
                             ((sub_float | sub_float_2addr) @ { mathType = FLOAT; }) |
                             ((sub_double | sub_double_2addr) @ {mathType = DOUBLE; })) @ {
                mathOp = SUB;
            };
            arbitrary_mul = (mul_int | mul_int_2addr | mul_long | mul_long_2addr |
                            mul_float | mul_float_2addr | mul_double | mul_double_2addr) @ {
                mathOp = MUL;
            };
            arbitrary_div = (div_int | div_int_2addr | div_long | div_long_2addr |
                            div_float | div_float_2addr | div_double | div_double_2addr) @ {
                mathOp = DIV;
            };
            arbitrary_rem = (rem_int | rem_int_2addr | rem_long | rem_long_2addr |
                            rem_float | rem_float_2addr | rem_double | rem_double_2addr) @ {
                mathOp = REM;
            };
            arbitrary_and = (and_int | and_int_2addr | and_long | and_long_2addr) @ {
                mathOp = AND;
            };
            arbitrary_or = (or_int | or_int_2addr | or_long | or_long_2addr) @ {
                mathOp = OR;
            };
            arbitrary_xor = (xor_int | xor_int_2addr | xor_long | xor_long_2addr) @ {
                mathOp = XOR;
            };
            arbitrary_shl = (shl_int | shl_int_2addr | shl_long | shl_long_2addr) @ {
                mathOp = SHL;
            };
            arbitrary_shr = (shr_int | shr_int_2addr | shr_long | shr_long_2addr) @ {
                mathOp = SHR;
            };
            arbitrary_ushr = (ushr_int | ushr_int_2addr | ushr_long | ushr_long_2addr) @ {
                mathOp = USHR;
            };

            type_conversion = 0x81 .. 0x8f; # all type-conversion opcodes

            return_something = (return | return_wide | return_object) @ {
                returnRegister = ((OneRegisterInstruction)instructions.get(p)).getRegisterA();
            };

            any_move_result = move_result | move_result_wide | move_result_object;

            get_accessor = get return_something @ {
                accessorType = SyntheticAccessorResolver.GETTER; fbreak;
            };

            put_accessor = put return_something @ {
                accessorType = SyntheticAccessorResolver.SETTER; fbreak;
            };

            invoke_accessor = invoke (return_void | (any_move_result return_something)) @ {
                accessorType = SyntheticAccessorResolver.METHOD; fbreak;
            };

            increment_accessor = get add_const type_conversion? put return_something @ {
                accessorType = getIncrementType(mathOp, mathType, constantValue, putRegister, returnRegister);
            };

            alt_increment_accessor = get const_literal (arbitrary_add | arbitrary_sub) put return_something @ {
                accessorType = getIncrementType(mathOp, mathType, constantValue, putRegister, returnRegister);
            };

            math_assignment_accessor = get type_conversion?
                                       (arbitrary_add | arbitrary_sub | arbitrary_mul | arbitrary_div | arbitrary_rem |
                                        arbitrary_and | arbitrary_or | arbitrary_xor | arbitrary_shl | arbitrary_shr |
                                        arbitrary_ushr)
                                        type_conversion{0,2} put return_something @ {
                accessorType = mathOp; fbreak;
            };

            main := get_accessor |
                    put_accessor |
                    invoke_accessor |
                    increment_accessor |
                    alt_increment_accessor |
                    math_assignment_accessor;

            write init;
            write exec;
        }%%

        return accessorType;
    }

    private static int getIncrementType(int mathOp, int mathType, long constantValue, int putRegister,
            int returnRegister) {
        boolean isPrefix = putRegister == returnRegister;

        boolean negativeConstant = false;

        switch (mathType) {
            case INT:
            case LONG: {
                if (constantValue == 1) {
                    negativeConstant = false;
                } else if (constantValue == -1) {
                    negativeConstant = true;
                } else {
                    return -1;
                }
                break;
            }
            case FLOAT: {
                float val = Float.intBitsToFloat((int)constantValue);
                if (val == 1) {
                    negativeConstant = false;
                } else if (val == -1) {
                    negativeConstant = true;
                } else {
                    return -1;
                }
                break;
            }
            case DOUBLE: {
                double val = Double.longBitsToDouble(constantValue);
                if (val == 1) {
                    negativeConstant = false;
                } else if (val == -1) {
                    negativeConstant = true;
                } else {
                    return -1;
                }
                break;
            }
        }

        boolean isAdd = ((mathOp == ADD) && !negativeConstant) ||
                        ((mathOp == SUB) && negativeConstant);

        if (isPrefix) {
            if (isAdd) {
                return SyntheticAccessorResolver.PREFIX_INCREMENT;
            } else {
                return SyntheticAccessorResolver.PREFIX_DECREMENT;
            }
        } else {
            if (isAdd) {
                return SyntheticAccessorResolver.POSTFIX_INCREMENT;
            } else {
                return SyntheticAccessorResolver.POSTFIX_DECREMENT;
            }
        }
    }
}