/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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.ide.eclipse.adt.internal.editors.drawable;

import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME;

import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.common.resources.platform.DeclareStyleableInfo;
import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
import com.android.resources.ResourceType;
import com.android.sdklib.SdkConstants;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Descriptors for /res/drawable files
 */
public class DrawableDescriptors implements IDescriptorProvider {
    private static final String SDK_URL_BASE =
        "http://d.android.com/guide/topics/resources/"; //$NON-NLS-1$

    /** The root element descriptor */
    private ElementDescriptor mDescriptor;
    /** The root element descriptors */
    private ElementDescriptor[] mRootDescriptors;
    private Map<String, ElementDescriptor> nameToDescriptor;

    /** @return the root descriptor. */
    public ElementDescriptor getDescriptor() {
        if (mDescriptor == null) {
            mDescriptor = new ElementDescriptor("", getRootElementDescriptors()); //$NON-NLS-1$
        }

        return mDescriptor;
    }

    public ElementDescriptor[] getRootElementDescriptors() {
        return mRootDescriptors;
    }

    /**
     * Returns a descriptor for the given root tag name
     *
     * @param tag the tag name to look up a descriptor for
     * @return a descriptor with the given tag name
     */
    public ElementDescriptor getElementDescriptor(String tag) {
        if (nameToDescriptor == null) {
            nameToDescriptor = new HashMap<String, ElementDescriptor>();
            for (ElementDescriptor descriptor : getRootElementDescriptors()) {
                nameToDescriptor.put(descriptor.getXmlName(), descriptor);
            }
        }

        ElementDescriptor descriptor = nameToDescriptor.get(tag);
        if (descriptor == null) {
            descriptor = getDescriptor();
        }
        return descriptor;
    }

    public synchronized void updateDescriptors(Map<String, DeclareStyleableInfo> styleMap) {
        XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_NAME,
                SdkConstants.NS_RESOURCES);
        Format[] referenceFormat = new Format[] { Format.REFERENCE };

