/*
 * Copyright (C) 2012 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.ide.common.xml;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;

import org.w3c.dom.Attr;

import java.util.Comparator;

import static com.android.SdkConstants.ATTR_COLOR;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_STYLE;
import static com.android.SdkConstants.XMLNS;
import static com.google.common.base.Strings.nullToEmpty;

/** Order to use when sorting attributes */
public enum XmlAttributeSortOrder {
    NO_SORTING("none"),     //$NON-NLS-1$
    ALPHABETICAL("alpha"),  //$NON-NLS-1$
    LOGICAL("logical");     //$NON-NLS-1$

    XmlAttributeSortOrder(String key) {
        this.key = key;
    }

    public final String key;

    /**
     * @return a comparator for use by this attribute sort order
     */
    @Nullable
    public Comparator<Attr> getAttributeComparator() {
        switch (this) {
            case NO_SORTING:
                return null;
            case ALPHABETICAL:
                return ALPHABETICAL_COMPARATOR;
            case LOGICAL:
            default:
                return SORTED_ORDER_COMPARATOR;
        }
    }

    /** Comparator which can be used to sort attributes in the coding style priority order */
    private static final Comparator<Attr> SORTED_ORDER_COMPARATOR = new Comparator<Attr>() {
        @Override
        public int compare(Attr attr1, Attr attr2) {
            // Namespace declarations should always go first
            String prefix1 = attr1.getPrefix();
            String prefix2 = attr2.getPrefix();
            if (XMLNS.equals(prefix1)) {
                if (XMLNS.equals(prefix2)) {
                    return 0;
                }
                return -1;
            } else if (XMLNS.equals(attr2.getPrefix())) {
                return 1;
            }

            // Sort by preferred attribute order
            String name1 = prefix1 != null ? attr1.getLocalName() : attr1.getName();
            String name2 = prefix2 != null ? attr2.getLocalName() : attr2.getName();
            return compareAttributes(prefix1, name1, prefix2, name2);
        }
    };

    /**
     * Comparator which can be used to sort attributes into alphabetical order (but xmlns
     * is always first)
     */
    private static final Comparator<Attr> ALPHABETICAL_COMPARATOR = new Comparator<Attr>() {
        @Override
        public int compare(Attr attr1, Attr attr2) {
            // Namespace declarations should always go first
            if (XMLNS.equals(attr1.getPrefix())) {
                if (XMLNS.equals(attr2.getPrefix())) {
                    return 0;
                }
                return -1;
            } else if (XMLNS.equals(attr2.getPrefix())) {
                return 1;
            }

            // Sort by name rather than local name to ensure we sort by namespaces first,
            // then by names.
            return attr1.getName().compareTo(attr2.getName());
        }
    };

    /**
     * Returns {@link Comparator} values for ordering attributes in the following
     * order:
     * <ul>
     *   <li> id
     *   <li> style
     *   <li> layout_width
     *   <li> layout_height
     *   <li> other layout params, sorted alphabetically
     *   <li> other attributes, sorted alphabetically
     * </ul>
     *
     * @param name1 the first attribute name to compare
     * @param name2 the second attribute name to compare
     * @return a negative number if name1 should be ordered before name2
     */
    public static int compareAttributes(String name1, String name2) {
        int priority1 = getAttributePriority(name1);
        int priority2 = getAttributePriority(name2);
        if (priority1 != priority2) {
            return priority1 - priority2;
        }

        // Sort remaining attributes alphabetically
        return name1.compareTo(name2);
    }

    /**
     * Returns {@link Comparator} values for ordering attributes in the following
     * order:
     * <ul>
     *   <li> id
     *   <li> style
     *   <li> layout_width
     *   <li> layout_height
     *   <li> other layout params, sorted alphabetically
     *   <li> other attributes, sorted alphabetically, first by namespace, then by name
     * </ul>
     * @param prefix1 the namespace prefix, if any, of {@code name1}
     * @param name1 the first attribute name to compare
     * @param prefix2  the namespace prefix, if any, of {@code name2}
     * @param name2 the second attribute name to compare
     * @return a negative number if name1 should be ordered before name2
     */
    public static int compareAttributes(
            @Nullable String prefix1, @NonNull String name1,
            @Nullable String prefix2, @NonNull String name2) {
        int priority1 = getAttributePriority(name1);
        int priority2 = getAttributePriority(name2);
        if (priority1 != priority2) {
            return priority1 - priority2;
        }

        int namespaceDelta = nullToEmpty(prefix1).compareTo(nullToEmpty(prefix2));
        if (namespaceDelta != 0) {
            return namespaceDelta;
        }

        // Sort remaining attributes alphabetically
        return name1.compareTo(name2);
    }


    /** Returns a sorting priority for the given attribute name */
    private static int getAttributePriority(String name) {
        if (ATTR_ID.equals(name)) {
            return 10;
        }

        if (ATTR_NAME.equals(name)) {
            return 15;
        }

        if (ATTR_STYLE.equals(name)) {
            return 20;
        }

        if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
            // Width and height are special cased because we (a) want width and height
            // before the other layout attributes, and (b) we want width to sort before height
            // even though it comes after it alphabetically.
            if (name.equals(ATTR_LAYOUT_WIDTH)) {
                return 30;
            }
            if (name.equals(ATTR_LAYOUT_HEIGHT)) {
                return 40;
            }

            return 50;
        }

        // "color" sorts to the end
        if (ATTR_COLOR.equals(name)) {
            return 100;
        }

        return 60;
    }
}