/*
 * Created on 24.08.2004
 */
package commons.collections;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import basics.math.BasicStatistics;
import basics.utl.EmptyIterator;
import commons.collections.interfaces.Sized;
import commons.collections.interfaces.TermTableCalcCondition;
import commons.collections.interfaces.TermTableCalculation;

/**
 * @author kst
 */
public class TermTable implements Sized
{
static final String               DELIMITER = ",";
private Hashtable<String, Double> table     = null;
// private double minValue = -1;
// private double maxValue = -1;
@SuppressWarnings("unchecked")
static final Comparator           DESCEND   = new Comparator()
                                            {
                                               public int compare(Object o1, Object o2)
                                               {
                                                  Double v1 = ((TermValue)o1).getValue();
                                                  Double v2 = ((TermValue)o2).getValue();
                                                  return v2.compareTo(v1);
                                               }
                                            };
@SuppressWarnings("unchecked")
static final Comparator           ASCEND    = new Comparator()
                                            {
                                               public int compare(Object o1, Object o2)
                                               {
                                                  Double v1 = ((TermValue)o1).getValue();
                                                  Double v2 = ((TermValue)o2).getValue();
                                                  return v1.compareTo(v2);
                                               }
                                            };
@SuppressWarnings("unchecked")
static final Comparator           ALPHA     = new Comparator()
                                            {
                                               public int compare(Object o1, Object o2)
                                               {
                                                  TermValue r1 = (TermValue)o1;
                                                  TermValue r2 = (TermValue)o2;
                                                  return r1.term.compareToIgnoreCase(r2.term);
                                               }
                                            };

public TermTable()
{
   table = new Hashtable<String, Double>();
}

public void clear()
{
   table.clear();
}

public double getMaxValue()
{
   double max = Double.MIN_VALUE;
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      double v = entry.getValue();
      if(v > max)
         max = v;
   }
   return max;
}

public double getMinValue()
{
   double max = Double.MAX_VALUE;
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      double v = entry.getValue();
      if(v < max)
         max = v;
   }
   return max;
}

public synchronized void normaliseTable()
{
   double max = getMaxValue();
   max = max == 0 ? 1 : max;
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      double v = entry.getValue();
      entry.setValue(v / max);
   }
}

public synchronized void removeLowEnd(double quota)
{
   int size = size();
   int max = size - ((int)((double)size * quota));
   if(max == size)
      return;
   List<TermValue> list = getSortedListDescending();
   clear();
   for(TermValue tv : list)
   {
      if(max-- <= 0)
         break;
      add(tv);
   }
}

static synchronized String cutDelimiterTail(String text)
{
   if(text == null)
      return null;
   if(text.endsWith(DELIMITER))
   {
      text = text.substring(0, text.length() - DELIMITER.length());
   }
   return text;
}

@SuppressWarnings("unchecked")
public List<TermValue> getSortedList()
{
   List<TermValue> list = getList();
   Collections.sort(list, ASCEND);
   return list;
}

@SuppressWarnings("unchecked")
public ArrayList<TermValue> getList()
{
   ArrayList<TermValue> list = new ArrayList<TermValue>();
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      String k = entry.getKey();
      double v = entry.getValue();
      list.add(new TermValue(k, v));
   }
   return list;
}

@SuppressWarnings("unchecked")
public synchronized List<TermValue> getAlphaSortedList()
{
   List<TermValue> list = getList();
   if(list != null)
      Collections.sort(list, ALPHA);
   return list;
}

@SuppressWarnings("unchecked")
public synchronized List<TermValue> getSortedListDescending()
{
   List<TermValue> list = getList();
   if(list != null)
      Collections.sort(list, DESCEND);
   return list;
}

public double getTotalSum()
{
   double x = 0;
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      x += entry.getValue();
   }
   return x;
}

public BasicStatistics getStats()
{
   BasicStatistics stats = new BasicStatistics();
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      stats.add(entry.getValue());
   }
   return stats;
}

