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

import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.Hex;
import java.util.List;

/**
 * Class that represents a contiguous list of uniform items. Each
 * item in the list, in particular, must have the same write size and
 * alignment.
 *
 * <p>This class inherits its alignment from its items, bumped up to
 * {@code 4} if the items have a looser alignment requirement. If
 * it is more than {@code 4}, then there will be a gap after the
 * output list size (which is four bytes) and before the first item.</p>
 *
 * @param <T> type of element contained in an instance
 */
public final class UniformListItem<T extends OffsettedItem>
        extends OffsettedItem {
    /** the size of the list header */
    private static final int HEADER_SIZE = 4;

    /** {@code non-null;} the item type */
    private final ItemType itemType;

    /** {@code non-null;} the contents */
    private final List<T> items;

    /**
     * Constructs an instance. It is illegal to modify the given list once
     * it is used to construct an instance of this class.
     *
     * @param itemType {@code non-null;} the type of the item
     * @param items {@code non-null and non-empty;} list of items to represent
     */
    public UniformListItem(ItemType itemType, List<T> items) {
        super(getAlignment(items), writeSize(items));

        if (itemType == null) {
            throw new NullPointerException("itemType == null");
        }

        this.items = items;
        this.itemType = itemType;
    }

    /**
     * Helper for {@link #UniformListItem}, which returns the alignment
     * requirement implied by the given list. See the header comment for
     * more details.
     *
     * @param items {@code non-null;} list of items being represented
     * @return {@code >= 4;} the alignment requirement
     */
    private static int getAlignment(List<? extends OffsettedItem> items) {
        try {
            // Since they all must have the same alignment, any one will do.
            return Math.max(HEADER_SIZE, items.get(0).getAlignment());
        } catch (IndexOutOfBoundsException ex) {
            // Translate the exception.
            throw new IllegalArgumentException("items.size() == 0");
        } catch (NullPointerException ex) {
            // Translate the exception.
            throw new NullPointerException("items == null");
        }
    }

    /**
     * Calculates the write size for the given list.
     *
     * @param items {@code non-null;} the list in question
     * @return {@code >= 0;} the write size
     */
    private static int writeSize(List<? extends OffsettedItem> items) {
        /*
         * This class assumes all included items are the same size,
         * an assumption which is verified in place0().
         */
        OffsettedItem first = items.get(0);
        return (items.size() * first.writeSize()) + getAlignment(items);
    }

    /** {@inheritDoc} */
    @Override
    public ItemType itemType() {
        return itemType;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(100);

        sb.append(getClass().getName());
        sb.append(items);

        return sb.toString();
    }

    /** {@inheritDoc} */
    @Override
    public void addContents(DexFile file) {
        for (OffsettedItem i : items) {
            i.addContents(file);
        }
    }

    /** {@inheritDoc} */
    @Override
    public final String toHuman() {
        StringBuffer sb = new StringBuffer(100);
        boolean first = true;

        sb.append("{");

        for (OffsettedItem i : items) {
            if (first) {
                first = false;
            } else {
                sb.append(", ");
            }
            sb.append(i.toHuman());
        }

        sb.append("}");
        return sb.toString();
    }

    /**
     * Gets the underlying list of items.
     *
     * @return {@code non-null;} the list
     */
    public final List<T> getItems() {
        return items;
    }

    /** {@inheritDoc} */
    @Override
    protected void place0(Section addedTo, int offset) {
        offset += headerSize();

        boolean first = true;
        int theSize = -1;
        int theAlignment = -1;

        for (OffsettedItem i : items) {
            int size = i.writeSize();
            if (first) {
                theSize = size;
                theAlignment = i.getAlignment();
                first = false;
            } else {
                if (size != theSize) {
                    throw new UnsupportedOperationException(
                            "item size mismatch");
                }
                if (i.getAlignment() != theAlignment) {
                    throw new UnsupportedOperationException(
                            "item alignment mismatch");
                }
            }

            offset = i.place(addedTo, offset) + size;
        }
    }

    /** {@inheritDoc} */
    @Override
    protected void writeTo0(DexFile file, AnnotatedOutput out) {
        int size = items.size();

        if (out.annotates()) {
            out.annotate(0, offsetString() + " " + typeName());
            out.annotate(4, "  size: " + Hex.u4(size));
        }

        out.writeInt(size);

        for (OffsettedItem i : items) {
            i.writeTo(file, out);
        }
    }

    /**
     * Get the size of the header of this list.
     *
     * @return {@code >= 0;} the header size
     */
    private int headerSize() {
        /*
         * Because of how this instance was set up, this is the same
         * as the alignment.
         */
        return getAlignment();
    }
}
