//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   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 de.ugoe.cs.autoquest.usability.result;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.EnumMap;
import java.util.Map;
import java.util.Map.Entry;

import jodd.props.Props;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;

import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * Helper class, which creates a {@link UsabilityProblemDescription} for a usability rule.
 * </p>
 * 
 * @author Alexander Deicke
 */
public class UsabilityProblemDescriptionResolver {

    /**
     * <p>
     * .properties file, which contains all details concerning a usability defect.
     * </p>
     */
    private final String defectDescriptionFile = "defects.props";

    /**
     * <p>
     * Creates a defect description for a {@link UsabilityRule}.
     * </p>
     * 
     * @param name
     *            of usability rule
     * @return defect description for usability rule
     */
    public UsabilityProblemDescription descriptionFor(String usabilityRuleName) {
        Props allProperties = initProperties();
        Map<String, String> usabilityRuleProperties =
            allUsabilityRuleProperties(allProperties, usabilityRuleName);
        return createUsabilityDefect(usabilityRuleProperties);
    }

    /**
     * <p>
     * Initializes the properties, which are used to create the defect description.
     * </p>
     * 
     * @return properties needed to create defect description
     */
    private Props initProperties() {
        Optional<File> defectDescriptionFile = getDefectDescriptionFile();
        Props props = new Props();
        props.setEscapeNewLineValue("\n");
        props.setValueTrimLeft(true);
        if (defectDescriptionFile.isPresent()) {
            loadProperties(defectDescriptionFile, props);
        }
        return props;
    }

    /**
     * <p>
     * Loads the .properties file from the system.
     * </p>
     * 
     * @return iff present, {@link File} object of the .properties file
     */
    private Optional<File> getDefectDescriptionFile() {
        try {
            return Optional.fromNullable(new File(ClassLoader
                .getSystemResource(defectDescriptionFile).toURI()));
        }
        catch (URISyntaxException e) {
            Console.printerr("Error while loading defect description file.");
            Console.logException(e);
            return Optional.absent();
        }
    }

    /**
     * 
     * <p>
     * Loads the values from the .properties.
     * </p>
     * 
     * @param defectDescriptionFile
     *            .properties file
     * @param props
     *            object, which stores the loaded values
     */
    private void loadProperties(Optional<File> defectDescriptionFile, Props props) {
        try {
            props.load(defectDescriptionFile.get());
        }
        catch (IOException e) {
            Console.logException(e);
        }
    }

    /**
     * <p>
     * Returns all existing properties for a given usability rule.
     * </p>
     * 
     * @param allProperties
     *            all properties available
     * @param usabilityRuleName
     *            name of usability rule
     * @return all properties of certain usability rule
     */
    private Map<String, String> allUsabilityRuleProperties(Props allProperties,
                                                           String usabilityRuleName)
    {
        Map<String, String> usabilityRuleProperties = Maps.newHashMap();
        allProperties.extractSubProps(usabilityRuleProperties, usabilityRuleName + ".*");
        return usabilityRuleProperties;
    }

    /**
     * <p>
     * Creates the usability defect.
     * </p>
     * 
     * @param usabilityRuleProperties
     *            all properties needed for creation.
     * @return defect description for a usability rule
     */
    private UsabilityProblemDescription createUsabilityDefect(Map<String, String> usabilityRuleProperties)
    {
        String description =
            Iterables.getOnlyElement(Maps
                .filterKeys(usabilityRuleProperties, descriptionProperty()).values());
        EnumMap<UsabilityProblemSeverityLevel, Double> severity =
            getSeverityMap(usabilityRuleProperties);
        return new UsabilityProblemDescription(description, severity);
    }

    /**
     * <p>
     * Gets the description property.
     * </p>
     * 
     * @return description property
     */
    private Predicate<String> descriptionProperty() {
        return new Predicate<String>() {

            public boolean apply(String key) {
                return key.endsWith("description");
            }

        };
    }

    /**
     * <p>
     * Creates severity level map for defect description, by matching all entried from .properties
     * file to corresponding {@link UsabilityProblemSeverityLevel}.
     * </p>
     * 
     * @param usabilityRuleProperties
     *            all properties of certain usability rule
     * @return assignment of {@link UsabilityProblemSeverityLevel} and corresponding threshold
     */
    private EnumMap<UsabilityProblemSeverityLevel, Double> getSeverityMap(Map<String, String> usabilityRuleProperties)
    {
        EnumMap<UsabilityProblemSeverityLevel, Double> severityMap =
            Maps.newEnumMap(UsabilityProblemSeverityLevel.class);
        Map<String, String> allSeverityProperties =
            Maps.filterEntries(usabilityRuleProperties, allSeverityProperties());
        for (Entry<String, String> severityProperty : allSeverityProperties.entrySet()) {
            UsabilityProblemSeverityLevel severityLevel =
                getSeverityLevel(severityProperty.getKey());
            Double rule = Double.valueOf(severityProperty.getValue());
            severityMap.put(severityLevel, rule);
        }
        return severityMap;
    }

    /**
     * <p>
     * Matches severity level from .properties file against {@link UsabilityProblemSeverityLevel}.
     * </p>
     * 
     * @param severityProperty
     *            severity level from .properties file
     * @return matching {@link UsabilityProblemSeverityLevel}
     */
    private UsabilityProblemSeverityLevel getSeverityLevel(String severityProperty) {
        int startSeverityLevel = severityProperty.lastIndexOf(".") + 1;
        String severityLevelIdentifier =
            severityProperty.substring(startSeverityLevel).toUpperCase();
        return UsabilityProblemSeverityLevel.valueOf(severityLevelIdentifier);
    }

    /**
     * <p>
     * Gets the severity level properties.
     * </p>
     * 
     * @return severity level
     */
    private Predicate<Entry<String, String>> allSeverityProperties() {
        return new Predicate<Map.Entry<String, String>>() {

            public boolean apply(Map.Entry<String, String> entry) {
                return entry.getKey().contains("severity");
            }

        };
    }

}
