package br.ufrgs.inf.prosoft.adaptivecaching.analysis.decision.flowchart.model;

import br.ufrgs.inf.prosoft.tigris.monitoring.metadata.LogTrace;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashSet;
import java.util.Set;

/**
 * The type Method stats.
 */
public class MethodStats {

    //exactly same method calls
    private Long sameOccurrences;

    //number of same method calls with different return
    private Long differentReturnOccurrences;
    private Long sameOccurrencesExecutionTime;
    private Long sameOccurrencesHigherExecutionTime;

    private Long amountOfIdentifiedSameOccurences;
    private Set<String> uniqueUsers;

    /**
     * Instantiates a new Method stats.
     */
    public MethodStats() {
        sameOccurrences = 0L;
        differentReturnOccurrences = 0L;
        sameOccurrencesExecutionTime = 0L;
        sameOccurrencesHigherExecutionTime = 0L;
        amountOfIdentifiedSameOccurences = 0L;
        uniqueUsers = new HashSet<>();
    }

    /**
     * Instantiates a new Method stats.
     *
     * @param logTrace the log trace
     */
    public MethodStats(LogTrace logTrace) {
        this();
        addSameOccurrence(logTrace);
    }

    /**
     * Add same occurrence.
     *
     * @param logTrace the log trace
     */
    public void addSameOccurrence(LogTrace logTrace) {
        sameOccurrences++;
        addSameOccurrencesTotalExecutionTime(logTrace.totalTime());

        //todo cannot be based on string
        if (!logTrace.getUserId().equals("Anonymous")) {
            uniqueUsers.add(logTrace.getUserId());
            addIdentifiedSameOccurence();
        }
    }

    /**
     * Add different return occurrence.
     */
    public void addDifferentReturnOccurrence() {
        //todo if the request was identified, it should counts
        //addIdentifiedSameOccurence();
        differentReturnOccurrences += 1;
    }

    private void addSameOccurrencesTotalExecutionTime(Long executionTime) {
        sameOccurrencesExecutionTime += executionTime;
        if (executionTime > sameOccurrencesHigherExecutionTime)
            sameOccurrencesHigherExecutionTime = executionTime;
    }

    /**
     * Gets same occurrences total execution time.
     *
     * @return the same occurrences total execution time
     */
    public Long getSameOccurrencesTotalExecutionTime() {
        return sameOccurrencesExecutionTime;
    }

    /**
     * Gets same occurrences average execution time.
     *
     * @return the same occurrences average execution time
     */
    public double getSameOccurrencesAverageExecutionTime() {
        return new BigDecimal(getSameOccurrencesTotalExecutionTime())
                .divide(new BigDecimal(sameOccurrences), 5, RoundingMode.HALF_UP)
                .doubleValue();
    }

    /**
     * Gets same occurrences higher execution time.
     *
     * @return the same occurrences higher execution time
     */
    public long getSameOccurrencesHigherExecutionTime() {
        return sameOccurrencesHigherExecutionTime;
    }

    /**
     * Gets number of occurrences.
     *
     * @return All occurrences of a method, i.e. with different params and return or not
     */
    public Long getNumberOfOccurrences() {
        return sameOccurrences + differentReturnOccurrences;
    }

    /**
     * Gets number of same occurrences.
     *
     * @return the number of same occurrences
     */
    public Long getNumberOfSameOccurrences() {
        return sameOccurrences;
    }

    /**
     * Gets number of different return occurrences.
     *
     * @return the number of different return occurrences
     */
    public Long getNumberOfDifferentReturnOccurrences() {
        return differentReturnOccurrences;
    }

    /**
     * Hit ratio double.
     *
     * @return the double
     */
//from 0% to 100%
    public double hitRatio() {
        BigDecimal bd = new BigDecimal(getNumberOfSameOccurrences() * 100);
        return bd.divide(new BigDecimal(getNumberOfOccurrences()), 5, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * Miss ratio double.
     *
     * @return the double
     */
//from 0% to 100%
    public double missRatio() {
        BigDecimal bd = new BigDecimal(getNumberOfDifferentReturnOccurrences() * 100);
        return bd.divide(new BigDecimal(getNumberOfOccurrences()), 5, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * Shareability double.
     *
     * @return the double
     */
    public double shareability() {

        Long amountOfIdentifiedSameOccurences = getAmountOfIdentifiedSameOccurences();
        if (amountOfIdentifiedSameOccurences == 0)
            return 100;

        BigDecimal bd = new BigDecimal(getAmountOfUniqueIdentifiedSameOccurences() * 100);
        return bd.divide(new BigDecimal(amountOfIdentifiedSameOccurences), 5, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * To csv string.
     *
     * @return the string
     */
    public String toCSV() {
        //numberOfSameOccurrences,numberOfDifferentReturnOccurrences,totalOccurrences,sameOccurrencesAverageExecutionTime,sameOccurrencesTotalExecutionTime,hitRatio,missRatio
        return getNumberOfSameOccurrences() +
                "," + getNumberOfDifferentReturnOccurrences() +
                "," + getNumberOfOccurrences() +
                "," + getSameOccurrencesAverageExecutionTime() +
                "," + getSameOccurrencesTotalExecutionTime() +
                "," + hitRatio() +
                "," + missRatio();
    }

    @Override
    public String toString() {
        return
                "MethodStats{" +
                        "numberOfSameOccurrences=" + getNumberOfSameOccurrences() +
                        ", numberOfDifferentReturnOccurrences=" + getNumberOfDifferentReturnOccurrences() +
                        ", totalOccurrences =" + getNumberOfOccurrences() +
                        ", sameOccurrencesAverageExecutionTime =" + getSameOccurrencesAverageExecutionTime() +
                        ", sameOccurrencesTotalExecutionTime =" + getSameOccurrencesTotalExecutionTime() +
                        ", hitRatio =" + hitRatio() +
                        ", missRatio =" + missRatio() +
                        '}';
    }

    /**
     * Gets amount of identified same occurences.
     *
     * @return the amount of identified same occurences
     */
    public Long getAmountOfIdentifiedSameOccurences() {
        return amountOfIdentifiedSameOccurences;
    }

    /**
     * Gets amount of unique identified same occurences.
     *
     * @return the amount of unique identified same occurences
     */
    public int getAmountOfUniqueIdentifiedSameOccurences() {
        return uniqueUsers.size();
    }

    /**
     * Gets amount of anonymous same occurences.
     *
     * @return the amount of anonymous same occurences
     */
    public Long getAmountOfAnonymousSameOccurences() {
        return sameOccurrences - amountOfIdentifiedSameOccurences;
    }

    private void addIdentifiedSameOccurence() {
        amountOfIdentifiedSameOccurences++;
    }
}
