/*
 * Copyright (C) 2013 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.resources;

import static com.android.SdkConstants.ANDROID_NS_NAME;
import static com.android.SdkConstants.ATTR_REF_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.PREFIX_THEME_REF;
import static com.android.SdkConstants.RESOURCE_CLZ_ATTR;
import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY;
import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.resources.ResourceType;

/**
 * A {@linkplain ResourceUrl} represents a parsed resource url such as {@code @string/foo} or
 * {@code ?android:attr/bar}
 */
public class ResourceUrl {
    /** Type of resource */
    @NonNull public final ResourceType type;

    /** Name of resource */
    @NonNull public final String name;

    /** The namespace, or null if it's in the project namespace */
    @Nullable public final String namespace;

    /** If true, the resource is in the android: framework */
    public final boolean framework;

    /** Whether an id resource is of the form {@code @+id} rather than just {@code @id} */
    public final boolean create;

    /** Whether this is a theme resource reference */
    public final boolean theme;

    private ResourceUrl(@NonNull ResourceType type, @NonNull String name,
                        @Nullable String namespace, boolean framework,
                        boolean create, boolean theme) {
        this.type = type;
        this.name = name;
        this.framework = framework;
        this.namespace = namespace;
        this.create = create;
        this.theme = theme;
    }

    /**
     * Creates a new resource URL. Normally constructed via {@link #parse(String)}.
     *
     * @param type the resource type
     * @param name the name
     * @param framework whether it's a framework resource
     * @param create if it's an id resource, whether it's of the form {@code @+id}
     */
    public static ResourceUrl create(@NonNull ResourceType type, @NonNull String name,
            boolean framework, boolean create) {
        return new ResourceUrl(type, name, framework ? ANDROID_NS_NAME : null, framework, create, false);
    }

    public static ResourceUrl create(@NonNull ResourceValue value) {
        return create(value.getResourceType(), value.getName(), value.isFramework(), false);
    }

    /**
     * Return the resource type of the given url, and the resource name
     *
     * @param url the resource url to be parsed
     * @return a pair of the resource type and the resource name
     */
    @Nullable
    public static ResourceUrl parse(@NonNull String url) {
        return parse(url, false);
    }

    /**
     * Return the resource type of the given url, and the resource name.
     *
     * @param url the resource url to be parsed
     * @param forceFramework force the returned value to be a framework resource.
     * @return a pair of the resource type and the resource name
     */
    @Nullable
    public static ResourceUrl parse(@NonNull String url, boolean forceFramework) {
        boolean isTheme = false;
        // Handle theme references
        if (url.startsWith(PREFIX_THEME_REF)) {
            isTheme = true;
            String remainder = url.substring(PREFIX_THEME_REF.length());
            if (url.startsWith(ATTR_REF_PREFIX)) {
                url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
            } else {
                int colon = url.indexOf(':');
                if (colon != -1) {
                    // Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig
                    if (remainder.indexOf('/', colon) == -1) {
                        remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/'
                                + remainder.substring(colon);
                    }
                    url = PREFIX_RESOURCE_REF + remainder;
                } else {
                    int slash = url.indexOf('/');
                    if (slash == -1) {
                        url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
                    }
                }
            }
        }

        if (!url.startsWith(PREFIX_RESOURCE_REF) || isNullOrEmpty(url)) {
            return null;
        }

        int typeEnd = url.indexOf('/', 1);
        if (typeEnd == -1) {
            return null;
        }
        int nameBegin = typeEnd + 1;

        // Skip @ and @+
        boolean create = url.startsWith("@+");
        int typeBegin = create ? 2 : 1;

        int colon = url.lastIndexOf(':', typeEnd);
        boolean framework = forceFramework;
        String namespace = forceFramework ? ANDROID_NS_NAME : null;
        if (colon != -1) {
            if (url.startsWith(ANDROID_NS_NAME, typeBegin)) {
                framework = true;
                namespace = ANDROID_NS_NAME;
            } else {
                namespace = url.substring(typeBegin, colon);
            }
            typeBegin = colon + 1;
        }
        String typeName = url.substring(typeBegin, typeEnd);
        ResourceType type = ResourceType.getEnum(typeName);
        if (type == null) {
            return null;
        }
        String name = url.substring(nameBegin);
        if (name.isEmpty()) {
            return null;
        }
        return new ResourceUrl(type, name, namespace, framework, create, isTheme);
    }

    /** Returns if the resource url is @null, @empty or @undefined. */
    public static boolean isNullOrEmpty(@NonNull String url) {
        return url.equals(REFERENCE_NULL) || url.equals(REFERENCE_EMPTY) ||
                url.equals(REFERENCE_UNDEFINED);
    }

    /**
     * Checks whether this resource has a valid name. Used when parsing data that isn't
     * necessarily known to be a valid resource; for example, "?attr/hello world"
     */
    public boolean hasValidName() {
        // Make sure it looks like a resource name; if not, it could just be a string
        // which starts with a ?, etc.
        if (name.isEmpty()) {
            return false;
        }

        if (!Character.isJavaIdentifierStart(name.charAt(0))) {
            return false;
        }
        for (int i = 1, n = name.length(); i < n; i++) {
            char c = name.charAt(i);
            if (!Character.isJavaIdentifierPart(c) && c != '.') {
                return false;
            }
        }

        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(theme ? PREFIX_THEME_REF : PREFIX_RESOURCE_REF);
        if (create) {
            sb.append('+');
        }
        if (framework) {
            sb.append(ANDROID_NS_NAME);
            sb.append(':');
        }
        sb.append(type.getName());
        sb.append('/');
        sb.append(name);
        return sb.toString();
    }

    @SuppressWarnings("RedundantIfStatement")
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ResourceUrl that = (ResourceUrl) o;

        if (create != that.create) {
            return false;
        }
        if (framework != that.framework) {
            return false;
        }
        if (!name.equals(that.name)) {
            return false;
        }
        if (type != that.type) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = type.hashCode();
        result = 31 * result + name.hashCode();
        result = 31 * result + (framework ? 1 : 0);
        result = 31 * result + (create ? 1 : 0);
        return result;
    }
}
