/**
 *******************************************************************************
 * Copyright (C) 1996-2001, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 *
 * $Revision: 13987 $
 *
 *******************************************************************************
 */

package org.unicode.cldr.util;

import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import com.ibm.icu.impl.Row;
import com.ibm.icu.impl.Row.R2;

public class Counter<T> implements Iterable<T>, Comparable<Counter<T>> {
    Map<T, RWLong> map;
    Comparator<T> comparator;

    public Counter() {
        this(null);
    }

    public Counter(boolean naturalOrdering) {
        this(naturalOrdering ? new CldrUtility.ComparableComparator() : null);
    }

    public Counter(Comparator<T> comparator) {
        if (comparator != null) {
            this.comparator = comparator;
            map = new TreeMap<T, RWLong>(comparator);
        } else {
            map = new LinkedHashMap<T, RWLong>();
        }
    }

    static private final class RWLong implements Comparable<RWLong> {
        // the uniqueCount ensures that two different RWIntegers will always be different
        static int uniqueCount;
        public long value;
        private final int forceUnique;
        public long time;
        {
            synchronized (RWLong.class) { // make thread-safe
                forceUnique = uniqueCount++;
            }
        }

        public int compareTo(RWLong that) {
            if (that.value < value) return -1;
            if (that.value > value) return 1;
            if (this == that) return 0;
            synchronized (this) { // make thread-safe
                if (that.forceUnique < forceUnique) return -1;
            }
            return 1; // the forceUnique values must be different, so this is the only remaining case
        }

        public String toString() {
            return String.valueOf(value);
        }

    }

    public Counter<T> add(T obj, long countValue) {
        RWLong count = map.get(obj);
        if (count == null) map.put(obj, count = new RWLong());
        count.value += countValue;
        count.time = 0;
        return this;
    }

    public Counter<T> add(T obj, long countValue, long time) {
        RWLong count = map.get(obj);
        if (count == null) map.put(obj, count = new RWLong());
        count.value += countValue;
        count.time = time;
        return this;
    }

    public Counter<T> add(T obj, long countValue, boolean boo) {
        RWLong count = map.get(obj);
        if (count == null) map.put(obj, count = new RWLong());
        count.value = countValue;
        count.time = 0;
        return this;
    }

    public long getCount(T obj) {
        return get(obj);
    }

    public long get(T obj) {
        RWLong count = map.get(obj);
        return count == null ? 0 : count.value;
    }

    /**
     * Get the time, or 0
     * @param obj
     * @return the time, or 0 as a fallback
     */
    public final long getTime(T obj) {
        RWLong count = map.get(obj);
        return count == null ? 0 : count.time;
    }

    public Counter<T> clear() {
        map.clear();
        return this;
    }

    public long getTotal() {
        long count = 0;
        for (T item : map.keySet()) {
            count += map.get(item).value;
        }
        return count;
    }

    public int getItemCount() {
        return size();
    }

    private static class Entry<T> {
        RWLong count;
        T value;
        int uniqueness;

        public Entry(RWLong count, T value, int uniqueness) {
            this.count = count;
            this.value = value;
            this.uniqueness = uniqueness;
        }
    }

    private static class EntryComparator<T> implements Comparator<Entry<T>> {
        int countOrdering;
        Comparator<T> byValue;

        public EntryComparator(boolean ascending, Comparator<T> byValue) {
            countOrdering = ascending ? 1 : -1;
            this.byValue = byValue;
        }

        public int compare(Entry<T> o1, Entry<T> o2) {
            if (o1.count.value < o2.count.value) return -countOrdering;
            if (o1.count.value > o2.count.value) return countOrdering;
            if (byValue != null) {
                return byValue.compare(o1.value, o2.value);
            }
            return o1.uniqueness - o2.uniqueness;
        }
    }

    public Set<T> getKeysetSortedByCount(boolean ascending) {
        return getKeysetSortedByCount(ascending, null);
    }

    public Set<T> getKeysetSortedByCount(boolean ascending, Comparator<T> byValue) {
        Set<Entry<T>> count_key = new TreeSet<Entry<T>>(new EntryComparator<T>(ascending, byValue));
        int counter = 0;
        for (T key : map.keySet()) {
            count_key.add(new Entry<T>(map.get(key), key, counter++));
        }
        Set<T> result = new LinkedHashSet<T>();
        for (Entry<T> entry : count_key) {
            result.add(entry.value);
        }
        return result;
    }

    public Set<Row.R2<Long, T>> getEntrySetSortedByCount(boolean ascending, Comparator<T> byValue) {
        Set<Entry<T>> count_key = new TreeSet<Entry<T>>(new EntryComparator<T>(ascending, byValue));
        int counter = 0;
        for (T key : map.keySet()) {
            count_key.add(new Entry<T>(map.get(key), key, counter++));
        }
        Set<R2<Long, T>> result = new LinkedHashSet<Row.R2<Long, T>>();
        for (Entry<T> entry : count_key) {
            result.add(Row.of(entry.count.value, entry.value));
        }
        return result;
    }

    public Set<T> getKeysetSortedByKey() {
        Set<T> s = new TreeSet<T>(comparator);
        s.addAll(map.keySet());
        return s;
    }

    // public Map<T,RWInteger> getKeyToKey() {
    // Map<T,RWInteger> result = new HashMap<T,RWInteger>();
    // Iterator<T> it = map.keySet().iterator();
    // while (it.hasNext()) {
    // Object key = it.next();
    // result.put(key, key);
    // }
    // return result;
    // }

    public Set<T> keySet() {
        return map.keySet();
    }

    public Iterator<T> iterator() {
        return map.keySet().iterator();
    }

    public Map<T, RWLong> getMap() {
        return map; // older code was protecting map, but not the integer values.
    }

    public int size() {
        return map.size();
    }

    public String toString() {
        return map.toString();
    }

    public Counter<T> addAll(Collection<T> keys, int delta) {
        for (T key : keys) {
            long time = getTime(key);
            add(key, delta, time);
        }
        return this;
    }

    public Counter<T> addAll(Counter<T> keys) {
        for (T key : keys) {
            long time = getTime(key);
            add(key, keys.getCount(key), time);
        }
        return this;
    }

    public int compareTo(Counter<T> o) {
        Iterator<T> i = map.keySet().iterator();
        Iterator<T> j = o.map.keySet().iterator();
        while (true) {
            boolean goti = i.hasNext();
            boolean gotj = j.hasNext();
            if (!goti || !gotj) {
                return goti ? 1 : gotj ? -1 : 0;
            }
            T ii = i.next();
            T jj = i.next();
            int result = ((Comparable<T>) ii).compareTo(jj);
            if (result != 0) {
                return result;
            }
            final long iv = map.get(ii).value;
            final long jv = o.map.get(jj).value;
            if (iv != jv) return iv < jv ? -1 : 0;
        }
    }

    public Counter<T> increment(T key) {
        return add(key, 1, getTime(key));
    }

    public boolean containsKey(T key) {
        return map.containsKey(key);
    }

    public boolean equals(Object o) {
        return map.equals(o);
    }

    public int hashCode() {
        return map.hashCode();
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public Counter<T> remove(T key) {
        map.remove(key);
        return this;
    }

    // public RWLong put(T key, RWLong value) {
    // return map.put(key, value);
    // }
    //
    // public void putAll(Map<? extends T, ? extends RWLong> t) {
    // map.putAll(t);
    // }
    //
    // public Set<java.util.Map.Entry<T, Long>> entrySet() {
    // return map.entrySet();
    // }
    //
    // public Collection<RWLong> values() {
    // return map.values();
    // }

}