package lifestart;

/**
 * Title:        Life Start
 * Description:  A simplistic simulation of the start of biological life.
 * Copyright:    Copyright (c) 2002
 * Company:      Indiana University
 * @author Harry E. Foundalis
 * @version 1.0
 */

import java.awt.*;
import java.util.*;
import java.math.*;


public class Space implements Runnable {
   // Space is the space of molecules in the Start of Life.

   LifeStart lifeApp;              // the applet or application owing the space
   Molecule[][] pos;               // the rectangular array of molecules
   Molecule[] molarray;            // the list of molecules
   int space_width;                // space width, in # of elements
   int space_height;               // space height, in # of elements
   int epoch;                      // epoch (generation) of space
   int num_of_elem;                // number of cells at current epoch
   int cell_side;                  // molecule side length, in pixels
   int replMarkSide;               // replicator marker side-length, in pixels
   int replMarkOffs;               // replicatoe marker offset, in pixels
   int TotalRepl;                  // # of replicator molecules
   int TotalFood;                  // # of non-replicator molecules
   Rectangle visibleWindow;        // coordinates of visible portion of Space
   Color BackgrColor;              // background color for space
   int demoMode;                   // 0: no demo; 1: no repls; 2: some repls
   int smoothSteps;                // min value = 1; the higher, the smoother
   int smoothStep;                 // the current smooth step

   Space (LifeStart _lifeApp) {
      super();
      lifeApp = _lifeApp;
      epoch = 0;
      TotalRepl = 0;
      TotalFood = 0;
      visibleWindow = null;
      num_of_elem = 1000;
      cell_side = 8;
      replMarkSide = 3;
      replMarkOffs = 0;
      pos = null;
      molarray = null;
      demoMode = 0;
      smoothSteps = 1;
   }

   void DecideNewNeighboringPos (int x, int y, Point p) {
      switch (Molecule.Random (8)) {
         case 0:
            p.x = x - 1;
            p.y = y - 1;
            break;
         case 1:
            p.x = x;
            p.y = y - 1;
            break;
         case 2:
            p.x = x + 1;
            p.y = y - 1;
            break;
         case 3:
            p.x = x - 1;
            p.y = y;
            break;
         case 4:
            p.x = x + 1;
            p.y = y;
            break;
         case 5:
            p.x = x - 1;
            p.y = y + 1;
            break;
         case 6:
            p.x = x;
            p.y = y + 1;
            break;
         case 7:
            p.x = x + 1;
            p.y = y + 1;
            break;
      }
   }

   void findEmptyPos (Point p) {
      // loops until a null position at pos[x][y] is found, returned in 'p'.
      do {
         p.x = Molecule.Random (space_width);
         p.y = Molecule.Random (space_height);
      } while (pos [p.x][p.y] != null);
   }

   public int getEpoch () { return epoch; }

   public int getMolSize () { return cell_side; }

   public int getNumOfElems () { return num_of_elem; }

   public int getNumOfMols () { return TotalFood; }

   public int getNumOfRepls () { return TotalRepl; }

   public Rectangle getVisibleWindow () { return visibleWindow; }

   void Initialize () {
      space_width  = visibleWindow.width  / cell_side;
      space_height = visibleWindow.height / cell_side;
      num_of_elem = Math.min (num_of_elem, space_width * space_height);
      pos = new Molecule[space_width][space_height];
      for (int i = 0;  i < space_width;  i ++)
         for (int j = 0;  j < space_height;  j ++)
            pos[i][j] = null;

      molarray = new Molecule[num_of_elem];
      TotalRepl = 0;
      TotalFood = num_of_elem;
      int init_repls = 0;
      Point p = new Point();
      if (demoMode == 2) {
         // Under this demo mode, a few replicators are created to begin with.
         int tot_repls = Math.max (1, (int) (num_of_elem * 0.1));
         for ( ;  init_repls < tot_repls;  init_repls ++) {
            HSL hsl = HSL.GetReplicator();
            Color c = hsl.HSL_to_RGB();
            findEmptyPos (p);
            Molecule mol = new Molecule (this, p.x, p.y, c);
            molarray[init_repls] = mol;
            pos[p.x][p.y] = mol;
         }
      }
      for (int i = init_repls;  i < num_of_elem;  i ++) {
         findEmptyPos (p);
         int r, g, b;
         // The following loop avoids any chance replicators
         boolean isReplic;
         do {
            r = Molecule.Random(256);
            g = Molecule.Random(256);
            b = Molecule.Random(256);
            isReplic = Molecule.IsReplicator (r, g, b);
            if (isReplic)
               r++;
         } while (isReplic);
         Molecule mol = new Molecule (this, p.x, p.y, new Color (r, g, b));
         molarray[i] = mol;
         pos[p.x][p.y] = mol;
      }
      epoch = 0;
   }

