import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.text.NumberFormat;

public class MonteCarlo implements ChangeListener, ActionListener
{
    CommunicatorInstance CI;
    JFrame MC_FRAME = null;
    boolean WORKING = false;
    JSpinner CPU_SPINNER, DEPTH_SPINNER, LENGTH_SPINNER;
    JSpinner VERBOSE_SPINNER, MAX_SPINNER, MIN_SPINNER;
    JButton ON_OFF;
    Object[][] DATA;
    Long[] EVAL;
    JTable TABLE;
    int[] SORT;
    int LEGAL_COUNT;
    long UP_DATE;

    public MonteCarlo (CommunicatorInstance ci)
    {
	super ();
	CI = ci;
	EVAL = new Long[256];
	SORT = new int[256];
	for (int i = 0; i < 256; i++)
	    EVAL[i] = new Long (0);
	for (int i = 0; i < 256; i++)
	    SORT[i] = i;
	LEGAL_COUNT = CI.COMM.CF.BOARD_PANEL.POS.COUNT_OF_LEGAL_MOVES;
	UP_DATE = new Date ().getTime ();
	StartMonteCarlo ();
    }

    public void GoMonteCarlo ()
    {
	if (CI.on)
	    CI.SendHalt ();
	ON_OFF.setBackground (Color.green);
	ON_OFF.repaint ();
        CI.SendTo (CI.GetFenMoves (), false);
	String S = "go montecarlo";
	S += " length " + LENGTH_SPINNER.getValue ();
	S += " depth " + DEPTH_SPINNER.getValue ();
	S += " cpus " + CPU_SPINNER.getValue ();
	S += " verbose vlength " + VERBOSE_SPINNER.getValue ();
	S += " max " + MAX_SPINNER.getValue ();
	S += " min " + MIN_SPINNER.getValue ();
	S += " moves";
	BoardPosition BP = CI.COMM.CF.BOARD_PANEL.POS;
	for (int i = 0; i < BP.COUNT_OF_LEGAL_MOVES; i++)
	    if ((Boolean) (DATA[i][0]))
		S += " " + BP.GetDirect (BP.move_list[i]);
	// System.out.println (S);
	CI.SendTo (S, true);
	WORKING = true;
    }

    public void HaltMonteCarlo ()
    {
	if (!WORKING)
	    return;
	CI.SendTo ("stop", false);
	CI.SendTo ("isready", true);
	ON_OFF.setBackground (Color.red);
	ON_OFF.repaint ();
	CI.WaitForThroughPut ("readyok", -1, false);
	WORKING = false;
    }

    public void stateChanged (ChangeEvent chg_evt)
    {
	if (WORKING)
	    HaltMonteCarlo ();
    }

    public void actionPerformed (ActionEvent act_evt)
    {
	if (WORKING)
	    HaltMonteCarlo ();
	else
	    GoMonteCarlo ();
    }

    
    public JSpinner GetSpinner (String S, int def, int min, int max, Box BOX)
    {
	Box B = new Box (BoxLayout.X_AXIS);
	JLabel L = new JLabel (S + ":  ");
	L.setBackground (new Color (205, 195, 235));
	B.add (L);
	SpinnerNumberModel MODEL = new SpinnerNumberModel (def, min, max, 1);
	JSpinner J = new JSpinner (MODEL);
	J.setName (S);
	J.addChangeListener (this); // HACK
	J.setPreferredSize (new Dimension (80, 16));
	J.setMaximumSize (new Dimension (80, 16));
	B.add (J);
	JLabel M = new JLabel ("  min " + min + " max " + max);
	M.setFont (new Font ("Monospaced", 0, 12));
	B.add (M);
	B.setAlignmentX (0.05f);
	BOX.add (B);
	return J;
    }

    public class RenderFloat extends DefaultTableCellRenderer
    {
	public RenderFloat ()
	{
	    super ();
	    setHorizontalAlignment(SwingConstants.RIGHT);  
	}
	public void setValue (Object obj)
	{
	    String S = String.format ("%03.3g%%", (Float) obj);
	    super.setValue (S);
	}
    }