// public double getMinValue ()
// {
// double min = Double.MAX_VALUE;
// Enumeration<TermValue> e = this.table.elements();
// while (e.hasMoreElements())
// {
// TermValue t = e.nextElement();
// if (t.value < min) min = t.value;
// }
// return min;
// }
public double getAverageValue()
{
   double total = 0;
   Iterator<Map.Entry<String, Double>> iter = this.table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> entry = iter.next();
      total += entry.getValue();
   }
   long x = size();
   return x == 0 ? total : total / ((double)x);
}

public TermValue get(String key)
{
   if(!table.containsKey(key))
      return null;
   return new TermValue(key, table.get(key));
}

// public void put (String term, double weight)
// {
// set(term, weight);
// }
//   
public Iterator<String> keys()
{
   if(table == null)
      return null;
   return table.keySet().iterator();
}

/**
 * Enumerates over the elements in the map. Never return null, but an empty enumerator.
 */
public Iterator<TermValue> entries()
{
   final Iterator<Map.Entry<String, Double>> iter = table.entrySet().iterator();
   // if (table == null) return new Hashtable<String, TermValue>().elements();
   return new Iterator<TermValue>()
   {
      public boolean hasNext()
      {
         return iter.hasNext();
      }

      public TermValue next()
      {
         Map.Entry<String, Double> e = iter.next();
         return new TermValue(e.getKey(), e.getValue());
      }

      public void remove()
      {
      }
   };
   // return table.keySet().iterator();
}

public void add(TermTable tft)
{
   final Iterator<Map.Entry<String, Double>> iter = table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> e = iter.next();
      add(e.getKey(), e.getValue());
   }
}

public void add(TermValue tf)
{
   this.add(tf.term, tf.value);
}

public void add(String term, double weight)
{
   // if (term != null)
   {
      // if (weight == Double.NEGATIVE_INFINITY || weight == Double.POSITIVE_INFINITY)
      // {
      // OsUtl.trace("term weight is initity, corrected to 0.");
      // weight = 0;
      // }
      // term = term.trim();
      // if (term.length() != 0)
      {
         if(table.containsKey(term))
         {
            double v = table.get(term) + weight;
            table.put(term, v);
         }
         else
         {
            table.put(term, weight);
         }
      }
   }
}

public void remove(String term)
{
   table.remove(term);
}

public void put(String term, double weight)
{
   if(term != null)
   {
      // term = term.trim();
      // if (term.length() != 0)
      {
         table.put(term, weight);
         // TermValue t = null;
         // if (table.containsKey(term))
         // {
         // t = (TermValue) table.get(term);
         // t.value = weight;
         // }
         // else
         // {
         // t = new TermValue(term);
         // t.value = weight;
         // }
         // table.put(term, t);
         // // minValue = (minValue == -1 || minValue > t.value) ? t.value : minValue;
         // // maxValue = (maxValue < t.value) ? t.value : minValue;
      }
   }
}

// public TermTable getSorted (int zoom)
// {
// ArrayList list = getSortedList(zoom);
//
// TermTable tft = new TermTable();
// if (list == null || list.size() == 0)
// {
// return tft;
// }
//
// Term tf = (Term) list.get(list.size() - 1);
//
// Enumeration I = table.keys();
//
// if (I != null)
// {
// while (I.hasMoreElements())
// {
// String k = (String) I.nextElement();
// tf = (Term) table.get(k);
// tft.put(tf.term, tf.value);
// }
// }
//
// return tft;
// }
//
public double getValue(String term)
{
   if(!table.containsKey(term))
   {
      return 0;
   }
   else
   {
      return table.get(term);
   }
}

public double getValue(String term, double dv)
{
   if(!table.containsKey(term))
   {
      return dv;
   }
   else
   {
      return table.get(term);
   }
}

public int size()
{
   return table.size();
}

public boolean hasTerm(String term)
{
   return table.containsKey(term);
}

