import java.awt.*;
import java.applet.*;
import java.util.*;

public class USAmap_color extends Applet implements Runnable {
/************************************************************************/
/*									*/
/*   Class USAmap_color: USA Map Coloring Problem Solver		*/
/*									*/
/*		Copyright (C) 1996 by Yasusi Kanada			*/
/*									*/
/*	This program can be freely distributed under the GNU General	*/
/*	Public License.							*/
/*									*/
/* Initial Version = "ver 1.0, 1996-8-13";				*/
   String Version = "ver 1.13, 1996-11-23";
/*									*/
/************************************************************************/

/*======================================================================*/
/*	Class global objects and methods				*/
/*======================================================================*/
   protected int n_colors = 4;		/* default: 4 colors		*/
   protected int n_states = 48;
			/* number of states (except Alaska and Hawaii)	*/

   private Thread solver;

   private int min(int x, int y) {
      return x <= y ? x : y;
   }


/*======================================================================*/
/*	Widget handler							*/
/*======================================================================*/
   private TextField message_box;	/* public inside the class	*/

   private void init_widgets() {
      setLayout(new FlowLayout());
      add(new Label
             ("USA Mainland Coloring using CCM (Chemical Casting Model) -- " +
              Version + " --"));
      add(new Button("Stop"));
      add(new Button("Restart"));

      init_frustration_choice(this);

      Panel major_option_panel = new Panel();
      major_option_panel.add(new Label("Major options:"));
      init_speed_choice(major_option_panel);
      init_catalyst_choice(major_option_panel);
      add(major_option_panel);

      message_box = new TextField(72);
      add(message_box);
   }

   public boolean action(Event e, Object arg) {
      message_box.setText("");
      if (speed_action(arg) || catalyst_action(arg) ||
	  frustration_action(arg)) {
         return true;
      } else if ("Restart".equals(arg)) {
         stop();
         start();
         return true;
      } else if ("Stop".equals(arg)) {
         stop();
         return true;
      } else {
         return false;
      };
   }


/* Choice extension: */

   private Choice new_choice(String[] labels, int default_index,
                             Panel p) {
      Choice c = new Choice();
      for (int i = 0; i < labels.length; i++) {
         c.addItem(labels[i]);
      };
      c.select(default_index);
      p.add(c);
      return c;
   }

   private int find_index(Object arg, String[] labels) {
      for (int i = 0; i < labels.length; i++) {
         if (labels[i].equals(arg)) {
            return i;
         };
      };
      return -1;	/* not found */
   }


   final String Dangerous_string =
      "Combination of `no catalyst' and `fast speed' is inhibited, " +
      "because you may loose control! ";


/* Speed control: */

   final int[] Sleep_time = {333, 50, 0};	/* in mili seconds	*/
   final int Medium_speed_index = 1;
   final int Fast_speed_index = 2;
   final int Full_speed_index = 3;
   final int Speed_default_index = Medium_speed_index;
   final String[] Speed_choice_labels =
      {"Slow speed (3 reactions/sec)", "Medium speed (20 reactions/sec)",
       "Fast speed", "Full speed"};

   private int speed_selected_index;
   private Choice speed_choice;

   private void init_speed_choice(Panel p) {
   /* Speed choice is created and initialized.	*/
      speed_choice =
         new_choice(Speed_choice_labels, Speed_default_index, p);
      speed_selected_index = Speed_default_index;
   }

   private boolean speed_action(Object arg) {
   /* Event handler for speed control choice.				*/
   /* Warning: This function is called not only when the event is	*/
   /*	caused by the speed control choice.				*/
      int index = find_index(arg, Speed_choice_labels);
      if (index >= 0) {		/* found	*/
         if (index == Full_speed_index &&
	     catalyst_selected_index == No_catalyst_index) {
            message_box.setText(Dangerous_string);
            speed_choice.select(Speed_choice_labels[speed_selected_index]);
            return false;
         } else if (index == Full_speed_index || index == Fast_speed_index) {
            display_switch = false;	/* no display while executing	*/
            speed_selected_index = index;
            return true;
         } else {
            display_switch = true;
            speed_selected_index = index;
            return true;
         };
      } else {			/* not found	*/
         return false;
      };
   }


/* Catalyst control: */

