/* 
 * Pescetti Pseudo-Duplimate Generator
 * 
 * Copyright (C) 2007 Matthew Johnson
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License Version 2 as published by
 * the Free Software Foundation.  This program 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 General Public License for more details.  You should have received a
 * copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 *
 * To Contact me, please email src@matthew.ath.cx
 *
 */

package cx.ath.matthew.pescetti;

import cx.ath.matthew.debug.Debug;

import static cx.ath.matthew.pescetti.pescetti.count;
import static cx.ath.matthew.pescetti.pescetti.length;
import static cx.ath.matthew.pescetti.pescetti.bacon;

import java.util.HashMap;
import java.util.Random;

class WeirdShape extends Criteria
{
	private double[] probabilities = new double[]
								{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
	private Random r = new Random();
	private boolean check(int n, double w)
	{
		return w < probabilities[n];
	}
	private void frob(int n)
	{
		for (int i = 0; i < probabilities.length; i++)
			if (i == n)
				probabilities[i] -= 0.17;
			else
				probabilities[i] += 0.04;
	}
   public boolean match(byte[][] board)
   {
		double cw = ((double) r.nextInt(1073741824)) / 1073741824.0;
		
		if (check(0, cw))
      for (int p = 0; p < 4; p++)
         for (int s = 0; s < 4; s++)
            if (length(board[p], s) >= 8) {
               if (Debug.debug) Debug.print(Debug.INFO, "8 card suit");
					frob(0);
               return true;
            }

		if (check(1, cw))
      for (int s = 0; s < 4; s++) {
         if (length(board[NORTH], s) +
               length(board[SOUTH], s) >= 12) {
            if (Debug.debug) Debug.print(Debug.INFO, "12 card fit");
				frob(1);
            return true;
         }
         if (length(board[WEST], s) +
               length(board[EAST], s) >= 12) {
            if (Debug.debug) Debug.print(Debug.INFO, "12 card fit");
				frob(1);
            return true;
         }
      }

		if (check(2, cw))
      for (int p = 0; p < 4; p++)
         for (int s1 = 0; s1 < 4; s1++)
            for (int s2 = s1+1; s2 < 4; s2++)
               if (length(board[p], s1) <= 1 &&
                     length(board[p], s2) <= 1) {
                  if (Debug.debug) Debug.print(Debug.INFO, "double splinter");
						frob(2);
                  return true;
               }

		if (check(3, cw)) {
			boolean ns11 = false;
			boolean ew11 = false;
			for (int s1 = 0; s1 < 4; s1++) {
				if ((length(board[NORTH], s1) + 
							length(board[SOUTH], s1)) >= 11) ns11=true;
				if ((length(board[EAST], s1) + 
							length(board[WEST], s1)) >= 11) ew11=true;
			}
			if (ns11 && ew11) {
				if (Debug.debug) Debug.print(Debug.INFO, "11 card fit both ways");
				frob(3);
				return true;
			}
		}

		if (check(4, cw))
      for (int s1 = 0; s1 < 4; s1++)
         for (int s2 = s1+1; s2 < 4; s2++) {
            if (length(board[WEST], s1) + 
                  length(board[EAST], s1) +
                  length(board[WEST], s2) +
                  length(board[EAST], s2) >= 19) {
               if (Debug.debug) Debug.print(Debug.INFO, "19 card double fit");
					frob(4);
               return true;
            }
            if (length(board[NORTH], s1) + 
                  length(board[SOUTH], s1) +
                  length(board[NORTH], s2) +
                  length(board[SOUTH], s2) >= 19) {
               if (Debug.debug) Debug.print(Debug.INFO, "19 card double fit");
					frob(4);
               return true;
            }
         }
		if (check(5, cw))
      for (int s = 0; s < 4; s++) {
         if (length(board[NORTH], s) >= 4 &&
					length(board[NORTH], s) <= 6 &&
               length(board[SOUTH], s) == 0) {
            if (Debug.debug) Debug.print(Debug.INFO, "bad split");
				frob(5);
            return true;
         }
         if (length(board[SOUTH], s) >= 4 &&
					length(board[SOUTH], s) <= 6 &&
               length(board[NORTH], s) == 0) {
            if (Debug.debug) Debug.print(Debug.INFO, "bad split");
				frob(5);
            return true;
         }
         if (length(board[EAST], s) >= 4 &&
					length(board[EAST], s) <= 6 &&
               length(board[WEST], s) == 0) {
            if (Debug.debug) Debug.print(Debug.INFO, "bad split");
				frob(5);
            return true;
         }
         if (length(board[WEST], s) >= 4 &&
					length(board[WEST], s) <= 6 &&
               length(board[EAST], s) == 0) {
            if (Debug.debug) Debug.print(Debug.INFO, "bad split");
				frob(5);
            return true;
         }
      }
      return false;
   }
}
class Bacon extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (bacon(hand)) 
            return true;
      return false;
   }
}
class Unbalanced extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (balanced(hand)) 
            return false;
      return true;
   }
}
class WeakNT extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (balanced(hand) && count(hand) >= 12 && count(hand) <= 14) 
            return true;
      return false;
   }
}
class StrongNT extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (balanced(hand) && count(hand) >= 15 && count(hand) <= 17) 
            return true;
      return false;
   }
}
class TwoNT extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (semibalanced(hand) && count(hand) >= 20 && count(hand) <= 22) 
            return true;
      return false;
   }
}
class WeakTwo extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if ((length(hand, SPADES) == 6 || length(hand, HEARTS) == 6)
               && count(hand) >= 6 && count(hand) <= 10) 
            return true;
      return false;
   }
}
class StrongTwo extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if ((length(hand, SPADES) >= 6 || length(hand, HEARTS) >= 6 || length(hand, DIAMONDS) >= 6)
               && count(hand) >= 18 && count(hand) <= 22) 
            return true;
      return false;
   }
}
class Three extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (longest(hand) >= 7
               && count(hand) >= 6 && count(hand) <= 10) 
            return true;
      return false;
   }
}
class TwoClubs extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (count(hand) >= 23) 
            return true;
      return false;
   }
}
class FFFOne extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board)
         if (shortest(hand) == 1 && longest(hand) == 4) 
            return true;
      return false;
   }
}
class SingleSuited extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board) {
         int six = 0;
         for (int s = 0; s < 4; s++)
            if (length(hand, s) >= 6) six++;
         if (six == 1) return true;
      }
      return false;
   }
}
class TwoSuited extends Criteria
{
   public boolean match(byte[][] board)
   {
      for (byte[] hand: board) {
         int five = 0;
         for (int s = 0; s < 4; s++)
            if (length(hand, s) >= 5) five++;
         if (five >= 2) return true;
      }
      return false;
   }
}
class PartScore extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (combinedcount(board[NORTH], board[SOUTH]) >= 17 &&
          combinedcount(board[NORTH], board[SOUTH]) <= 23 &&
          fit(board[NORTH], board[SOUTH]) >= 8)
         return true;
      if (combinedcount(board[EAST], board[WEST]) >= 17 &&
          combinedcount(board[EAST], board[WEST]) <= 23 &&
          fit(board[EAST], board[WEST]) >= 8)
         return true;
      return false;
   }
}
class Game extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (combinedcount(board[NORTH], board[SOUTH]) >= 26 &&
          fit(board[NORTH], board[SOUTH]) >= 8)
         return true;
      if (combinedcount(board[EAST], board[WEST]) >= 26 &&
          fit(board[EAST], board[WEST]) >= 8)
         return true;
      return false;
   }
}
class GrandSlam extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (combinedcount(board[NORTH], board[SOUTH]) >= 37 &&
          fit(board[NORTH], board[SOUTH]) >= 9)
         return true;
      if (combinedcount(board[EAST], board[WEST]) >= 37 &&
          fit(board[EAST], board[WEST]) >= 9)
         return true;
      return false;
   }
}
class Slam extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (combinedcount(board[NORTH], board[SOUTH]) >= 33 &&
          fit(board[NORTH], board[SOUTH]) >= 9)
         return true;
      if (combinedcount(board[EAST], board[WEST]) >= 33 &&
          fit(board[EAST], board[WEST]) >= 9)
         return true;
      return false;
   }
}
class GameInvite extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (combinedcount(board[NORTH], board[SOUTH]) >= 23 &&
          combinedcount(board[NORTH], board[SOUTH]) <= 27 &&
          fit(board[NORTH], board[SOUTH]) >= 8)
         return true;
      if (combinedcount(board[EAST], board[WEST]) >= 23 &&
          combinedcount(board[EAST], board[WEST]) <= 27 &&
          fit(board[EAST], board[WEST]) >= 8)
         return true;
      return false;
   }
}
class SlamInvite extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (combinedcount(board[NORTH], board[SOUTH]) >= 30 &&
          combinedcount(board[NORTH], board[SOUTH]) <= 33 &&
          fit(board[NORTH], board[SOUTH]) >= 8)
         return true;
      if (combinedcount(board[EAST], board[WEST]) >= 30 &&
          combinedcount(board[EAST], board[WEST]) <= 33 &&
          fit(board[EAST], board[WEST]) >= 8)
         return true;
      return false;
   }
}
class JumpShift extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (opener(board[NORTH]) 
            && count(board[SOUTH]) >= 16
            && longest(board[SOUTH]) >= 5) {
         for (int s = 0; s < 4; s++)
            if (length(board[NORTH], s) >= 4 && length(board[SOUTH], s) >= 4)
               return false;
         return true;
      }
      if (opener(board[SOUTH]) 
            && count(board[NORTH]) >= 16
            && longest(board[NORTH]) >= 5) {
         for (int s = 0; s < 4; s++)
            if (length(board[NORTH], s) >= 4 && length(board[SOUTH], s) >= 4)
               return false;
         return true;
      }
      if (opener(board[EAST]) 
            && count(board[WEST]) >= 16
            && longest(board[WEST]) >= 5) {
         for (int s = 0; s < 4; s++)
            if (length(board[WEST], s) >= 4 && length(board[EAST], s) >= 4)
               return false;
         return true;
      }
      if (opener(board[WEST]) 
            && count(board[EAST]) >= 16
            && longest(board[EAST]) >= 5) {
         for (int s = 0; s < 4; s++)
            if (length(board[WEST], s) >= 4 && length(board[EAST], s) >= 4)
               return false;
         return true;
      }

      return false;
   }
}
class JumpFit extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (opener(board[NORTH]) 
            && count(board[SOUTH]) >= 12) {
         for (int s = 0; s < 4; s++)
            if (length(board[NORTH], s) >= 4 && length(board[SOUTH], s) >= 4)
               return true;
         return false;
      }
      if (opener(board[EAST]) 
            && count(board[WEST]) >= 12) {
         for (int s = 0; s < 4; s++)
            if (length(board[EAST], s) >= 4 && length(board[WEST], s) >= 4)
               return true;
         return false;
      }
      return false;
   }
}
class Splinter extends Criteria
{
   public boolean match(byte[][] board)
   {
      if (opener(board[NORTH]) 
            && count(board[SOUTH]) >= 16
            && shortest(board[SOUTH]) < 2) {
         for (int s = 0; s < 4; s++)
            if (length(board[NORTH], s) >= 4 && length(board[SOUTH], s) >= 4)
               return true;
         return false;
      }
      if (opener(board[SOUTH]) 
            && count(board[NORTH]) >= 16
            && shortest(board[NORTH]) < 2) {
         for (int s = 0; s < 4; s++)
            if (length(board[NORTH], s) >= 4 && length(board[SOUTH], s) >= 4)
               return true;
         return false;
      }
      if (opener(board[EAST]) 
            && count(board[WEST]) >= 16
            && shortest(board[WEST]) < 2) {
         for (int s = 0; s < 4; s++)
            if (length(board[EAST], s) >= 4 && length(board[WEST], s) >= 4)
               return true;
         return false;
      }
      if (opener(board[WEST]) 
            && count(board[EAST]) >= 16
            && shortest(board[EAST]) < 2) {
         for (int s = 0; s < 4; s++)
            if (length(board[EAST], s) >= 4 && length(board[WEST], s) >= 4)
               return true;
         return false;
      }
      return false;
   }
}