// /**
// * partitioniere die tabelle abhaengig von den werten
// */
// public Hashtable<String, TermTable> partitionize ()
// {
// Hashtable<String, TermTable> allFractions = new Hashtable<String, TermTable>();
// double htw_avg = getAverageValue();
// int fractionCounter = -1;
// List<TermValue> htwsorted = getSortedListDescending();
// TermTable currentFraction = new TermTable();
// for (int i = 0; i < htwsorted.size(); i++)
// {
// // pr�fen ob weight des n�chsten terms noch in die aktuelle
// // fraktion
// // geh�rt, sonst fractionCounter erh�hen.
// double currentLimit = ((double) (allFractions.size() + 1)) * htw_avg;
// TermValue tw = htwsorted.get(i);
// if (fractionCounter == -1 || tw.getValue() > currentLimit)
// {
// currentFraction = new TermTable();
// fractionCounter++;
// }
// currentFraction.put(tw);
// allFractions.put(String.valueOf(fractionCounter), currentFraction);
// }
// return allFractions;
// }
//
// /**
// * partitioniere die tabelle abhaengig von den werten
// */
// public ArrayList<TermTable> partitionize (TermTable inputTft)
// {
// ArrayList<TermTable> allFractions = new ArrayList<TermTable>();
// double htw_avg = inputTft.getAverageValue();
// List<TermValue> htwsorted = inputTft.getSortedList();
// // if (htwsorted.size() != inputTft.size()) trace.warn("htwsorted.size() !=
// // htw.size()");
// // trace.info("-->avg:" + htw_avg);
// TermTable currentFraction = new TermTable();
// for (int i = htwsorted.size() - 1; i >= 0; i--)
// {
// // pr�fen ob weight des n�chsten terms noch in die aktuelle
// // fraktion geh�rt, sonst fractionCounter erh�hen.
// double currentLimit = ((double) (allFractions.size() + 1)) * htw_avg;
// TermValue tf = (TermValue) htwsorted.get(i);
// TermValue tw = (TermValue) tf.clone();
// if (tw.getValue() > currentLimit)
// {
// if (currentFraction.size() > 0) allFractions.add(currentFraction);
// currentFraction = new TermTable();
// }
// currentFraction.put(tw);
// }
// if (currentFraction.size() > 0) allFractions.add(currentFraction);
// return allFractions;
// }
// Mengenoperationen
/**
 * gibt die schnittmenge der aktuellen und der struktur
 * @other zur�ck
 */
public TermTable intersect(TermTable other)
{
   if(table == null)
      return other;
   if(other == null)
      return this;
   TermTable out = new TermTable();
   Iterator<String> current = keyIterator();
   while(current.hasNext())
   {
      String currKey = (String)current.next();
      if(other.hasTerm(currKey))
      {
         out.add(get(currKey));
         out.add(other.get(currKey));
      }
   }
   return out;
}

/**
 * gibt die vereinigung der aktuellen und der struktur
 * @other zur�ck
 */
public TermTable union(TermTable other)
{
   if(table == null)
      return other;
   if(other == null)
      return this;
   TermTable out = new TermTable();
   Iterator<String> iterator = keyIterator();
   while(iterator.hasNext())
   {
      out.add(get((String)iterator.next()));
   }
   iterator = other.keyIterator();
   while(iterator.hasNext())
   {
      out.add(other.get((String)iterator.next()));
   }
   return out;
}

/**
 * zieht von der aktuellen struktur die struktur
 * @other ab
 */
public TermTable substract(TermTable other)
{
   if(table == null)
      return other;
   if(other == null)
      return this;
   Iterator<String> iterator = other.keyIterator();
   while(iterator.hasNext())
   {
      remove((String)iterator.next());
   }
   return this;
}

/**
 * bewegt identische elemente aus
 * @other in die aktuelle struktur
 */
public TermTable reduce(TermTable other)
{
   if(table == null)
      return other;
   if(other == null)
      return this;
   Iterator<String> iterator = keyIterator();
   while(iterator.hasNext())
   {
      String currKey = (String)iterator.next();
      if(other.hasTerm(currKey))
      {
         TermValue tf = (TermValue)get(currKey);
         add(tf);
         other.remove(currKey);
      }
   }
   return this;
}

public void put(TermValue tf)
{
   put(tf.term, tf.value);
}

// listenoperationen
/**
 * f�hrt eine rechenoperation auf allen elementen aus. die calculatorklasse kann eine condition
 * definieren. sie muss eine calc(long) methode implementieren.
 */
public void recalc(TermTableCalculation calculation, TermTableCalcCondition condition)
{
   for(Iterator<String> iterator = keyIterator(); iterator.hasNext();)
   {
      TermValue tf = get((String)iterator.next());
      if(condition == null || condition.isTrue(tf.term, tf.value))
         tf.value = calculation.calc(tf.term, tf.value);
   }
}

