package basics.collections.index;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import basics.collections.index.HashedCharacterTree.IHashNode;
import basics.collections.index.XHashedCharacterTree.XHashNode;

/**
 * Provides an index an a list of features.
 * @author ks
 *
 * @param <D>
 */
public class MultiIndexList<T extends Indexable>
{
List<T>              indexedList        = new ArrayList<T>();
HashedCharacterTree  primaryIndex       = new HashedCharacterTree();
XHashedCharacterTree secondaryIndexes[] = null;

/**
 * Creates a multiple index list.
 * A multiple index list consists of a list with entries,
 * a primary index that has unique keys,
 * and a set of secondary index that are not necessarily unique.
 * @param n number of secondary indexes.
 */
public MultiIndexList(int n)
{
   secondaryIndexes = new XHashedCharacterTree [n];
   for(int i = 0; i < n; i++)
      secondaryIndexes[i] = new XHashedCharacterTree();
}

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

public synchronized void clear()
{
   int n = secondaryIndexes.length;
   //
   secondaryIndexes = null;
   primaryIndex = null;
   indexedList = null;
   //
   secondaryIndexes = new XHashedCharacterTree [n];
   for(int i = 0; i < n; i++)
      secondaryIndexes[i] = new XHashedCharacterTree();
   //
   primaryIndex = new HashedCharacterTree();
   //
   indexedList = new ArrayList<T>();
}

public Object applyFunctor(IndexableFunctor<T> functor, boolean changeT)
{
   if(changeT)
      for(int i = 0; i < indexedList.size(); i++)
         indexedList.set(i, functor.apply(indexedList.get(i)));
   else
      for(int i = 0; i < indexedList.size(); i++)
         functor.apply(indexedList.get(i));
   return functor;
}

/**
 * Check trie if key exists.
 * @param key key to search
 * @return true if found
 */
public boolean contains(int i, String key)
{
   return secondaryIndexes[i].retrieve(key).size() > 0;
}

public boolean contains(String key)
{
   return primaryIndex.retrieve(key) > -1;
}

/**
 * Check trie if key exists, and return the index position stored with the node.
 * @param key key to search
 * @return -1 if not found, or a index position for retrieving the associated datarow.
 */
public synchronized Set<Integer> indexOf(int i, String key)
{
   return secondaryIndexes[i].retrieve(key);
}

public synchronized int indexOf(String key)
{
   return primaryIndex.retrieve(key);
}

public synchronized void removeKey(String key)
{
   int x = primaryIndex.retrieve(key);
   if(x > -1)
      primaryIndex.remove(key);
}

public synchronized void removeListEntry(int i)
{
   indexedList.remove(i);
}

public synchronized int getIndexEntry(String key){
	
	return primaryIndex.retrieve(key);
}

public synchronized int enshure(T indexable)
{
   int freepos = indexedList.size();
   int x = primaryIndex.enshure(indexable._key, freepos);
   if(x == freepos)
      indexedList.add(indexable);
   if(indexable._index != x)
      indexable._index = x;
   return x;
}

public synchronized void addKey(int indexPos, int secondaryKey, String secondaryKeyValue)
{
   secondaryIndexes[secondaryKey].enshure(secondaryKeyValue, indexPos);
}

public synchronized int addKey(T indexable, int secondaryKey, String secondaryKeyValue)
{
   int x = enshure(indexable);
   secondaryIndexes[secondaryKey].enshure(secondaryKeyValue, x);
   return x;
}

public T get(String key)
{
   return get(indexOf(key));
}

public List<T> get(int i, String key)
{
   List<T> result = new ArrayList<T>();
   Set<Integer> set = indexOf(i, key);
   if(set != null)
      for(Iterator<Integer> iter = set.iterator(); iter.hasNext();)
         result.add(get(iter.next()));
   return result;
}

public T get(int x)
{
   return x > -1 && x < indexedList.size() ? indexedList.get(x) : null;
}

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

public synchronized Iterator<String> pkIterator()
{
   final Iterator<T> iter = iterator();
   return new Iterator<String>()
   {
      public boolean hasNext()
      {
         return iter.hasNext();
      }

      public String next()
      {
         return iter.next()._key;
      }

      public void remove()
      {
      }
   };
}

public List<Match<T>> retrieveFuzzy(String key, double likeness, boolean searchPK,
   int searchFKFields[])
{
   Collector resultCollector = new Collector();
   if(searchPK)
      primaryIndex.retrieveFuzzy(resultCollector, (IHashNode)primaryIndex.getRoot(), key, likeness);
   if(searchFKFields != null)
      for(int searchNdx : searchFKFields)
      {
         XHashedCharacterTree xtree = secondaryIndexes[searchNdx];
         xtree.retrieveFuzzy(resultCollector, (XHashNode)xtree.getRoot(), key, likeness);
      }
   Map<Integer, Match<Integer>> rawmap = resultCollector.getResults();
   List<Match<T>> results = new ArrayList<Match<T>>();
   for(Iterator<Integer> iter = rawmap.keySet().iterator(); iter.hasNext();)
   {
      int i = iter.next();
      Match<Integer> m = rawmap.get(i);
      T t = get(m.content);
      if(likeness <= m.quality)
      {
         Match<T> mw = new Match<T>(t, m.quality);
         results.add(mw);
      }
   }
   Collections.sort(results, new Comparator<Match<T>>()
   {
      public int compare(Match<T> mw1, Match<T> mw2)
      {
         Double v1 = mw1.quality;
         Double v2 = mw2.quality;
         return v2.compareTo(v1);
      }
   });
   return results;
}

public List<T> copyList()
{
   List<T> newlist = new ArrayList<T>();
   for(T xd : indexedList)
      newlist.add(xd);
   return newlist;
}

public List<T> getValueList()
{
   return Collections.unmodifiableList(indexedList);
}

public static void unittest()
{
   Indexable fd = null;
   //   MultiIndexList<MultiLang> f = new MultiIndexList<MultiLang>(2);
   //      f.set(new MultiLang("hallo", "de"), new String [] { "hi" });
   //      f.set(new MultiLang("hello", "en"), new String [] { "hi" });
   //      f.set(new MultiLang("haus", "de"), new String [] { "how" });
   //      f.set(new MultiLang("house", "en"), new String [] { "how" });
   //      fd = f.get("hallo");
   //      Test.assertNotNull(fd, "missing hallo");
   //      Test.printout("%", fd.toString());
   //      List<MultiLang> list = f.get(0, "hi");
   //      Test.assertNotNull(fd, "missing hi");
   //      Test.printout("hi: % %", list.size(), SUtl.join(list, ","));
   //      fd = f.get("haus");
   //      Test.assertNotNull(fd, "missing haus");
   //      Test.printout("%", fd.toString());
   //      fd = f.get("house");
   //      Test.assertNotNull(fd, "missing house");
   //      Test.printout("%", fd.toString());
   //      list = f.get(0, "how");
   //      Test.printout("how: % %", list.size(), SUtl.join(list, ","));
   //      f.applyFunctor(new IndexableFunctor<MultiLang>()
   //      {
   //   
   //         public MultiLang apply(MultiLang fd)
   //         {
   //            fd.add("gr");
   //            return fd;
   //         }
   //      });
   //      fd = f.get("hallo");
   //      Test.assertNotNull(fd, "missing hallo");
   //      Test.printout("%", fd.toString());
   //      list = f.get(0, "hi");
   //      Test.printout("hi: % %", list.size(), SUtl.join(list, ","));
   //      fd = f.get("house");
   //      Test.assertNotNull(fd, "missing house");
   //      Test.printout("%", fd.toString());
}
}
