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

import com.android.dx.util.IntList;
import com.android.dx.util.MutabilityControl;

/**
 * List of (value, target) mappings representing the choices of a
 * {@code tableswitch} or {@code lookupswitch} instruction. It
 * also holds the default target for the switch.
 */
public final class SwitchList extends MutabilityControl {
    /** {@code non-null;} list of test values */
    private final IntList values;

    /**
     * {@code non-null;} list of targets corresponding to the test values; there
     * is always one extra element in the target list, to hold the
     * default target
     */
    private final IntList targets;

    /** ultimate size of the list */
    private int size;

    /**
     * Constructs an instance.
     *
     * @param size {@code >= 0;} the number of elements to be in the table
     */
    public SwitchList(int size) {
        super(true);
        this.values = new IntList(size);
        this.targets = new IntList(size + 1);
        this.size = size;
    }

    /** {@inheritDoc} */
    @Override
    public void setImmutable() {
        values.setImmutable();
        targets.setImmutable();
        super.setImmutable();
    }

    /**
     * Gets the size of the list.
     *
     * @return {@code >= 0;} the list size
     */
    public int size() {
        return size;
    }

    /**
     * Gets the indicated test value.
     *
     * @param n {@code >= 0;}, &lt; size(); which index
     * @return the test value
     */
    public int getValue(int n) {
        return values.get(n);
    }

    /**
     * Gets the indicated target. Asking for the target at {@code size()}
     * returns the default target.
     *
     * @param n {@code >= 0, <= size();} which index
     * @return {@code >= 0;} the target
     */
    public int getTarget(int n) {
        return targets.get(n);
    }

    /**
     * Gets the default target. This is just a shorthand for
     * {@code getTarget(size())}.
     *
     * @return {@code >= 0;} the default target
     */
    public int getDefaultTarget() {
        return targets.get(size);
    }

    /**
     * Gets the list of all targets. This includes one extra element at the
     * end of the list, which holds the default target.
     *
     * @return {@code non-null;} the target list
     */
    public IntList getTargets() {
        return targets;
    }

    /**
     * Gets the list of all case values.
     *
     * @return {@code non-null;} the case value list
     */
    public IntList getValues() {
        return values;
    }

    /**
     * Sets the default target. It is only valid to call this method
     * when all the non-default elements have been set.
     *
     * @param target {@code >= 0;} the absolute (not relative) default target
     * address
     */
    public void setDefaultTarget(int target) {
        throwIfImmutable();

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

        if (targets.size() != size) {
            throw new RuntimeException("non-default elements not all set");
        }

        targets.add(target);
    }

    /**
     * Adds the given item.
     *
     * @param value the test value
     * @param target {@code >= 0;} the absolute (not relative) target address
     */
    public void add(int value, int target) {
        throwIfImmutable();

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

        values.add(value);
        targets.add(target);
    }

    /**
     * Shrinks this instance if possible, removing test elements that
     * refer to the default target. This is only valid after the instance
     * is fully populated, including the default target (naturally).
     */
    public void removeSuperfluousDefaults() {
        throwIfImmutable();

        int sz = size;

        if (sz != (targets.size() - 1)) {
            throw new IllegalArgumentException("incomplete instance");
        }

        int defaultTarget = targets.get(sz);
        int at = 0;

        for (int i = 0; i < sz; i++) {
            int target = targets.get(i);
            if (target != defaultTarget) {
                if (i != at) {
                    targets.set(at, target);
                    values.set(at, values.get(i));
                }
                at++;
            }
        }

        if (at != sz) {
            values.shrink(at);
            targets.set(at, defaultTarget);
            targets.shrink(at + 1);
            size = at;
        }
    }
}