/**
 * f�hrt eine rechenoperation auf allen elementen aus. die calculatorklasse kann eine condition
 * definieren. sie muss eine calc(long) methode implementieren.
 */
// @SuppressWarnings("unchecked")
// public synchronized void remove (TermTableCalcCondition condition)
// {
// Hashtable<String, TermValue> copy = (Hashtable<String, TermValue>) table.clone();
// Enumeration<TermValue> enumeration = copy.elements();
// while (enumeration.hasMoreElements())
// {
// TermValue tf = (TermValue) enumeration.nextElement();
// if (condition.isTrue(tf.term, tf.value)) remove(tf.term);
// }
// copy = null;
// }
public String toString()
{
   int size = size();
   long length = signs();
   long d = length / size;
   StringBuilder sb = new StringBuilder();
   sb.append(this.getClass().getSimpleName());
   sb.append("{");
   sb.append(size);
   sb.append("x");
   sb.append(d);
   sb.append(":");
   sb.append(length);
   sb.append("}");
   return sb.toString();
}

public String report()
{
   StringBuilder sb = new StringBuilder();
   sb.append(this.getClass().getSimpleName());
   sb.append("{");
   sb.append("sum:" + getTotalSum());
   sb.append(", terms:" + size());
   sb.append(", max:" + getMaxValue());
   sb.append(", d:" + getAverageValue());
   sb.append(", min:" + getMinValue());
   sb.append(" [ ");
   for(TermValue tf : getSortedListDescending())
   {
      sb.append(tf.toString(3));
      sb.append(DELIMITER);
      sb.append(" ");
   }
   sb.append("]}");
   return sb.toString();
}

public static String toString(List<TermValue> sortedTerms)
{
   StringBuilder s = new StringBuilder();
   for(Iterator<TermValue> iter = sortedTerms.iterator(); iter.hasNext();)
   {
      TermValue t = (TermValue)iter.next();
      s.append(t.toString(3));
      s.append(";");
   }
   return s.toString();
}

/**
 */
public Iterator<String> keyIterator()
{
   try
   {
      return table.keySet().iterator();
   }
   catch(Exception e)
   {
      return new EmptyIterator<String>();
   }
}

public Set<String> keySet()
{
   if(table == null)
      return new HashSet<String>();
   return table.keySet();
}

public Hashtable<String, Double> getTable()
{
   return table;
}

public void setTable(Hashtable<String, Double> table)
{
   this.table = table;
}

// public void setMaxValue (double maxValue)
// {
// this.maxValue = maxValue;
// }
//
// public void setMinValue (double minValue)
// {
// this.minValue = minValue;
// }
public Object clone()
{
   TermTable tt = new TermTable();
   final Iterator<Map.Entry<String, Double>> iter = table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> e = iter.next();
      tt.add(e.getKey(), e.getValue());
   }
   return tt;
}

public synchronized TermTable copyIfValueExceeds(double limit)
{
   TermTable tt = new TermTable();
   final Iterator<Map.Entry<String, Double>> iter = table.entrySet().iterator();
   while(iter.hasNext())
   {
      Map.Entry<String, Double> e = iter.next();
      if(e.getValue() > limit)
         tt.add(e.getKey(), e.getValue());
   }
   return tt;
}

//public synchronized TermTable copyQuotaOfSize (double quota)
//{
//   int counter = 0;
//   int size = table.size();
//   int max = (int) ((double) size * quota);
//   TermTable tt = new TermTable();
//   for (Enumeration<TermValue> e = this.table.elements(); e.hasMoreElements() && counter++ < max;)
//      tt.add(e.nextElement());
//   final Iterator<Map.Entry<String, Double>> iter = table.entrySet().iterator();
//   while (iter.hasNext())
//   {
//      Map.Entry<String, Double> e = iter.next();
//      if (e.getValue() > limit) tt.add(e.getKey(), e.getValue());
//   }
//   return tt;
//}
public long signs()
{
   long length = 0;
   for(Iterator<String> I = keyIterator(); I.hasNext();)
   {
      String t = I.next();
      length += t.length();
   }
   return length;
}
}
