/*
 * Copyright (C) 2008 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.dex.file;

import com.android.dexgen.util.AnnotatedOutput;
import com.android.dexgen.util.Hex;

import java.util.ArrayList;

/**
 * Class that represents a map item.
 */
public final class MapItem extends OffsettedItem {
    /** file alignment of this class, in bytes */
    private static final int ALIGNMENT = 4;

    /** write size of this class, in bytes: three {@code uint}s */
    private static final int WRITE_SIZE = (4 * 3);

    /** {@code non-null;} item type this instance covers */
    private final ItemType type;

    /** {@code non-null;} section this instance covers */
    private final Section section;

    /**
     * {@code null-ok;} first item covered or {@code null} if this is
     * a self-reference
     */
    private final Item firstItem;

    /**
     * {@code null-ok;} last item covered or {@code null} if this is
     * a self-reference
     */
    private final Item lastItem;

    /**
     * {@code > 0;} count of items covered; {@code 1} if this
     * is a self-reference
     */
    private final int itemCount;

    /**
     * Constructs a list item with instances of this class representing
     * the contents of the given array of sections, adding it to the
     * given map section.
     *
     * @param sections {@code non-null;} the sections
     * @param mapSection {@code non-null;} the section that the resulting map
     * should be added to; it should be empty on entry to this method
     */
    public static void addMap(Section[] sections,
            MixedItemSection mapSection) {
        if (sections == null) {
            throw new NullPointerException("sections == null");
        }

        if (mapSection.items().size() != 0) {
            throw new IllegalArgumentException(
                    "mapSection.items().size() != 0");
        }

        ArrayList<MapItem> items = new ArrayList<MapItem>(50);

        for (Section section : sections) {
            ItemType currentType = null;
            Item firstItem = null;
            Item lastItem = null;
            int count = 0;

            for (Item item : section.items()) {
                ItemType type = item.itemType();
                if (type != currentType) {
                    if (count != 0) {
                        items.add(new MapItem(currentType, section,
                                        firstItem, lastItem, count));
                    }
                    currentType = type;
                    firstItem = item;
                    count = 0;
                }
                lastItem = item;
                count++;
            }

            if (count != 0) {
                // Add a MapItem for the final items in the section.
                items.add(new MapItem(currentType, section,
                                firstItem, lastItem, count));
            } else if (section == mapSection) {
                // Add a MapItem for the self-referential section.
                items.add(new MapItem(mapSection));
            }
        }

        mapSection.add(
                new UniformListItem<MapItem>(ItemType.TYPE_MAP_LIST, items));
    }

    /**
     * Constructs an instance.
     *
     * @param type {@code non-null;} item type this instance covers
     * @param section {@code non-null;} section this instance covers
     * @param firstItem {@code non-null;} first item covered
     * @param lastItem {@code non-null;} last item covered
     * @param itemCount {@code > 0;} count of items covered
     */
    private MapItem(ItemType type, Section section, Item firstItem,
            Item lastItem, int itemCount) {
        super(ALIGNMENT, WRITE_SIZE);

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

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

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

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

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

        this.type = type;
        this.section = section;
        this.firstItem = firstItem;
        this.lastItem = lastItem;
        this.itemCount = itemCount;
    }

    /**
     * Constructs a self-referential instance. This instance is meant to
     * represent the section containing the {@code map_list}.
     *
     * @param section {@code non-null;} section this instance covers
     */
    private MapItem(Section section) {
        super(ALIGNMENT, WRITE_SIZE);

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

        this.type = ItemType.TYPE_MAP_LIST;
        this.section = section;
        this.firstItem = null;
        this.lastItem = null;
        this.itemCount = 1;
    }

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

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

        sb.append(getClass().getName());
        sb.append('{');
        sb.append(section.toString());
        sb.append(' ');
        sb.append(type.toHuman());
        sb.append('}');

        return sb.toString();
    }

    /** {@inheritDoc} */
    @Override
    public void addContents(DexFile file) {
        // We have nothing to add.
    }

    /** {@inheritDoc} */
    @Override
    public final String toHuman() {
        return toString();
    }

    /** {@inheritDoc} */
    @Override
    protected void writeTo0(DexFile file, AnnotatedOutput out) {
        int value = type.getMapValue();
        int offset;

        if (firstItem == null) {
            offset = section.getFileOffset();
        } else {
            offset = section.getAbsoluteItemOffset(firstItem);
        }

        if (out.annotates()) {
            out.annotate(0, offsetString() + ' ' + type.getTypeName() +
                    " map");
            out.annotate(2, "  type:   " + Hex.u2(value) + " // " +
                    type.toString());
            out.annotate(2, "  unused: 0");
            out.annotate(4, "  size:   " + Hex.u4(itemCount));
            out.annotate(4, "  offset: " + Hex.u4(offset));
        }

        out.writeShort(value);
        out.writeShort(0); // unused
        out.writeInt(itemCount);
        out.writeInt(offset);
    }
}
