package ipipan.poliqarp.stat;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Random;

import ipipan.poliqarp.logic.Job;
import ipipan.poliqarp.logic.Match;
import ipipan.poliqarp.logic.MatchList;

/**
 * Match counter, segregating incoming matches into kinds (according
 * to values returned by selectors created with the counter) and
 * counting the number of occurences for each kind (selector value).
 * May contain two additional simpler "subcounters" for queries with
 * complex grouping rules (for example for calculating collocation or
 * dependency tests).
 */
public class MatchCounter extends SimpleMatchCounter implements Job {
   private static final MatchCount[] fakeArray = new MatchCount[0];

   MatchCountComparator sortOrder = null;
   TimeWatch timer;
   int countLimit = 1000;
   int displLimit = 10;
   int occurLimit = 1;
   SimpleMatchCounter left = null, right = null;
   MatchCount[] countArray = null;
   String groupby;
   String leftGroupby, rightGroupby;
   int maxCount = 0;

   public MatchCounter(String gb, Tagset tagset)
   throws StatQueryException {

      timer = new TimeWatch();

      groupby = stripModifiers(gb);

      int i = groupby.indexOf(';');
      if (i != -1) {
         leftGroupby = groupby.substring(0, i).trim();
         left  = new SimpleMatchCounter(leftGroupby, tagset);
         rightGroupby = groupby.substring(i+1).trim();
         right = new SimpleMatchCounter(rightGroupby, tagset);
      } else {
         leftGroupby = groupby;
         rightGroupby = "";
         selector = new Selector(groupby, tagset);
      }
   }

   /**
    * Looks at the given position in the string where a modifier description
    * starts and determines where it ends.
    */
   // added by DJ
   private int modifierEndPosition(String groupby, int start)
   throws StatQueryException
   {
      boolean checkNumber = false, checkAll = false;
      int offset = -1;
      int len = groupby.length();

      if (groupby.regionMatches(start, " display ", 0, 9)) {
         checkNumber = true;
         offset = 9;
      } else if (groupby.regionMatches(start, " min ", 0, 5)) {
         checkNumber = true;
         offset = 5;
      } else if (groupby.regionMatches(start, " count ", 0, 7)) {
         checkNumber = true;
         checkAll = true;
         offset = 7;
      } else if (groupby.regionMatches(start, " sort ", 0, 6)) {
         offset = 6;
      } else {
         throw new StatQueryException();
      } // failed to parse

      offset += start;
      if (checkNumber) {
         if (checkAll && groupby.regionMatches(offset, "all", 0, 3)) {
            return offset + 3;
         }
         while (offset < len && Character.isDigit(groupby.charAt(offset))) {
            offset++;
         }
         return offset;
      }
      // sort modifier, check possible cases
      if (groupby.regionMatches(offset, "a fronte", 0, 8)) {
         return offset + 8;
      }
      if (groupby.regionMatches(offset, "a tergo", 0, 7)) {
         return offset + 7;
      }
      if (groupby.regionMatches(offset, "by ", 0, 3)) {
         offset += 3;
         if (groupby.regionMatches(offset, "freq", 0, 4)) {
            return offset + 4;
         }
         if (groupby.regionMatches(offset, "maxcp", 0, 5)) {
            return offset + 5;
         }
         if (groupby.regionMatches(offset, "dice", 0, 4)) {
            return offset + 4;
         }
         if (groupby.regionMatches(offset, "cp", 0, 2)) {
            return offset + 2;
         }
         if (groupby.regionMatches(offset, "scp bias ", 0, 9)) {
            offset += 9;
            while (offset < len && groupby.charAt(offset) != ' ') {
               offset++;
            }
            return offset;
         }
         if (groupby.regionMatches(offset, "scp", 0, 3)) {
            return offset + 3;
         }
      }
      throw new StatQueryException();
   }

   String stripModifiers(String groupby) throws StatQueryException {
      int i, end;

      i = groupby.indexOf(" display ");
      if (i != -1) {
         end = modifierEndPosition(groupby, i);
         displLimit = Util.safeParseInt(groupby.substring(i + 9, end), displLimit);
         groupby = groupby.substring(0, i) + groupby.substring(end);
      }

      i = groupby.indexOf(" count ");
      if (i != -1) {
         end = modifierEndPosition(groupby, i);
         countLimit = Util.safeParseInt(groupby.substring(i + 7, end), countLimit);
         groupby = groupby.substring(0, i) + groupby.substring(end);
      }

      i = groupby.indexOf(" min ");
      if (i != -1) {
         end = modifierEndPosition(groupby, i);
         occurLimit = Util.safeParseInt(groupby.substring(i + 5, end), occurLimit);
         groupby = groupby.substring(0, i) + groupby.substring(end);
      }

      i = groupby.indexOf(" sort ");
      if (i != -1) {
         end = modifierEndPosition(groupby, i);
         sortOrder = new MatchCountComparator(groupby.substring(i + 6, end));
         groupby = groupby.substring(0, i) + groupby.substring(end);
      }

      return groupby;
   }