    public class RenderDouble extends DefaultTableCellRenderer // HACK
    {
	public RenderDouble ()
	{
	    super ();
	    setHorizontalAlignment(SwingConstants.RIGHT);  
	}
	public void setValue (Object obj)
	{
	    Double D = (Double) obj / 100.0d;
	    if (D > 99.9d)
		D = 99.9d;
	    if (D < -99.9d)
		D = -99.9d;
	    String S = String.format ("%0+3.2f", D);
	    super.setValue (S);
	}
    }

    public void DoFrameMC (JFrame F)
    {
	Box BOX = new Box (BoxLayout.Y_AXIS);	
	ON_OFF = new JButton ("On/Off");
	ON_OFF.setBackground (Color.red);
	ON_OFF.addActionListener (this);
	BOX.add (ON_OFF);
	CPU_SPINNER = GetSpinner ("CPUs", 1, 1, 64, BOX);
	DEPTH_SPINNER = GetSpinner ("Depth", 10, 5, 20, BOX);
	LENGTH_SPINNER = GetSpinner ("Length", 65535, 5, 65535, BOX);
	VERBOSE_SPINNER = GetSpinner ("VerboseLength", 10, 0, 65535, BOX);
	MAX_SPINNER = GetSpinner ("MaxValue", 10000, -9000, 10000, BOX);
	MIN_SPINNER = GetSpinner ("MinValue", -10000, -10000, 9000, BOX);
	TABLE = new JTable (new TableModel (CI.COMM.CF.BOARD_PANEL.POS));
	TABLE.setDefaultRenderer (Float.class, new RenderFloat ());
	TABLE.setDefaultRenderer (Double.class, new RenderDouble ());
	JScrollPane JSP = new JScrollPane (TABLE);
	BOX.add (JSP);
	F.add (BOX);
    }

    public class TableModel extends AbstractTableModel
    {
	String[] NAMES = { "on", "Move", "Trials", "Wins", "Loss", "Draw", "Eval", "Perc" };
	int row_count;
	public TableModel (BoardPosition BP)
	{
	    BP.MakeNormal (); // ensure
	    row_count = BP.COUNT_OF_LEGAL_MOVES;
	    DATA = new Object[BP.COUNT_OF_LEGAL_MOVES][8];
	    for (int i = 0; i < BP.COUNT_OF_LEGAL_MOVES; i++)
		{
		    DATA[i][0] = new Boolean (false);
		    DATA[i][1] = new String (BP.move_list_annotated[i]);
		    DATA[i][2] = new Integer (0);
		    DATA[i][3] = new Integer (0);
		    DATA[i][4] = new Integer (0);
		    DATA[i][5] = new Integer (0);
		    DATA[i][6] = new Double (0);
		    DATA[i][7] = new Float (0);
		}	    

	    addTableModelListener (new TableModelListener ()
		{
		    public void tableChanged (TableModelEvent tab_mod_evt)
		    {
			if (WORKING)
			    {
				HaltMonteCarlo ();
				SwingUtilities.invokeLater (new Runnable ()
				    {
					public void run ()
					{
					    GoMonteCarlo ();
					}
				    }
				    );
			    }
		    }
		}
		);
	}

        public Object getValueAt (int row, int col) { return DATA[SORT[row]][col]; }
	public String getColumnName (int col) { return NAMES[col]; }
	public int getColumnCount() { return NAMES.length; }
	public int getRowCount() { return row_count; }
	public Class getColumnClass (int c) { return getValueAt(0, c).getClass(); }
	public boolean isCellEditable (int row, int col) { return (col == 0); }
	public void setValueAt (Object obj, int row, int col) { DATA[SORT[row]][col] = obj; fireTableDataChanged ();}
    }