   final int Variable_catalyst_index = 0;
   final int No_catalyst_index     = 1;
   final int Single_catalyst_index = 2;
   final int Double_catalyst_index = 3;

   final int[] Catalysts =
      {Variable_catalyst_index, No_catalyst_index,
       Single_catalyst_index, Double_catalyst_index};

   final int Catalyst_default_index = Variable_catalyst_index;

   final String[] Catalyst_choice_labels =
      {"Variable catalyst rule", "No catalyst rule (running forever...)",
       "Single catalyst rule", "Double catalyst rule"};

   private int catalyst_selected_index;
   private Choice catalyst_choice;

   private void init_catalyst_choice(Panel p) {
   /* Catalyst control choice is created and initialized.		*/
      catalyst_choice =
         new_choice(Catalyst_choice_labels, Catalyst_default_index, p);
      catalyst_selected_index = Catalyst_default_index;
   }

   private boolean catalyst_action(Object arg) {
      int index = find_index(arg, Catalyst_choice_labels);
      if (index >= 0) {		/* found	*/
         if (index == No_catalyst_index &&
             speed_selected_index == Full_speed_index) {
            message_box.setText(Dangerous_string);
            catalyst_choice.select
               (Catalyst_choice_labels[catalyst_selected_index]);
            return false;
         } else {
            catalyst_selected_index = index;
            return true;
         };
      } else {
         return false;
      };
   }


/* Frustration control: */

   final int With_frustration_index = 0;
   final int Without_frustration_index     = 1;

   final int[] Frustrations =
      {With_frustration_index, Without_frustration_index};

   final double[] Frustration_values = {1e-5, 0.0};

   final int Frustration_default_index = With_frustration_index;

   final String[] Frustration_choice_labels =
      {"Frustration ON", "Frustration OFF"};

   private int frustration_selected_index;
   private Choice frustration_choice;

   private void init_frustration_choice(Panel p) {
   /* frustration choice is created and initialized.		*/
      frustration_choice =
         new_choice(Frustration_choice_labels, Frustration_default_index, p);
      frustration_selected_index = Frustration_default_index;
   }

   private boolean frustration_action(Object arg) {
      int index = find_index(arg, Frustration_choice_labels);
      if (index >= 0) {		/* found	*/
         frustration_selected_index = index;
	 initial_frustration = Frustration_values[index];
         return true;
      } else {
         return false;
      };
   }


/*======================================================================*/
/*	Map viewer							*/
/*======================================================================*/
   protected int size_factor;
			/* Actual drawing size = size_factor * coord.	*/
   protected boolean display_switch = true;
					/* To display the map or not	*/

   private Polygon[] state_polygons = new Polygon[n_states];
			/* Border polygons of the U.S. states.		*/
   private int[] painted_color = new int[n_states];
   protected Color[] id2color =
			/* Color ID to Color class constant conversion	*/
      {Color.yellow, Color.blue, Color.red, Color.cyan, Color.orange,
       Color.magenta, Color.green, Color.white};

   private Polygon create_polygon(int[] indices, int[] X, int[] Y) {
      int[] x_coords = new int[indices.length];
      int[] y_coords = new int[indices.length];
      for (int i = 0; i < indices.length; i++) {
         x_coords[i] = size_factor * X[indices[i]];
         y_coords[i] = size_factor * (Y[indices[i]] - 11);
				/* -11: adjust offset	*/
      };
      return new Polygon(x_coords, y_coords, indices.length);
   }

