/*
 * qualinsight-plugins-sonarqube-smell
 * Copyright (c) 2015, QualInsight
 * http://www.qualinsight.com/
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, you can retrieve a copy
 * from <http://www.gnu.org/licenses/>.
 */
package com.qualinsight.plugins.sonarqube.smell.plugin.extension;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import com.google.common.collect.ImmutableList;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.Metric.ValueType;
import org.sonar.api.measures.Metrics;
import com.qualinsight.plugins.sonarqube.smell.api.annotation.Smell;
import com.qualinsight.plugins.sonarqube.smell.api.model.SmellType;

/**
 * Provides the {@link Metric}s related to Smell measurement.
 *
 * @author Michel Pawlak
 */
@SuppressWarnings("unchecked")
public final class SmellMetrics implements Metrics {

    public static final String DOMAIN = "Code Smells";

    private static final List<Metric<Serializable>> SMELL_METRICS;

    private static final Map<SmellType, Metric<Serializable>> SMELL_METRICS_BY_TYPE = new EnumMap<>(SmellType.class);

    private static final Map<String, Metric<Serializable>> SMELL_METRICS_BY_KEY = new HashMap<>();

    /**
     * Metric that tracks the debt related to Smell issues.
     */
    public static final Metric<Long> SMELL_DEBT = new Metric.Builder("SMELL_DEBT", "Code Smells debt (reported by manual reviews)", ValueType.WORK_DUR).setBestValue(0d)
        .setDescription("Technical debt reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(CoreMetrics.DOMAIN_MAINTAINABILITY)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that tracks the count of {@link Smell} annotations.
     */
    public static final Metric<Integer> SMELL_COUNT = new Metric.Builder("SMELL_COUNT", "Smells count", ValueType.INT).setBestValue(0d)
        .setDescription("Total number of reported code smells.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts WRONG_LOGIC {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_WRONG_LOGIC = new Metric.Builder("SMELL_COUNT_WRONG_LOGIC", "Wrong logic count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of wrong logics reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts WRONG_LANGUAGE {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_WRONG_LANGUAGE = new Metric.Builder("SMELL_COUNT_WRONG_LANGUAGE", "Wrong language count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of wrong languages reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts OVERCOMPLICATED_ALGORITHM {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_OVERCOMPLICATED_ALGORITHM = new Metric.Builder("SMELL_COUNT_OVERCOMPLICATED_ALGORITHM", "Overcomplicated algorithm count", ValueType.INT)
        .setBestValue(0d)
        .setDescription("Number of overcomplicated algorithms reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts ANTI_PATTERN {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_ANTI_PATTERN = new Metric.Builder("SMELL_COUNT_ANTI_PATTERN", "Anti-pattern count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of anti-patterns reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts BAD_DESIGN {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_BAD_DESIGN = new Metric.Builder("SMELL_COUNT_BAD_DESIGN", "Bad design count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of bad designs reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts USELESS_TEST {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_USELESS_TEST = new Metric.Builder("SMELL_COUNT_USELESS_TEST", "Useless test count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of useless tests reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts MEANINGLESS_COMMENT {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_MEANINGLESS_COMMENT = new Metric.Builder("SMELL_COUNT_MEANINGLESS_COMMENT", "Meaningless comment count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of meaningless comments reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts UNCOMMUNICATIVE_NAME {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_UNCOMMUNICATIVE_NAME = new Metric.Builder("SMELL_COUNT_UNCOMMUNICATIVE_NAME", "Uncommunicative name count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of uncommunicative names reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts SPECULATIVE_GENERALITY {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_SPECULATIVE_GENERALITY = new Metric.Builder("SMELL_COUNT_SPECULATIVE_GENERALITY", "Speculative generality count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of speculative generalities reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts ODDBALL_SOLUTION {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_ODDBALL_SOLUTION = new Metric.Builder("SMELL_COUNT_ODDBALL_SOLUTION", "Oddball solution count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of oddball solutions reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts PRIMITIVES_OBSESSION {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_PRIMITIVES_OBSESSION = new Metric.Builder("SMELL_COUNT_PRIMITIVES_OBSESSION", "Primitives obsession count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of primitives obsessions reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts INDECENT_EXPOSURE {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_INDECENT_EXPOSURE = new Metric.Builder("SMELL_COUNT_INDECENT_EXPOSURE", "Indecent exposure count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of indecent exposures reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts SOLUTION_SPRAWL {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_SOLUTION_SPRAWL = new Metric.Builder("SMELL_COUNT_SOLUTION_SPRAWL", "Solution sprawl count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of solution sprawls reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts MIDDLE_MAN {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_MIDDLE_MAN = new Metric.Builder("SMELL_COUNT_MIDDLE_MAN", "Middle man count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of middle men reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts REFUSED_BEQUEST {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_REFUSED_BEQUEST = new Metric.Builder("SMELL_COUNT_REFUSED_BEQUEST", "Refused bequest count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of refused bequests reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts NON_EXCEPTION {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_NON_EXCEPTION = new Metric.Builder("SMELL_COUNT_NON_EXCEPTION", "Non exception count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of non exceptions reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts HOW_COMMENT {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_HOW_COMMENT = new Metric.Builder("SMELL_COUNT_HOW_COMMENT", "How comment count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of how comments reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts MISSING_IMPLEMENTATION {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_MISSING_IMPLEMENTATION = new Metric.Builder("SMELL_COUNT_MISSING_IMPLEMENTATION", "Missing implementation count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of missing implementations reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts MULTIPLE_RESPONSIBILITIES {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_MULTIPLE_RESPONSIBILITIES = new Metric.Builder("SMELL_COUNT_MULTIPLE_RESPONSIBILITIES", "Multiple responsibilities count", ValueType.INT)
        .setBestValue(0d)
        .setDescription("Number of multiple responsibilities reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts ABBREVIATIONS_USAGE {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_ABBREVIATIONS_USAGE = new Metric.Builder("SMELL_COUNT_ABBREVIATIONS_USAGE", "Abbreviations usage count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of abbreviations usages reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts BAD_LOGGING {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_BAD_LOGGING = new Metric.Builder("SMELL_COUNT_BAD_LOGGING", "Bad logging count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of bad loggings reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts REINVENTED_WHEEL {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_REINVENTED_WHEEL = new Metric.Builder("SMELL_COUNT_REINVENTED_WHEEL", "Reinvented wheel count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of reinvented wheels reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts BAD_FRAMEWORK_USAGE {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_BAD_FRAMEWORK_USAGE = new Metric.Builder("SMELL_COUNT_BAD_FRAMEWORK_USAGE", "Bad framework usage count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of bad framework usages reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts MISSING_DOCUMENTATION {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_MISSING_DOCUMENTATION = new Metric.Builder("SMELL_COUNT_MISSING_DOCUMENTATION", "Missing documentation count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of missing documentation reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts MISSING_TEST {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_MISSING_TEST = new Metric.Builder("SMELL_COUNT_MISSING_TEST", "Missing tests count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of missing tests reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts OTHER {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_OTHER = new Metric.Builder("SMELL_COUNT_OTHER", "Uncategorized smells count", ValueType.INT).setBestValue(0d)
        .setDescription("Number of uncategorized smells reported by developers.")
        .setDirection(Metric.DIRECTION_WORST)
        .setDomain(DOMAIN)
        .setOptimizedBestValue(true)
        .create();

    /**
     * Metric that counts NON_COMPLIANCE_WITH_STANDARDS {@link SmellType}
     */
    public static final Metric<Integer> SMELL_COUNT_NON_COMPLIANCE_WITH_STANDARDS = new Metric.Builder("SMELL_COUNT_NON_COMPLIANCE_WITH_STANDARDS", "Non compliances with standards count",
        ValueType.INT).setBestValue(0d)
            .setDescription("Number of non compliances with standards reported by developers.")
            .setDirection(Metric.DIRECTION_WORST)
            .setDomain(DOMAIN)
            .setOptimizedBestValue(true)
            .create();

    /*
     * Blocks that populates the list of Smell Metrics using reflection as well as SmellType to Metric map.
     */
    static {
        SMELL_METRICS = new LinkedList<>();
        for (final Field field : SmellMetrics.class.getFields()) {
            final String fieldName = field.getName();
            final Metric<Serializable> metric;
            if (Metric.class.isAssignableFrom(field.getType())) {
                try {
                    metric = (Metric<Serializable>) field.get(null);
                    SMELL_METRICS.add(metric);
                    SMELL_METRICS_BY_KEY.put(metric.getKey(), metric);
                } catch (final IllegalAccessException e) {
                    throw new IllegalStateException("Introspection error while declaring Smell Metrics", e);
                }
                // ugly hack to map SmellTypes to Metrics without having to make the API depend on the SQ API.
                // best approach needs to be discussed.
                if (fieldName.contains("SMELL_COUNT_")) {
                    SMELL_METRICS_BY_TYPE.put(SmellType.valueOf(fieldName.replace("SMELL_COUNT_", "")), metric);
                }
            }
        }
    }

    @SuppressWarnings("rawtypes")
    @Override
    public List<Metric> getMetrics() {
        return ImmutableList.<Metric> copyOf(SMELL_METRICS);
    }

    /**
     * Returns a {@link Metric} from a {@link SmellType}
     *
     * @param type {@link SmellType} to convert to a metric
     * @return a {@link Metric} corresponding to the {@link SmellType}
     */
    @CheckForNull
    public static final Metric<Serializable> metricFor(final SmellType type) {
        return SMELL_METRICS_BY_TYPE.get(type);
    }

    /**
     * Returns a {@link Metric} from a {@link SmellType}
     *
     * @param metricKey Key to convert to a metric
     * @return a {@link Metric} corresponding to the key
     */
    @CheckForNull
    public static final Metric<Serializable> metricFor(final String metricKey) {
        return SMELL_METRICS_BY_KEY.get(metricKey);
    }

    /**
     * Returns a {@link List} of all {@link Metric}s handled by the Smells plugin.
     *
     * @return {@link List} of {@link Metric}
     */
    @CheckForNull
    public static final List<Metric<Serializable>> metrics() {
        return ImmutableList.<Metric<Serializable>> copyOf(SMELL_METRICS);
    }

}