    public void StartMonteCarlo ()
    {
	MC_FRAME = new JFrame ("MonteCarlo " + CI.COMM.id);
	DoFrameMC (MC_FRAME);
	MC_FRAME.addWindowListener (new WindowAdapter ()
	    {
		public void windowClosing (WindowEvent win_evt)
		{
		    HaltMonteCarlo ();
		    CI.MONTE_CARLO = null;
		}
	    }
	    );
	MC_FRAME.setBackground (Color.lightGray);
	MC_FRAME.pack ();
	MC_FRAME.setSize (420, 300); // demand
	MC_FRAME.setResizable (false);
	MC_FRAME.setVisible (true);
    }

    public void ReSort ()
    {
	int[] PRIORITY = new int[LEGAL_COUNT];
	int[] SWIVEL = new int[LEGAL_COUNT];
	for (int i = 0; i < LEGAL_COUNT; i++)
	    {
		SWIVEL[i] = i;
		PRIORITY[i] = ((Boolean) DATA[i][0]) ? 1000000000 : 0;
		PRIORITY[i] += (Integer) DATA[i][2] * 1; // Trials
		PRIORITY[i] += (int) ((Double) DATA[i][6] * 1000.0d); // eval (cp)
		PRIORITY[i] += (int) ((Float) DATA[i][7] * 10000000.0d); // win perc
	    }
	for (int j = LEGAL_COUNT - 1; j >= 0; j--)
	    {
		int P = PRIORITY[j];
		int S = SWIVEL[j];
		for (int i = j + 1; i < LEGAL_COUNT; i++)
		    {
			if (P < PRIORITY[i])
			    {
				PRIORITY[i - 1] = PRIORITY[i];
				SWIVEL[i - 1] = SWIVEL[i];
			    }
			else
			    {
				PRIORITY[i - 1] = P;
				SWIVEL[i - 1] = S;
				break;
			    }			    
		    }	
	    }		
	for (int i = 0; i < LEGAL_COUNT;i++)
	    SORT[i] = SWIVEL [i];
    }

    public void ResultMC (String S)
    {
	BoardPosition BP = CI.COMM.CF.BOARD_PANEL.POS;
	StringTokenizer ST = new StringTokenizer (S);
	String MOVE = ST.nextToken ();
	Long VALUE = Long.valueOf (ST.nextToken ());
	int i;
	for (i = 0; i < BP.COUNT_OF_LEGAL_MOVES; i++)
	    if ((Boolean) (DATA[i][0]) &&
		MOVE.equals (BP.GetDirect (BP.move_list[i])))
		break;
	if (i >= BP.COUNT_OF_LEGAL_MOVES)
	    return;
	DATA[i][2] = (Integer) DATA[i][2] + 1; // Trials
	EVAL[i] += VALUE;
	DATA[i][6] = ((double) EVAL[i]) / ((double) (Integer) (DATA[i][2]));
	if (VALUE > (Integer) MAX_SPINNER.getValue ())
	    DATA[i][3] = (Integer) DATA[i][3] + 1; // Wins
	else if (VALUE < (Integer) MIN_SPINNER.getValue ())
	    DATA[i][4] = (Integer) DATA[i][4] + 1; // Loss
	else
	    DATA[i][5] = (Integer) DATA[i][5] + 1; // Data
	DATA[i][7] = (50.0f * (float) (2 * (Integer) DATA[i][3] + (Integer) DATA[i][5])) / ((float) (Integer) DATA[i][2]);
	ReSort ();
	if (new Date ().getTime () - UP_DATE > 200) // elapse 0.2 ms
	    {
		TABLE.repaint ();
		UP_DATE = new Date ().getTime ();
	    }
    }

    public void ParseLineMC (String S)
    {
	// System.out.println (S);
	if (S.equals ("MonteCarlo finds completion"))
	    HaltMonteCarlo ();
	if (S.startsWith ("MCresult"))
	    ResultMC (S.substring (9));
    }

    public void EndMonteCarlo ()
    {
	HaltMonteCarlo ();
	CI.MONTE_CARLO = null;
	MC_FRAME.setVisible (false);
	MC_FRAME.dispose (); // null
    }
}