   private void init_view() {
      int[] X =		/* X coordinate indices for polygons	*/
	{ 17, 15, 23, 26, 12, 17, 21, 10, 11, 13, 11, 13, 15, 21, 22,
          16, 23, 26, 30, 33, 30, 28, 29, 31, 33, 30, 27, 31, 41, 41,
          43, 40, 41, 41, 43, 43, 43, 35, 39, 41, 51, 51, 52, 52, 53,
          52, 54, 54, 45, 45, 53, 54, 39, 43, 51, 51, 55, 55, 59, 57,
          59, 61, 58, 59, 59, 60, 60, 59, 61, 63, 63, 59, 61, 62, 62,
          61, 59, 65, 65, 62, 61, 65, 64, 68, 72, 75, 73, 63, 69, 71,
          72, 70, 70, 68, 70, 63, 63, 73, 73, 69, 64, 66, 67, 70, 75,
          75, 76, 79, 77, 77, 83, 84, 81, 73, 77, 77, 81, 77, 77, 70,
          71, 75, 75, 78, 79, 81, 77, 77, 83, 86, 89, 85, 85, 85, 83,
          80, 79, 83, 83, 85, 85, 87, 85, 87, 89, 87, 87, 89, 89, 87,
          92, 89, 67, 66, 67, 50, 54, 56, 60, 66, 68, 70, 72, 80, 82,
          84, 86, 64, 68, 70, 74, 78, 82, 84, 88, 64, 68, 70, 72, 66,
          68, 70, 74, 82, 84, 50, 54, 56, 60, 64, 68, 70, 74, 82, 84,
          52, 54, 56, 58, 66, 68, 70, 72, 78, 82, 84, 88, 70, 74, 64,
          68 };
      int[] Y =		/* Y coordinate indices for polygons	*/
	{ 33, 37, 41, 34, 43, 46, 47, 47, 51, 51, 53, 59, 64, 65, 61,
          51, 57, 49, 49, 45, 45, 42, 35, 59, 53, 52, 69, 69, 46, 44,
          39, 54, 50, 60, 60, 57, 55, 70, 70, 62, 45, 40, 52, 50, 50,
          56, 61, 57, 63, 65, 69, 63, 75, 75, 80, 76, 75, 70, 50, 47,
          44, 42, 42, 57, 55, 52, 63, 65, 65, 63, 62, 69, 67, 78, 76,
          73, 73, 52, 48, 47, 45, 53, 66, 66, 65, 63, 63, 75, 54, 52,
          49, 49, 47, 46, 44, 44, 42, 61, 59, 59, 60, 75, 73, 73, 57,
          52, 61, 57, 57, 56, 63, 61, 59, 66, 64, 66, 67, 74, 70, 77,
          75, 77, 79, 81, 83, 80, 51, 53, 53, 54, 53, 52, 50, 49, 45,
          47, 49, 56, 55, 58, 57, 49, 44, 50, 49, 52, 55, 51, 47, 43,
          42, 37, 46, 49, 51, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
          16, 16, 18, 18, 18, 18, 18, 18, 18, 18, 20, 20, 20, 20, 22,
          22, 22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
          26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 19, 23,
          23 };
      int[][] border_points =	/* Border point coordinates of polygons */
         {{0, 1, 2, 3, 0}, 
          {1, 4, 5, 6, 2, 1}, 
          {4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 5, 4},
          {5, 15, 14, 16, 17, 6, 5},
          {3, 2, 6, 17, 18, 19, 20, 21, 22, 3},
          {17, 16, 23, 24, 25, 18, 17},
          {16, 14, 13, 26, 27, 23, 16},
          {22, 21, 20, 19, 28, 29, 30, 22},
          {19, 18, 25, 24, 31, 32, 28, 19}, 
          {24, 23, 33, 34, 35, 36, 31, 24},		/* 10 */
          {23, 27, 37, 38, 39, 33, 23},
          {30, 29, 40, 41, 30},
          {29, 28, 32, 42, 43, 44, 40, 29},
          {32, 31, 36, 35, 45, 42, 32},
          {35, 34, 46, 47, 45, 35},
          {33, 39, 48, 49, 50, 51, 46, 34, 33},
          {39, 38, 37, 52, 53, 54, 55, 56, 57, 50, 49, 48, 39}, 
          {41, 40, 44, 58, 59, 60, 61, 62, 41}, 
          {43, 42, 45, 47, 63, 64, 65, 58, 44, 43},
          {47, 46, 51, 66, 67, 68, 69, 70, 63, 47},	/* 20 */
          {51, 50, 57, 71, 72, 68, 67, 66, 51},
          {57, 56, 73, 74, 75, 76, 71, 57},
          {60, 59, 58, 65, 77, 78, 79, 80, 60},
          {65, 64, 63, 70, 100, 81, 77, 65},
          {69, 68, 72, 82, 83, 84, 85, 86, 69}, 
          {72, 71, 76, 75, 74, 87, 82, 72}, 
          {96, 80, 79, 78, 152, 153, 154, 81, 88, 89, 90, 91, 92,
           93, 94, 95, 96},
          {81, 100, 99, 88, 81},
          {100, 70, 69, 86, 97, 98, 99, 100},
          {82, 87, 101, 102, 103, 83, 82},		/* 30 */
          {89, 88, 99, 98, 104, 105, 89},
          {104, 98, 97, 106, 107, 108, 109, 104},
          {107, 106, 97, 86, 85, 110, 111, 112, 107}, 
          {85, 84, 113, 114, 115, 116, 110, 85}, 
          {83, 103, 117, 118, 113, 84, 83},
          {102, 101, 119, 120, 121, 122, 123, 124, 125, 117, 
           103, 102},
          {113, 118, 116, 115, 114, 113},
          {135, 136, 126, 127, 128, 129, 130, 131, 132, 133, 
           134, 135},
          {105, 104, 109, 137, 138, 128, 127, 126, 105},
          {109, 108, 107, 112, 139, 137, 109},		/* 40 */
          {138, 137, 139, 140, 138},
          {134, 133, 141, 142, 134}, 
          {133, 132, 143, 147, 144, 141, 133},
          {132, 131, 145, 143, 132},
          {128, 138, 140, 146, 129, 128},
          {143, 145, 147, 143},
          {142, 141, 144, 148, 149, 142},
          {149, 148, 150, 151, 149}};

      for (int i = 0; i < painted_color.length; i++) {
         painted_color[i] = -1;	/* equal to no color	*/
      };
      size_factor = min(size().width / 100, size().height / 80);
      if (size_factor < 1) {
         size_factor = 1;
      };
      for (int i = 0; i < border_points.length; i++) {
         state_polygons[i] = create_polygon(border_points[i], X, Y);
      };
   }

