/*
 * [The "BSD licence"]
 * Copyright (c) 2010 Ben Gruver
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.baksmali.Adaptors;

import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.analysis.AnalyzedInstruction;
import org.jf.dexlib2.analysis.MethodAnalyzer;
import org.jf.dexlib2.analysis.RegisterType;
import org.jf.dexlib2.iface.instruction.*;
import org.jf.util.IndentingWriter;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.BitSet;

public class PreInstructionRegisterInfoMethodItem extends MethodItem {
    private final int registerInfo;
    @Nonnull private final MethodAnalyzer methodAnalyzer;
    @Nonnull private final RegisterFormatter registerFormatter;
    @Nonnull private final AnalyzedInstruction analyzedInstruction;

    public PreInstructionRegisterInfoMethodItem(int registerInfo,
                                                @Nonnull MethodAnalyzer methodAnalyzer,
                                                @Nonnull RegisterFormatter registerFormatter,
                                                @Nonnull AnalyzedInstruction analyzedInstruction,
                                                int codeAddress) {
        super(codeAddress);
        this.registerInfo = registerInfo;
        this.methodAnalyzer = methodAnalyzer;
        this.registerFormatter = registerFormatter;
        this.analyzedInstruction = analyzedInstruction;
    }

    @Override
    public double getSortOrder() {
        return 99.9;
    }

    @Override
    public boolean writeTo(IndentingWriter writer) throws IOException {
        int registerCount = analyzedInstruction.getRegisterCount();
        BitSet registers = new BitSet(registerCount);
        BitSet mergeRegisters = null;

        if ((registerInfo & BaksmaliOptions.ALL) != 0) {
            registers.set(0, registerCount);
        } else {
            if ((registerInfo & BaksmaliOptions.ALLPRE) != 0) {
                registers.set(0, registerCount);
            } else {
                if ((registerInfo & BaksmaliOptions.ARGS) != 0) {
                    addArgsRegs(registers);
                }
                if ((registerInfo & BaksmaliOptions.MERGE) != 0) {
                    if (analyzedInstruction.isBeginningInstruction()) {
                        addParamRegs(registers, registerCount);
                    }
                    mergeRegisters = new BitSet(registerCount);
                    addMergeRegs(mergeRegisters, registerCount);
                } else if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0 &&
                        (analyzedInstruction.isBeginningInstruction())) {
                    addParamRegs(registers, registerCount);
                }
            }
        }

        if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
            if (mergeRegisters == null) {
                mergeRegisters = new BitSet(registerCount);
                addMergeRegs(mergeRegisters, registerCount);
            }
            registers.or(mergeRegisters);
        } else if (mergeRegisters != null) {
            registers.or(mergeRegisters);
            mergeRegisters = null;
        }

        return writeRegisterInfo(writer, registers, mergeRegisters);
    }

    private void addArgsRegs(BitSet registers) {
        if (analyzedInstruction.getInstruction() instanceof RegisterRangeInstruction) {
            RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.getInstruction();

            registers.set(instruction.getStartRegister(),
                    instruction.getStartRegister() + instruction.getRegisterCount());
        } else if (analyzedInstruction.getInstruction() instanceof FiveRegisterInstruction) {
            FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.getInstruction();
            int regCount = instruction.getRegisterCount();
            switch (regCount) {
                case 5:
                    registers.set(instruction.getRegisterG());
                    //fall through
                case 4:
                    registers.set(instruction.getRegisterF());
                    //fall through
                case 3:
                    registers.set(instruction.getRegisterE());
                    //fall through
                case 2:
                    registers.set(instruction.getRegisterD());
                    //fall through
                case 1:
                    registers.set(instruction.getRegisterC());
            }
        } else if (analyzedInstruction.getInstruction() instanceof ThreeRegisterInstruction) {
            ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.getInstruction();
            registers.set(instruction.getRegisterA());
            registers.set(instruction.getRegisterB());
            registers.set(instruction.getRegisterC());
        } else if (analyzedInstruction.getInstruction() instanceof TwoRegisterInstruction) {
            TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.getInstruction();
            registers.set(instruction.getRegisterA());
            registers.set(instruction.getRegisterB());
        } else if (analyzedInstruction.getInstruction() instanceof OneRegisterInstruction) {
            OneRegisterInstruction instruction = (OneRegisterInstruction)analyzedInstruction.getInstruction();
            registers.set(instruction.getRegisterA());
        }
    }

    private void addMergeRegs(BitSet registers, int registerCount) {
        if (analyzedInstruction.getPredecessorCount() <= 1) {
            //in the common case of an instruction that only has a single predecessor which is the previous
            //instruction, the pre-instruction registers will always match the previous instruction's
            //post-instruction registers
            return;
        }

        for (int registerNum=0; registerNum<registerCount; registerNum++) {
            RegisterType mergedRegisterType = analyzedInstruction.getPreInstructionRegisterType(registerNum);

            for (AnalyzedInstruction predecessor: analyzedInstruction.getPredecessors()) {
                RegisterType predecessorRegisterType = analyzedInstruction.getPredecessorRegisterType(
                        predecessor, registerNum);
                if (predecessorRegisterType.category != RegisterType.UNKNOWN &&
                        !predecessorRegisterType.equals(mergedRegisterType)) {
                    registers.set(registerNum);
                }
            }
        }
    }

    private void addParamRegs(BitSet registers, int registerCount) {
        int parameterRegisterCount = methodAnalyzer.getParamRegisterCount();
        registers.set(registerCount-parameterRegisterCount, registerCount);
    }

    private void writeFullMerge(IndentingWriter writer, int registerNum) throws IOException {
        registerFormatter.writeTo(writer, registerNum);
        writer.write('=');
        analyzedInstruction.getPreInstructionRegisterType(registerNum).writeTo(writer);
        writer.write(":merge{");

        boolean first = true;

        for (AnalyzedInstruction predecessor: analyzedInstruction.getPredecessors()) {
            RegisterType predecessorRegisterType = analyzedInstruction.getPredecessorRegisterType(
                    predecessor, registerNum);

            if (!first) {
                writer.write(',');
            }

            if (predecessor.getInstructionIndex() == -1) {
                //the fake "StartOfMethod" instruction
                writer.write("Start:");
            } else {
                writer.write("0x");
                writer.printUnsignedLongAsHex(methodAnalyzer.getInstructionAddress(predecessor));
                writer.write(':');
            }
            predecessorRegisterType.writeTo(writer);

            first = false;
        }
        writer.write('}');
    }

    private boolean writeRegisterInfo(IndentingWriter writer, BitSet registers,
                                      BitSet fullMergeRegisters) throws IOException {
        boolean firstRegister = true;
        boolean previousWasFullMerge = false;
        int registerNum = registers.nextSetBit(0);
        if (registerNum < 0) {
            return false;
        }

        writer.write('#');
        for (; registerNum >= 0; registerNum = registers.nextSetBit(registerNum + 1)) {
            boolean fullMerge = fullMergeRegisters!=null && fullMergeRegisters.get(registerNum);
            if (fullMerge) {
                if (!firstRegister) {
                    writer.write('\n');
                    writer.write('#');
                }
                writeFullMerge(writer, registerNum);
                previousWasFullMerge = true;
            } else {
                if (previousWasFullMerge) {
                    writer.write('\n');
                    writer.write('#');
                    previousWasFullMerge = false;
                }

                RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(registerNum);

                registerFormatter.writeTo(writer, registerNum);
                writer.write('=');

                registerType.writeTo(writer);
                writer.write(';');
            }

            firstRegister = false;
        }
        return true;
    }
}