   /**
    * Counts single match.
    *
    * @param match a match to count
    * @param matchRef match number on server (may be useful for
    * retrieving sample context and metadata)
    */
   public MatchCount count(Match match, int matchRef) {
      if ((counted+1) % 1000 == 0)
         System.out.println("MatchCounter.count(" + (matchRef+1) + "): " +
            (counted+1));
      if (left == null)
         return super.count(match, matchRef);
      else {
         MatchCount l =  left.count(match, matchRef);
         MatchCount r = right.count(match, matchRef);
         String val = l.getMatch() + "\t" + r.getMatch();
         MatchCount c = count.get(val);
         if (c == null) {
            c = new MatchCount(val, l, r, matchRef);
            count.put(val, c);
         } else
            c.inc();

         counted ++;
         return c;
      }
   }

   public void count(MatchList matches) {
      Match m;
      int lastMatch = 0;
      int nMatches = matches.count();
      long mtime = 0, stime = 0;

      timer.display("MatchCounter.start(): ");

      try {
         if (countLimit >= nMatches - lastMatch) {
            countLimit = -1;
         }

         if (countLimit < 0) {
            maxCount = nMatches;
            for (; lastMatch < nMatches; lastMatch ++) {
               stime += timer.getInterval();
               if (lastMatch % 1000 == 0)
                  matches.prefetchMatches(lastMatch, lastMatch + 1000);
               m = matches.getMatch(lastMatch);
               mtime += timer.getInterval();
               count(m, lastMatch);
            }
         } else {
            Random r = new Random();
            maxCount = countLimit;
            for (; countLimit > 0; countLimit --) {
               lastMatch += r.nextInt((nMatches-lastMatch)/countLimit) + 1;
               if (lastMatch >= nMatches)
                  lastMatch = nMatches - 1;
               stime += timer.getInterval();
               m = matches.getMatch(lastMatch);
               mtime += timer.getInterval();
               count(m, lastMatch);
            }
         }
         System.out.println("MatchCounter.count(): mTime: " +
            mtime + " ms; sTime: " + stime + " ms");
         timer.seset();
         toArray();

      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public void toArray() {
      if (occurLimit > 1) {
         ArrayList<MatchCount> l = new ArrayList<MatchCount>();
         for (MatchCount mc : count.values()) {
            if (mc.getCount() >= occurLimit)
               l.add(mc);
         }
         countArray = l.toArray(fakeArray);
      } else {
         countArray = count.values().toArray(fakeArray);
      }

      if (sortOrder != null) {
         Arrays.sort(countArray, sortOrder);
         timer.display("MatchCounter.sort(): ");
      }
   }

   public String[] getCount(int i) {
      return countArray[i].toStringArray();
   }

   public Match getCells(int i) {
      return new Match(countArray[i].toCellArray());
   }

   public String[] getColumnNames() {
      String[] res = null;
      if (right == null) {
         res = new String[2];
         res[0] = leftGroupby;
         res[1] = "c(" + groupby + ")";
         return res;
      }
      if (sortOrder != null && !sortOrder.getTestName().equals(""))
         res = new String[6];
      else
         res = new String[5];
      res[0] = leftGroupby;
      res[1] = rightGroupby;
      res[2] = (left != null) ? "c(" + leftGroupby + ")" : "";
      res[3] = (right != null) ? "c(" + rightGroupby + ")" : "";
      res[4] = "c(" + groupby + ")";
      if (sortOrder != null && !sortOrder.getTestName().equals(""))
         res[5] = sortOrder.getTestName();
      return res;
   }

   public int getMatchRef(int i) {
      return countArray[i].getMatchRef();
   }

   public int count() {
      return countArray != null ? countArray.length : count.size();
   }

   public void print() {
      int sum = 0;

      if (displLimit > countArray.length || displLimit < 0)
         displLimit = countArray.length;

      for (int i = 0; i < displLimit; i++) {
         System.out.println(countArray[i].toString());
         sum += countArray[i].getCount();
      }
      System.out.println("RAZEM: " + sum + " (" + counted + ")");
   }

   public int getProgress() {
      return maxCount > 0 ? counted*100/maxCount : 0;
   }

   public void sort(int column, boolean ascending)
   {
      Arrays.sort(countArray, new MatchCountGuiComparator(column, ascending));
   }
}