public abstract class Criteria implements Constants
{
   public static HashMap<Criteria, Double> parseCriteria(String criteria) throws CriteriaException
   {
      String[] clist = criteria.split(" ");
		HashMap<Criteria, Double> cs = new HashMap<Criteria, Double>();
      int i = 0;
      for (String d: clist) {
         if (Debug.debug) Debug.print(Debug.DEBUG, "new criteria: "+d);
			String[] ds = d.split(":");
         String c = ds[0].toLowerCase();
			double p = 1.0;
			if (ds.length > 1)
				p = Double.parseDouble(ds[1]);
			
         if (c.equals("unbalanced")) cs.put(new Unbalanced(), p);
         else if (c.equals("weaknt")) cs.put(new WeakNT(), p);
         else if (c.equals("strongnt")) cs.put(new StrongNT(), p);
         else if (c.equals("twont")) cs.put(new TwoNT(), p);
         else if (c.equals("weaktwo")) cs.put(new WeakTwo(), p);
         else if (c.equals("strongtwo")) cs.put(new StrongTwo(), p);
         else if (c.equals("three")) cs.put(new Three(), p);
         else if (c.equals("twoclubs")) cs.put(new TwoClubs(), p);
         else if (c.equals("4441")) cs.put(new FFFOne(), p);
         else if (c.equals("singlesuit")) cs.put(new SingleSuited(), p);
         else if (c.equals("twosuits")) cs.put(new TwoSuited(), p);
         else if (c.equals("partscore")) cs.put(new PartScore(), p);
         else if (c.equals("game")) cs.put(new Game(), p);
         else if (c.equals("slam")) cs.put(new Slam(), p);
         else if (c.equals("grand")) cs.put(new GrandSlam(), p);
         else if (c.equals("game-invite")) cs.put(new GameInvite(), p);
         else if (c.equals("slam-invite")) cs.put(new SlamInvite(), p);
         else if (c.equals("jumpshift")) cs.put(new JumpShift(), p);
         else if (c.equals("jumpfit")) cs.put(new JumpFit(), p);
         else if (c.equals("splinter")) cs.put(new Splinter(), p);
         else if (c.equals("bacon")) cs.put(new Bacon(), p);
         else if (c.equals("weird")) cs.put(new WeirdShape(), p);
         else
            throw new CriteriaException("Unknown criteria: "+c);
      }
      return cs;
   }
   public abstract boolean match(byte[][] board);
   public boolean opener(byte[] hand)
   {
      return 12 <= count(hand);
   }
	@SuppressWarnings("fallthrough")
   public boolean semibalanced(byte[] hand)
   {
      int xton = 0;
      for (int s = 0; s < 4; s++) {
         switch (length(hand, s)) {
            case 2:
               if (xton == 2) return false;
               else xton++;
            case 1:
            case 0:
               return false;
         }
      }
      return true;
   }
	@SuppressWarnings("fallthrough")
   public boolean balanced(byte[] hand)
   {
      boolean xton = false;
      for (int s = 0; s < 4; s++) {
         switch (length(hand, s)) {
            case 2:
               if (xton) return false;
               else xton = true;
            case 1:
            case 0:
               return false;
         }
      }
      return true;
   }
   public int shortest(byte[] hand)
   {
      int shortest = 14;
      for (int s = 0; s < 4; s++)
         if (length(hand, s) < shortest)
            shortest = length(hand, s);
      return shortest;
   }
   public int longest(byte[] hand)
   {
      int longest = 0;
      for (int s = 0; s < 4; s++)
         if (length(hand, s) > longest)
            longest = length(hand, s);
      return longest;
   }
   public int fit(byte[] hand1, byte[] hand2)
   {
      int fit = 0;
      for (int s = 0; s < 4; s++) {
         int sum = length(hand1, s) + length(hand2, s);
         if (sum > fit)
            fit = sum;
      }
      return fit;
   }
   public int combinedcount(byte[] hand1, byte[] hand2)
   {
      return count(hand1)+count(hand2);
   }
}

