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

import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;

import com.android.ide.common.resources.ResourceItem;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jface.dialogs.IInputValidator;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * Validator which ensures that new Android resource names are valid.
 */
public class ResourceNameValidator implements IInputValidator {
    /** Set of existing names to check for conflicts with */
    private Set<String> mExisting;

    /**
     * True if the resource name being considered is a "file" based resource (where the
     * resource name is the actual file name, rather than just a value attribute inside an
     * XML file name of arbitrary name
     */
    private boolean mIsFileType;

    /**
     * True if the resource type can point to image resources
     */
    private boolean mIsImageType;

    /** If true, allow .xml as a name suffix */
    private boolean mAllowXmlExtension;

    private ResourceNameValidator(boolean allowXmlExtension, Set<String> existing,
            boolean isFileType, boolean isImageType) {
        mAllowXmlExtension = allowXmlExtension;
        mExisting = existing;
        mIsFileType = isFileType;
        mIsImageType = isImageType;
    }

    public String isValid(String newText) {
        // IValidator has the same interface as SWT's IInputValidator
        try {
            if (newText == null || newText.trim().length() == 0) {
                return "Enter a new name";
            }

            if (mAllowXmlExtension && newText.endsWith(DOT_XML)) {
                newText = newText.substring(0, newText.length() - DOT_XML.length());
            }

            if (mAllowXmlExtension && mIsImageType
                    && ImageUtils.hasImageExtension(newText)) {
                newText = newText.substring(0, newText.lastIndexOf('.'));
            }

            if (newText.indexOf('.') != -1 && !newText.endsWith(DOT_XML)) {
                if (mIsImageType) {
                    return "The filename must end with .xml or .png";
                } else {
                    return "The filename must end with .xml";
                }
            }

            // Resource names must be valid Java identifiers, since they will
            // be represented as Java identifiers in the R file:
            if (!Character.isJavaIdentifierStart(newText.charAt(0))) {
                return "The resource name must begin with a character";
            }
            for (int i = 1, n = newText.length(); i < n; i++) {
                char c = newText.charAt(i);
                if (!Character.isJavaIdentifierPart(c)) {
                    return String.format("'%1$c' is not a valid resource name character", c);
                }
            }

            if (mIsFileType) {
                char first = newText.charAt(0);
                if (!(first >= 'a' && first <= 'z')) {
                    return String.format(
                            "File-based resource names must start with a lowercase letter.");
                }

                // AAPT only allows lowercase+digits+_:
                // "%s: Invalid file name: must contain only [a-z0-9_.]","
                for (int i = 0, n = newText.length(); i < n; i++) {
                    char c = newText.charAt(i);
                    if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')) {
                        return String.format(
                          "File-based resource names must contain only lowercase a-z, 0-9, or _.");
                    }
                }
            }

            String level = "1.5"; //$NON-NLS-1$
            IStatus validIdentifier = JavaConventions.validateIdentifier(newText, level, level);
            if (!validIdentifier.isOK()) {
                return String.format("%1$s is not a valid name (reserved Java keyword)", newText);
            }

            if (mExisting != null && mExisting.contains(newText)) {
                return String.format("%1$s already exists", newText);
            }

            return null;
        } catch (Exception e) {
            AdtPlugin.log(e, "Validation failed: %s", e.toString());
            return ""; //$NON-NLS-1$
        }
    }

    /**
     * Creates a new {@link ResourceNameValidator}
     *
     * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the
     *            resource name
     * @param type the resource type of the resource name being validated
     * @return a new {@link ResourceNameValidator}
     */
    public static ResourceNameValidator create(boolean allowXmlExtension,
            ResourceFolderType type) {
        boolean isFileType = type != ResourceFolderType.VALUES;
        return new ResourceNameValidator(allowXmlExtension, null, isFileType,
                type == ResourceFolderType.DRAWABLE);
    }

    /**
     * Creates a new {@link ResourceNameValidator}
     *
     * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the
     *            resource name
     * @param existing An optional set of names that already exist (and therefore will not
     *            be considered valid if entered as the new name)
     * @param type the resource type of the resource name being validated
     * @return a new {@link ResourceNameValidator}
     */
    public static ResourceNameValidator create(boolean allowXmlExtension, Set<String> existing,
            ResourceType type) {
        boolean isFileType = ResourceHelper.isFileBasedResourceType(type);
        return new ResourceNameValidator(allowXmlExtension, existing, isFileType,
                type == ResourceType.DRAWABLE);
    }

    /**
     * Creates a new {@link ResourceNameValidator}
     *
     * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the
     *            resource name
     * @param project the project to validate new resource names for
     * @param type the resource type of the resource name being validated
     * @return a new {@link ResourceNameValidator}
     */
    public static ResourceNameValidator create(boolean allowXmlExtension, IProject project,
            ResourceType type) {
        Set<String> existing = new HashSet<String>();
        ResourceManager manager = ResourceManager.getInstance();
        ProjectResources projectResources = manager.getProjectResources(project);
        Collection<ResourceItem> items = projectResources.getResourceItemsOfType(type);
        for (ResourceItem item : items) {
            existing.add(item.getName());
        }

        boolean isFileType = ResourceHelper.isFileBasedResourceType(type);
        return new ResourceNameValidator(allowXmlExtension, existing, isFileType,
                type == ResourceType.DRAWABLE);
    }
}