   private void paint_state(int state_id, Graphics g) {
      g.setColor(id2color[color_wm[state_id]]);
      g.fillPolygon(state_polygons[state_id]);
      g.setColor(Color.black);
      g.drawPolygon(state_polygons[state_id]);
   }

   public void paint(Graphics g) {
      for (int i = 0; i < n_states; i++) {
         paint_state(i, g);
         painted_color[i] = color_wm[i];
      };
   }

   public void update(Graphics g) {
      paint(g);
   }


/*======================================================================*/
/*	Solver								*/
/*======================================================================*/
   double initial_frustration = 1.0e-5;
   double frustration_factor = 2.0;

   /* Working memory: */
   private int[] color_wm, id_wm;
   private double[] frustration_wm;
   private int[] n_neighbors;
   private int[][] neighbors_wm;
   private int wm_size;

   private int n_tests;
   private int n_reactions;
   private int n_failed_tests;

   /* Order degree: */
   private int vertex_order(int color_i, int color_j) {
      return color_i != color_j ? 1 : 0;
   }

   private double mean_order() {
   /* Returns the mean order degree of the current WM.	*/
      int total_order = 0;
      int n_edges = 0;
      for (int i = 0; i < wm_size; i++) {
         for (int j = 0; j < n_neighbors[i]; j++) {
            total_order +=
               vertex_order(color_wm[i], color_wm[neighbors_wm[i][j]]);
            n_edges++;
         };
      };
      return total_order / (double)n_edges;
   }

   private void init_solver(int size) {
   /* Allocate working memory */
      color_wm = new int[size];
      id_wm = new int[size];
      frustration_wm = new double[size];
      n_neighbors = new int[size];
      neighbors_wm = new int[size][];

      init_wm();
   }


/* Random color/index generators */

   private Random selector = new Random();

   private int random(int max) {
      return (selector.nextInt() & 0x7FFFFFFF) % max;
   }

   private int select_color(int index) {
      int old_color = color_wm[index];
      int new_color;
      do {
         new_color = random(n_colors);
      } while (new_color == old_color);
      return new_color;
   }

