package ru.yandex.qatools.allure.data.plugins;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Base class for all plugins with resources.
 *
 * @author Dmitry Baev charlie@yandex-team.ru
 *         Date: 17.04.15
 * @see ProcessPlugin
 */
public abstract class AbstractPlugin implements WithResources, WithData, WithPriority {

    public static final Logger LOGGER = LoggerFactory.getLogger(AbstractPlugin.class);

    private String name = getClass().isAnnotationPresent(Plugin.Name.class) ?
            getClass().getAnnotation(Plugin.Name.class).value() : null;

    private int priority = getClass().isAnnotationPresent(Plugin.Priority.class) ?
            getClass().getAnnotation(Plugin.Priority.class).value() : 0;

    /**
     * Get name of plugin. Name should be specified in {@link Plugin.Name}
     * annotation.
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Get plugin priority. Plugins with higher priority will be processed firstly.
     */
    @Override
    public int getPriority() {
        return priority;
    }

    /**
     * This method creates a {@link PluginData} for each field with
     * {@link Plugin.Data} annotation.
     *
     * @see #getFileName(Field)
     * @see #getFieldValue(Field)
     */
    @Override
    public List<PluginData> getPluginData() {
        List<PluginData> results = new ArrayList<>();
        for (Field field : getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Plugin.Data.class)) {
                String fileName = getFileName(field);
                results.add(new PluginData(fileName, getFieldValue(field)));
            }
        }
        return results;
    }

    /**
     * Try to get field value. Field with {@link Plugin.Data} annotation should
     * be accessable.
     */
    private Object getFieldValue(Field field) {
        try {
            field.setAccessible(true);
            return field.get(this);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            LOGGER.error("Can't access to field value", e);
            return null;
        }
    }

    /**
     * Get file name for field with {@link Plugin.Data} annotation. If name
     * is default then use {@link #name} as file name and <code>json</code>
     * as file extension.
     *
     * @see #getName()
     */
    private String getFileName(Field field) {
        String fileName = field.getAnnotation(Plugin.Data.class).value();
        if ("##default".equals(fileName)) {
            fileName = getName() + ".json";
        }
        return fileName;
    }

    /**
     * Verify plugin class. Plugin with resources plugin should not be private, abstract or interface.
     * Also class should be annotated with {@link Plugin.Name}
     * annotation. Returns true if given class is valid plugin, false otherwise.
     * Plugin class should not be null.
     */
    public static boolean isValid(Class<? extends AbstractPlugin> pluginClass) {
        return pluginClass != null && pluginClass.isAnnotationPresent(Plugin.Name.class) &&
                checkModifiers(pluginClass) && checkFieldsWithDataAnnotation(pluginClass);
    }

    /**
     * Check given class modifiers. Plugin with resources plugin should not be private or abstract
     * or interface.
     */
    private static boolean checkModifiers(Class<? extends AbstractPlugin> pluginClass) {
        int modifiers = pluginClass.getModifiers();
        return !Modifier.isAbstract(modifiers) &&
                !Modifier.isInterface(modifiers) &&
                !Modifier.isPrivate(modifiers);
    }

    /**
     * Check fields with {@link Plugin.Data} annotation. Firstly filter all declared fields
     * and then check it using {@link #shouldHasUniqueValues(List)}
     */
    private static boolean checkFieldsWithDataAnnotation(Class<? extends AbstractPlugin> pluginClass) {
        List<Field> dataFields = new ArrayList<>();
        for (Field field : pluginClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(Plugin.Data.class)) {
                dataFields.add(field);
            }
        }
        return shouldHasUniqueValues(dataFields);
    }

    /**
     * Check that each given field has unique value in {@link Plugin.Data} annotation.
     */
    private static boolean shouldHasUniqueValues(List<Field> dataFields) {
        Set<String> dataValues = new HashSet<>();
        for (Field field : dataFields) {
            String value = field.getAnnotation(Plugin.Data.class).value();
            if (dataValues.contains(value)) {
                return false;
            }
            dataValues.add(value);
        }
        return true;
    }
}

@@ -1986,17 +1986,17 @@
 e access
-a
+i
 ble.%0A   

fix grammar in javadoc