        List<ElementDescriptor> descriptors = new ArrayList<ElementDescriptor>();

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "animation-list", "Animation List", "AnimationDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An animation defined in XML that shows a sequence of images in "
                + "order (like a film)",
            SDK_URL_BASE + "animation-resource.html#Frame",
            xmlns, null, true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "animated-rotate", "Animated Rotate", "AnimatedRotateDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            // Need docs
            null /* tooltip */,
            null /* sdk_url */,
            xmlns, null, true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "bitmap", "BitMap", "BitmapDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML bitmap is a resource defined in XML that points to a bitmap file. "
                    + "The effect is an alias for a raw bitmap file. The XML can "
                    + "specify additional properties for the bitmap such as "
                    + "dithering and tiling.",
            SDK_URL_BASE + "drawable-resource.html#Bitmap", //$NON-NLS-1$
            xmlns, null, true /* mandatory */);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "clip", "Clip", "ClipDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML file that defines a drawable that clips another Drawable based on "
                + "this Drawable's current level value.",
            SDK_URL_BASE + "drawable-resource.html#Clip", //$NON-NLS-1$
            xmlns, null, true /*mandatory*/);


        AnimatorDescriptors.addElement(descriptors, styleMap,
            "color", "Color", "ColorDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "XML resource that carries a color value (a hexadecimal color)",
            SDK_URL_BASE + "more-resources.html#Color",
            xmlns, null, true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "inset", "Inset", "InsetDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML file that defines a drawable that insets another drawable by a "
                + "specified distance. This is useful when a View needs a background "
                + "drawble that is smaller than the View's actual bounds.",
            SDK_URL_BASE + "drawable-resource.html#Inset", //$NON-NLS-1$
            xmlns, null, true /*mandatory*/);

        // Layer list

        // An <item> in a <selector> or <
        ElementDescriptor layerItem = AnimatorDescriptors.addElement(null, styleMap,
                "item", "Item", "LayerDrawableItem", null, //$NON-NLS-1$ //$NON-NLS-3$
                "Defines a drawable to place in the layer drawable, in a position "
                    + "defined by its attributes. Must be a child of a <selector> "
                    + "element. Accepts child <bitmap> elements.",
                SDK_URL_BASE + "drawable-resource.html#LayerList", //$NON-NLS-1$
                null, /* extra attribute */
                null, /* This is wrong -- we can now embed any above drawable
                            (but without xmlns as extra) */
                false /*mandatory*/);
        ElementDescriptor[] layerChildren = layerItem != null
            ? new ElementDescriptor[] { layerItem } : null;

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "layer-list", "Layer List", "LayerDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "A Drawable that manages an array of other Drawables. These are drawn in "
                    + "array order, so the element with the largest index is be drawn on top.",
            SDK_URL_BASE + "drawable-resource.html#LayerList", //$NON-NLS-1$
            xmlns,
            layerChildren,
            true /*mandatory*/);

        // Level list children
        ElementDescriptor levelListItem = AnimatorDescriptors.addElement(null, styleMap,
            "item", "Item", "LevelListDrawableItem", null, //$NON-NLS-1$ //$NON-NLS-3$
            "Defines a drawable to use at a certain level.",
            SDK_URL_BASE + "drawable-resource.html#LevelList", //$NON-NLS-1$
            null, /* extra attribute */
            null, /* no further children */
            // TODO: The inflation code seems to show that all drawables can be nested here!
            false /*mandatory*/);
        AnimatorDescriptors.addElement(descriptors, styleMap,
            "level-list", "Level List", "LevelListDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML file that defines a drawable that manages a number of alternate "
                + "Drawables, each assigned a maximum numerical value",
            SDK_URL_BASE + "drawable-resource.html#LevelList", //$NON-NLS-1$
            xmlns,
            levelListItem != null ? new ElementDescriptor[] { levelListItem } : null,
            true /*mandatory*/);

        // Not yet supported
        //addElement(descriptors, styleMap, "mipmap", "Mipmap", "MipmapDrawable", null,
        //        null /* tooltip */,
        //        null /* sdk_url */,
        //        xmlns, null, true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "nine-patch", "Nine Patch", "NinePatchDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "A PNG file with stretchable regions to allow image resizing "
                    + "based on content (.9.png).",
            SDK_URL_BASE + "drawable-resource.html#NinePatch", //$NON-NLS-1$
            xmlns, null, true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "rotate", "Rotate", "RotateDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            // Need docs
            null /* tooltip */,
            null /* sdk_url */,
            xmlns, null, true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "scale", "Shape", "ScaleDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML file that defines a drawable that changes the size of another Drawable "
                + "based on its current level value.",
            SDK_URL_BASE + "drawable-resource.html#Scale", //$NON-NLS-1$
            xmlns, null, true /*mandatory*/);

        // Selector children
        ElementDescriptor selectorItem = AnimatorDescriptors.addElement(null, styleMap,
            "item", "Item", "DrawableStates", null, //$NON-NLS-1$ //$NON-NLS-3$
            "Defines a drawable to use during certain states, as described by "
                 + "its attributes. Must be a child of a <selector> element.",
            SDK_URL_BASE + "drawable-resource.html#StateList", //$NON-NLS-1$
            new ReferenceAttributeDescriptor(
                    ResourceType.DRAWABLE, "drawable", "drawable", //$NON-NLS-1$ //$NON-NLS-2$
                    SdkConstants.NS_RESOURCES,
                    "Reference to a drawable resource.",
                    new AttributeInfo("drawable", referenceFormat)),
            null, /* This is wrong -- we can now embed any above drawable
                        (but without xmlns as extra) */
            false /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "selector", "Selector", "StateListDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML file that references different bitmap graphics for different states "
                + "(for example, to use a different image when a button is pressed).",
            SDK_URL_BASE + "drawable-resource.html#StateList", //$NON-NLS-1$
            xmlns,
            selectorItem != null ? new ElementDescriptor[] { selectorItem } : null,
            true /*mandatory*/);

        // Shape
        // Shape children
        List<ElementDescriptor> shapeChildren = new ArrayList<ElementDescriptor>();
        // Selector children
        AnimatorDescriptors.addElement(shapeChildren, styleMap,
            "size", "Size", "GradientDrawableSize", null, //$NON-NLS-1$ //$NON-NLS-3$
            null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
            null /* children */, false /* mandatory */);
        AnimatorDescriptors.addElement(shapeChildren, styleMap,
            "gradient", "Gradient", "GradientDrawableGradient", null, //$NON-NLS-1$ //$NON-NLS-3$
            null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
            null /* children */, false /* mandatory */);
        AnimatorDescriptors.addElement(shapeChildren, styleMap,
            "solid", "Solid", "GradientDrawableSolid", null, //$NON-NLS-1$ //$NON-NLS-3$
            null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
            null /* children */, false /* mandatory */);
        AnimatorDescriptors.addElement(shapeChildren, styleMap,
            "stroke", "Stroke", "GradientDrawableStroke", null, //$NON-NLS-1$ //$NON-NLS-3$
            null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
            null /* children */, false /* mandatory */);
        AnimatorDescriptors.addElement(shapeChildren, styleMap,
            "corners", "Corners", "DrawableCorners", null, //$NON-NLS-1$ //$NON-NLS-3$
            null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
            null /* children */, false /* mandatory */);
        AnimatorDescriptors.addElement(shapeChildren, styleMap,
            "padding", "Padding", "GradientDrawablePadding", null, //$NON-NLS-1$ //$NON-NLS-3$
            null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
            null /* children */, false /* mandatory */);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "shape", "Shape", //$NON-NLS-1$

            // The documentation says that a <shape> element creates a ShapeDrawable,
            // but ShapeDrawable isn't finished and the code currently creates
            // a GradientDrawable.
            //"ShapeDrawable", //$NON-NLS-1$
            "GradientDrawable", //$NON-NLS-1$

            null,
            "An XML file that defines a geometric shape, including colors and gradients.",
            SDK_URL_BASE + "drawable-resource.html#Shape", //$NON-NLS-1$
            xmlns,

            // These are the GradientDrawable children, not the ShapeDrawable children
            shapeChildren.toArray(new ElementDescriptor[shapeChildren.size()]),
            true /*mandatory*/);

        AnimatorDescriptors.addElement(descriptors, styleMap,
            "transition", "Transition", "TransitionDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
            "An XML file that defines a drawable that can cross-fade between two "
                + "drawable resources. Each drawable is represented by an <item> "
                + "element inside a single <transition> element. No more than two "
                + "items are supported. To transition forward, call startTransition(). "
                + "To transition backward, call reverseTransition().",
            SDK_URL_BASE + "drawable-resource.html#Transition", //$NON-NLS-1$
            xmlns,
            layerChildren, // children: a TransitionDrawable is a LayerDrawable
            true /*mandatory*/);

        mRootDescriptors = descriptors.toArray(new ElementDescriptor[descriptors.size()]);

        // A <selector><item> can contain any of the top level drawables
        if (selectorItem != null) {
            selectorItem.setChildren(mRootDescriptors);
        }
        // Docs says it can accept <bitmap> but code comment suggests any is possible;
        // test and either use this or just { bitmap }
        if (layerItem != null) {
            layerItem.setChildren(mRootDescriptors);
        }
    }
}