   private void reaction(int index, int old_order, int new_order,
                         int new_color) {
      int old_color = color_wm[index];
      if (old_order >= 0) {	/* A local termination condition holds	*/
         n_failed_tests++;
      } else if (((double)old_order - frustration_wm[index]) > new_order) {
						/* not to be reacted	*/
         n_failed_tests++;
         frustration_wm[index] *= frustration_factor;
      } else {
         n_reactions++;
         n_failed_tests = 0;
         color_wm[index] = new_color;		/* Change color	*/
         if (display_switch) {
            repaint();
            try {solver.sleep(Sleep_time[speed_selected_index]);}
            catch (InterruptedException e) {};
         };
         frustration_wm[index] = initial_frustration;
      };
   }

/* Rules */

   private void change_color() {
   /* Variable catalyst rule	*/
      int index = random(wm_size);
				/* select a vertex to apply the rule	*/
      int old_color = color_wm[index];
      int new_color = select_color(index);

      n_tests++;

   /* Compute LODs: */
      int old_order = 0;
      int new_order = 0;
      for (int i = 0; i < n_neighbors[index]; i++) {
         int neighbor_color = color_wm[neighbors_wm[index][i]];
         if (neighbor_color == old_color) {
             old_order -= 1;
         };
         if (neighbor_color == new_color) {
             new_order -= 1;
         };
      };

      reaction(index, old_order, new_order, new_color);
   }

   private void change_color0() {
   /* No catalyst rule		*/
      int index = random(wm_size);
				/* select a vertex to apply the rule	*/
      n_tests++;

      reaction(index, -1, -1, select_color(index));
   }

   private void change_color1() {
   /* Variable catalyst rule	*/
      int index1 = random(wm_size);
				/* select a vertex to apply the rule	*/
      if (n_neighbors[index1] == 0) {		/* selection failed	*/
         n_failed_tests++;
         return;
      };
      int index2 = neighbors_wm[index1][random(n_neighbors[index1])];
	/* select a neighbor vertex (index2 is the index in neighbors_wm) */

      int new_color = select_color(index1);
      n_tests++;

   /* Compute LODs: */
      int old_order = vertex_order(color_wm[index1], color_wm[index2]) - 1;
      int new_order = vertex_order(new_color, color_wm[index2]) - 1;

      reaction(index1, old_order, new_order, new_color);
   }

   private void change_color2() {
   /* Variable catalyst rule	*/
      int index1 = random(wm_size);
				/* select a vertex to apply the rule	*/
      if (n_neighbors[index1] <= 1) {		/* selection failed	*/
         n_failed_tests++;
         return;
      };
      int i = random(n_neighbors[index1]);
      int index2 = neighbors_wm[index1][i];
	/* select a neighbor vertex (index2 is the index in neighbors_wm) */
      int j;
      do {
         j = random(n_neighbors[index1]);
      } while (i == j);
      int index3 = neighbors_wm[index1][j];

      int old_color = color_wm[index1];
      int new_color = select_color(index1);
      n_tests++;

   /* Compute LODs: */
      int old_order = vertex_order(old_color, color_wm[index2]) +
                      vertex_order(old_color, color_wm[index3]) - 2;
      int new_order = vertex_order(new_color, color_wm[index2]) +
                      vertex_order(new_color, color_wm[index3]) - 2;

      reaction(index1, old_order, new_order, new_color);
   }


/* Solver main part: */