   public boolean isInitialized () { return molarray != null; }

   void MoveAllElements () {
      for (int i = 0;  i < molarray.length;  i ++)
         MoveElement (i);

      for (smoothStep = 0;  smoothStep < smoothSteps - 1;  smoothStep ++) {
         lifeApp.paintGame();
         lifeApp.delay (100);
      }
   }

   void MoveElement (int elem_i) {
      // Moves molarray # 'i' to a new position only if that position is not already
      // occupied. If it is, tries again until an empty position is found. If no
      // empty position exists, does nothing.
      int x = molarray[elem_i].x;
      int y = molarray[elem_i].y;

      // First, make sure there is at least one empty neighboring location.
      if (Occupied (x-1, y-1) && Occupied (x, y-1) && Occupied (x+1, y-1)
      &&  Occupied (x-1, y  ) && Occupied (x, y  ) && Occupied (x+1, y  )
      &&  Occupied (x-1, y+1) && Occupied (x, y+1) && Occupied (x+1, y+1))
         return;

      Point p = new Point();
      boolean moved = false;
      do {
         // Decide at random which of the 8 neighboring locations will be occupied.
         DecideNewNeighboringPos (x, y, p);
         if ( ! OutOfSpace (p.x, p.y))
            if ( ! Occupied (p.x, p.y)) {
               //molarray[elem_i].Erase();
               pos[molarray[elem_i].x][molarray[elem_i].y] = null;

               molarray[elem_i].Move (p.x, p.y);

               pos[p.x][p.y] = molarray[elem_i];
               //molarray[elem_i].paint();
               moved = true;
            }
            else
               molarray[elem_i].React (pos[p.x][p.y], false);
      } while ( ! moved);
   }

   boolean Occupied (int x, int y) {
      if (OutOfSpace (x, y))
         return true;
      else
         return pos[x][y] != null;
   }


   boolean OutOfSpace (int x, int y) {
      return x < 0  ||  y < 0  ||  x >= space_width  ||  y >= space_height;
   }

   public void paint (Graphics g) {
      if (molarray != null)
         for (int i = 0;  i < molarray.length;  i ++)
            molarray[i].paint (g);
   }

   public void run () {
      if (molarray != null) {
         while (true) {   // while thread is not interrupted by stop().
            if (demoMode != 0  &&  lifeApp.demosMustStop)
               lifeApp.hybernateDemos();
            else
               step();
         }
      }
   }

   public void SetDemoMode (int _demoMode) { demoMode = _demoMode; }

   public void SetMolSize (int _cell_side) {
      if (_cell_side >= 1) {
         cell_side = _cell_side;
         if (cell_side <= 2)
            replMarkSide = -1;
         else if (cell_side <= 4)
            replMarkSide = 0;
         else if (cell_side <= 5)
            replMarkSide = 1;
         else if (cell_side <= 7)
            replMarkSide = 2;
         else if (cell_side <= 16)
            replMarkSide = 3;
         else
            replMarkSide = 4;
         replMarkOffs = cell_side <= 5 ? 0
                      : replMarkSide / 2 + (cell_side % 2 == 0 ? 1 : 0);
      }
   }

   public void setNumOfElements (int _num_of_elem) { num_of_elem = _num_of_elem; }

   public void setSmoothSteps (int _smoothSteps) { smoothSteps = _smoothSteps; }

   public void setWindow (Rectangle r) { visibleWindow = new Rectangle (r); }

   public void step () {
      // Performs one step forward in time.
      if (smoothSteps == 1)
         lifeApp.delay (200);
      MoveAllElements();
      epoch ++;
      lifeApp.paintGame();
   }
}

