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

import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.code.SourcePosition;
import com.android.dx.util.AnnotatedOutput;

/**
 * Combination instruction which turns into a variable number of
 * {@code move*} instructions to move a set of registers into
 * registers starting at {@code 0} sequentially. This is used
 * in translating an instruction whose register requirements cannot
 * be met using a straightforward choice of a single opcode.
 */
public final class HighRegisterPrefix extends VariableSizeInsn {
    /** {@code null-ok;} cached instructions, if constructed */
    private SimpleInsn[] insns;

    /**
     * Constructs an instance. The output address of this instance is initially
     * unknown ({@code -1}).
     *
     * @param position {@code non-null;} source position
     * @param registers {@code non-null;} source registers
     */
    public HighRegisterPrefix(SourcePosition position,
                              RegisterSpecList registers) {
        super(position, registers);

        if (registers.size() == 0) {
            throw new IllegalArgumentException("registers.size() == 0");
        }

        insns = null;
    }

    /** {@inheritDoc} */
    @Override
    public int codeSize() {
        int result = 0;

        calculateInsnsIfNecessary();

        for (SimpleInsn insn : insns) {
            result += insn.codeSize();
        }

        return result;
    }

    /** {@inheritDoc} */
    @Override
    public void writeTo(AnnotatedOutput out) {
        calculateInsnsIfNecessary();

        for (SimpleInsn insn : insns) {
            insn.writeTo(out);
        }
    }

    /**
     * Helper for {@link #codeSize} and {@link #writeTo} which sets up
     * {@link #insns} if not already done.
     */
    private void calculateInsnsIfNecessary() {
        if (insns != null) {
            return;
        }

        RegisterSpecList registers = getRegisters();
        int sz = registers.size();

        insns = new SimpleInsn[sz];

        for (int i = 0, outAt = 0; i < sz; i++) {
            RegisterSpec src = registers.get(i);
            insns[i] = moveInsnFor(src, outAt);
            outAt += src.getCategory();
        }
    }

    /** {@inheritDoc} */
    @Override
    public DalvInsn withRegisters(RegisterSpecList registers) {
        return new HighRegisterPrefix(getPosition(), registers);
    }

    /** {@inheritDoc} */
    @Override
    protected String argString() {
        return null;
    }

    /** {@inheritDoc} */
    @Override
    protected String listingString0(boolean noteIndices) {
        RegisterSpecList registers = getRegisters();
        int sz = registers.size();
        StringBuffer sb = new StringBuffer(100);

        for (int i = 0, outAt = 0; i < sz; i++) {
            RegisterSpec src = registers.get(i);
            SimpleInsn insn = moveInsnFor(src, outAt);

            if (i != 0) {
                sb.append('\n');
            }

            sb.append(insn.listingString0(noteIndices));

            outAt += src.getCategory();
        }

        return sb.toString();
    }

    /**
     * Returns the proper move instruction for the given source spec
     * and destination index.
     *
     * @param src {@code non-null;} the source register spec
     * @param destIndex {@code >= 0;} the destination register index
     * @return {@code non-null;} the appropriate move instruction
     */
    private static SimpleInsn moveInsnFor(RegisterSpec src, int destIndex) {
        return DalvInsn.makeMove(SourcePosition.NO_INFO,
                RegisterSpec.make(destIndex, src.getType()),
                src);
    }
}
