/*
 * FrequencySet.java
 *
 * Copyright (C) 2002-2006 Alexei Drummond and Andrew Rambaut
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST 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 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST 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 BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package beast.base.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * modified from BEAST 1 FrequencySet<T>
 * Stores a set of objects with frequencies
 *
 * @author Andrew Rambaut
 * @author Walter Xie
 */
public class FrequencySet<T> {
    public static final double DEFAULT_CRED_SET = 0.95;

    public FrequencySet() {
        setCredSetProbability(DEFAULT_CRED_SET);
    }

    // really need ?
    public FrequencySet(double credSetProbability) {
        setCredSetProbability(credSetProbability);
    }

    /**
     * get number of objects
     */
    public int size() {
        return frequencyMap.size();
    }

    /**
     * get object in frequency order
     */
    public T get(int i) {
        if (!sorted) {
            sortByFrequency();
        }

        return sortedList.get(i);
    }


    public Integer getFrequency(T obj) {
        if (!sorted) {
            sortByFrequency();
        }

        return frequencyMap.get(obj);
    }

    /**
     * get frequency of ith object
     */
    public int getFrequency(int i) {
        if (!sorted) {
            sortByFrequency();
        }
        return getFrequency(sortedList.get(i));
    }

    /**
     * get sum of all frequencies
     */
    public int getSumFrequency() {
        if (!sorted) {
            sortByFrequency();
        }

        int sum = 0;
        for (int i = 0; i < size(); i++) {
            sum += getFrequency(i);
        }

        return sum;
    }

    /**
     * adds an object to the set
     */
    public void add(T object) {
        add(object, 1);
    }

    /**
     * adds an object to the set with an initial frequency, or if object already
     * in frequency set then frequency is incremented by given frequency.
     */
    public void add(T object, int frequency) {
        Integer freq = frequencyMap.get(object);
        if (freq != null) {
            freq += frequency;
            frequencyMap.put(object, freq);
        } else {
            frequencyMap.put(object, frequency);
            sortedList.add(object);
            sorted = false;
        }
    }

    /**
     * The frequencySets are equal if their inner sets are equal
     */
    @Override
    public boolean equals(Object obj) {
        return (obj instanceof FrequencySet) && sortedList.equals(((FrequencySet<?>) obj).sortedList);
    }

    public Map<T, Integer> getFrequencyMap() {
        return frequencyMap;
    }

    /**
     *
     * @param target     if null, then only return credibleSetList.credibleSetList
     * @return
     */
    public CredibleSet<T> getCredibleSet(T target) {
        CredibleSet<T> credibleSet = new CredibleSet<>(getCredSetProbability());
        credibleSet.setCredibleSetList(target, this);
        return credibleSet;
    }

    public CredibleSet<T> getCredibleSet() {
        return getCredibleSet(null);
    }


    public double getCredSetProbability() {
        if (credSetProbability == 0)
            setCredSetProbability(DEFAULT_CRED_SET);
        return credSetProbability;
    }

    public void setCredSetProbability(double credSetProbability) {
        this.credSetProbability = credSetProbability;
    }

    /**
     * sort by descending frequency
     */
    private void sortByFrequency() {
        sortedList.clear();
        sortedList.addAll(frequencyMap.keySet());
        Collections.sort(sortedList, frequencyComparator);
        sorted = true;
    }

    //
    // Private stuff
    //

    private List<T> sortedList = new ArrayList<>();
    private Map<T, Integer> frequencyMap = new HashMap<>();
    private boolean sorted = false;
    private double credSetProbability = 0;

    private Comparator<T> frequencyComparator = new Comparator<T>() {
        @Override
		public int compare(T obj1, T obj2) {
            return frequencyMap.get(obj2) - frequencyMap.get(obj1);
        }
    };

}