/*
 * 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.dexgen.rop.code;

import com.android.dexgen.util.Bits;
import com.android.dexgen.util.Hex;
import com.android.dexgen.util.IntList;

/**
 * All of the parts that make up a method at the rop layer.
 */
public final class RopMethod {
    /** {@code non-null;} basic block list of the method */
    private final BasicBlockList blocks;

    /** {@code >= 0;} label for the block which starts the method */
    private final int firstLabel;

    /**
     * {@code null-ok;} array of predecessors for each block, indexed by block
     * label
     */
    private IntList[] predecessors;

    /**
     * {@code null-ok;} the predecessors for the implicit "exit" block, that is
     * the labels for the blocks that return, if calculated
     */
    private IntList exitPredecessors;

    /**
     * Constructs an instance.
     *
     * @param blocks {@code non-null;} basic block list of the method
     * @param firstLabel {@code >= 0;} the label of the first block to execute
     */
    public RopMethod(BasicBlockList blocks, int firstLabel) {
        if (blocks == null) {
            throw new NullPointerException("blocks == null");
        }

        if (firstLabel < 0) {
            throw new IllegalArgumentException("firstLabel < 0");
        }

        this.blocks = blocks;
        this.firstLabel = firstLabel;

        this.predecessors = null;
        this.exitPredecessors = null;
    }

    /**
     * Gets the basic block list for this method.
     *
     * @return {@code non-null;} the list
     */
    public BasicBlockList getBlocks() {
        return blocks;
    }

    /**
     * Gets the label for the first block in the method that this list
     * represents.
     *
     * @return {@code >= 0;} the first-block label
     */
    public int getFirstLabel() {
        return firstLabel;
    }

    /**
     * Gets the predecessors associated with the given block. This throws
     * an exception if there is no block with the given label.
     *
     * @param label {@code >= 0;} the label of the block in question
     * @return {@code non-null;} the predecessors of that block
     */
    public IntList labelToPredecessors(int label) {
        if (exitPredecessors == null) {
            calcPredecessors();
        }

        IntList result = predecessors[label];

        if (result == null) {
            throw new RuntimeException("no such block: " + Hex.u2(label));
        }

        return result;
    }

    /**
     * Gets the exit predecessors for this instance.
     *
     * @return {@code non-null;} the exit predecessors
     */
    public IntList getExitPredecessors() {
        if (exitPredecessors == null) {
            calcPredecessors();
        }

        return exitPredecessors;
    }


    /**
     * Returns an instance that is identical to this one, except that
     * the registers in each instruction are offset by the given
     * amount.
     *
     * @param delta the amount to offset register numbers by
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public RopMethod withRegisterOffset(int delta) {
        RopMethod result = new RopMethod(blocks.withRegisterOffset(delta),
                                         firstLabel);

        if (exitPredecessors != null) {
            /*
             * The predecessors have been calculated. It's safe to
             * inject these into the new instance, since the
             * transformation being applied doesn't affect the
             * predecessors.
             */
            result.exitPredecessors = exitPredecessors;
            result.predecessors = predecessors;
        }

        return result;
    }

    /**
     * Calculates the predecessor sets for each block as well as for the
     * exit.
     */
    private void calcPredecessors() {
        int maxLabel = blocks.getMaxLabel();
        IntList[] predecessors = new IntList[maxLabel];
        IntList exitPredecessors = new IntList(10);
        int sz = blocks.size();

        /*
         * For each block, find its successors, and add the block's label to
         * the successor's predecessors.
         */
        for (int i = 0; i < sz; i++) {
            BasicBlock one = blocks.get(i);
            int label = one.getLabel();
            IntList successors = one.getSuccessors();
            int ssz = successors.size();
            if (ssz == 0) {
                // This block exits.
                exitPredecessors.add(label);
            } else {
                for (int j = 0; j < ssz; j++) {
                    int succLabel = successors.get(j);
                    IntList succPreds = predecessors[succLabel];
                    if (succPreds == null) {
                        succPreds = new IntList(10);
                        predecessors[succLabel] = succPreds;
                    }
                    succPreds.add(label);
                }
            }
        }

        // Sort and immutablize all the predecessor lists.
        for (int i = 0; i < maxLabel; i++) {
            IntList preds = predecessors[i];
            if (preds != null) {
                preds.sort();
                preds.setImmutable();
            }
        }

        exitPredecessors.sort();
        exitPredecessors.setImmutable();

        /*
         * The start label might not ever have had any predecessors
         * added to it (probably doesn't, because of how Java gets
         * translated into rop form). So, check for this and rectify
         * the situation if required.
         */
        if (predecessors[firstLabel] == null) {
            predecessors[firstLabel] = IntList.EMPTY;
        }

        this.predecessors = predecessors;
        this.exitPredecessors = exitPredecessors;
    }
}