   public void run() {
      long initial_time;
      int max_tests = 1000;
      int max_tests_20 = 20 * max_tests;

      for (int i = 0; i < n_states; i++) {
         color_wm[i] = 0;	/* or random(n_colors); */
      };
      message_box.setText("Running...");
      repaint();

      initial_time = System.currentTimeMillis();
      n_tests = 0;
      n_reactions = 0;
      n_failed_tests = 0;
      while (true) {
         switch (catalyst_selected_index) {
         case No_catalyst_index:
            change_color0();
            break;
         case Single_catalyst_index:
            change_color1();
            break;
         case Double_catalyst_index:
            change_color2();
            break;
         case Variable_catalyst_index:
            change_color();
            break;
         };

         if (n_failed_tests >= max_tests) {	/* termination condition */
            if (n_tests < max_tests_20) {
               break;
            };
            max_tests_20 = 2 * max_tests_20;
            max_tests = max_tests_20 / 20;
         };
         if (speed_selected_index != Full_speed_index &&
	     n_tests % 7500 == 0) {
            message_box.setText
               ("Running... #tests = " + n_tests +
                "  #reactions = " + n_reactions + "  MOD = " + mean_order() +
                "  Time = " +
                (System.currentTimeMillis() - initial_time) / 1000.0);
            repaint();
            try {solver.sleep(50);}
            catch (InterruptedException e) {};
         };
      };
      double order = mean_order();
      message_box.setText
         ("Stoppped.  #tests = " + n_tests +
          "  #reactions = " + n_reactions +
          "  MOD = " + order + (order < 1 ? "  *** Failed! ***" : "") +
          "  Time = " + (System.currentTimeMillis() - initial_time) / 1000.0);
      repaint();
   };


/*======================================================================*/
/*	Working memory (USA Graph) generation				*/
/*======================================================================*/
   private void count_edge(int[] edge_count, int n_vertices, int to_count) {
      if (0 <= to_count && to_count < n_vertices) {
         edge_count[to_count]++;
      } else {
         /* Exception must be raised! */
      };
   }

   private void graph_setup(int[] edge_sources, int[] edge_destinations,
                            int n_vertices) {
      int n_edges = edge_sources.length; /* = edge_destinations.length	*/
      int[] edge_counts = new int[n_vertices];
      for (int i = 0; i < n_edges; i++) {  /* count edges for allocation */
         count_edge(edge_counts, n_vertices, edge_sources[i]);
         count_edge(edge_counts, n_vertices, edge_destinations[i]);
      };
      for (int i = 0; i < n_vertices; i++) {	/* Make vertices	*/
         id_wm[i] = i;
         frustration_wm[i] = initial_frustration;
         neighbors_wm[i] = new int[edge_counts[i]];
      };
      wm_size = n_vertices;
      for (int i = 0; i < n_edges; i++) {	/* Make edges (links)	*/
         int index;
         index = edge_destinations[i];
         neighbors_wm[index][n_neighbors[index]++] = edge_sources[i];
         index = edge_sources[i];
         neighbors_wm[index][n_neighbors[index]++] = edge_destinations[i];
      };
   }

   private void init_wm() {
      int[] edge_sources =
         { 0,  0,  1,  1,  1,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5,
	   5,  6,  7,  7,  7,  8,  8,  8,  9,  9,  9,  9, 10, 10, 11,
	  11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16,
	  17, 17, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 21, 22, 22,
	  23, 23, 24, 24, 24, 24, 24, 24, 25, 26, 26, 27, 27, 28, 28,
	  28, 29, 29, 30, 30, 31, 31, 31, 32, 32, 33, 33, 34, 34, 37,
	  37, 37, 37, 37, 38, 38, 38, 39, 40, 41, 41, 42, 42, 42, 43,
	  46};
      int[] edge_destinations =
	 { 1,  4,  2,  3,  4,  3,  6,  4,  5,  6,  5,  7,  8,  6,  8,
	   9, 10,  8, 11, 12,  9, 12, 13, 10, 13, 14, 15, 15, 16, 12,
	  17, 13, 17, 18, 14, 18, 19, 15, 18, 19, 16, 19, 20, 20, 21,
	  18, 22, 19, 22, 23, 20, 23, 24, 28, 21, 24, 25, 25, 23, 26,
	  27, 28, 25, 28, 29, 32, 33, 34, 29, 27, 30, 28, 30, 30, 31,
	  32, 34, 35, 31, 38, 32, 38, 39, 33, 39, 34, 36, 35, 36, 38,
	  41, 42, 43, 44, 39, 40, 44, 40, 44, 42, 46, 43, 45, 46, 45,
	  47};
      graph_setup(edge_sources, edge_destinations, n_states);
   }


/*======================================================================*/
/*	Dispatchers							*/
/*======================================================================*/
   public void init() {
      init_solver(n_states);
      init_widgets();
      init_view();
   }

   public void start() {
      solver = new Thread(this);
      /* solver.setPriority(Thread.MIN_PRIORITY); */
      solver.start();
   }

   public void stop() {
      solver.stop();
   }

}
