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 Molecule {
   Color type;                    // type of molecule
   Color color;                   // the actual color in which it is painted
   boolean is_repl;               // true iff molecule is a replicator
   int x, y;                      // coordinates of point of element in space
   Color prevColor;               // color before moving
   boolean prevIsRepl;            // value of flag before moving
   int prevX, prevY;              // coordinates before moving
   HSL hsl;                       // [hue,sat,lum] corresponding to type
   int age;                       // age (how much it has moved)
   int food;                      // how much 'food' it has consumed
   int mut_rate;                  // mutate once in 'mut_rate' replications
   int steps_per_food;            // endurance, or energy-efficiency value
   int enough_food_to_split;      // # of food items needed for splitting
   Space space;                   // the space where the molecule moves

   // The following are constants for 2nd parameter of SetType().
   final int TurnToRepl =  1;
   final int TurnToFood = -1;
   final int DetermineByType = 0;
   // The following are the default variables for replicators.
   static final int ReplicatorHue = 200;
   static final int ReplHueMargin = 2;
   static final int ReplHueLow  = ReplicatorHue - ReplHueMargin;
   static final int ReplHueHigh = ReplicatorHue + ReplHueMargin;
   static final int ReplSatLow = 234;
   static final int ReplSatHigh = 238;
   static final int ReplLumLow = 149;
   static final int ReplLumHigh = 151;
   final int MutationRate = 10;
   final int StepsPerFood = 30;
   final int EnoughFood = 20;

   Molecule (Space _space) { this (_space, 0, 0); }

   Molecule (Space _space, int _x, int _y) {
      space = _space;
      x = _x;
      y = _y;
      is_repl = false;
      prevX = prevY = -1;
      age = 0;
      food = 0;
      mut_rate = MutationRate;
      steps_per_food = StepsPerFood;
      enough_food_to_split = EnoughFood;
      hsl = new HSL();
   }

   Molecule (Space _space, int _x, int _y, Color _type) {
      this (_space, _x, _y);
      SetType (_type, 0);
   }

   double discr (double x, double x1, double x2, int steps, boolean round)
      // Assuming (x2-x1) is divided into 'steps' # of intervals, returns the
      // interval point closest to 'x'. If 'round' then rounds, else truncates.
   {
      double interval = (x2 - x1) / steps;
      if (round)
         x += interval / 2;
      return x1 + interval * Math.floor ((x - x1) / interval);
   }

   /***
   void Erase (Graphics g) {
      int x0 = cell_side / 2 + x * cell_side;
      int y0 = cell_side / 2 + y * cell_side;
      g.setColor (space.BackgrColor);
      g.fillOval(x0-cell_side/2, y0-cell_side/2, cell_side, cell_side);
   }
   ***/

   boolean IsReplicator () { return is_repl; }

   void MagnifyReplicatorType () {
      // Called only on replicators.
      // "Magnifies" the replicator's type by a certain factor, so that
      // mutations can be more easily perceived visually.
      int magnFactor = space.demoMode == 0 ? space.TotalRepl / 30 : 20;

      byte h = (byte) (ReplicatorHue + (hsl.getHue() - ReplicatorHue) * magnFactor);
      byte s = (byte) (ReplSatHigh - (ReplSatHigh - hsl.getSat()) * magnFactor);
      byte l = (byte) (ReplLumHigh - (ReplLumHigh - hsl.getLum()) * magnFactor);
      hsl = new HSL (h, s, l);
      color = hsl.HSL_to_RGB();
   }

   int MinusOneOrPlusOne () { return Random(2) * 2 - 1; }

   void Move (int _x, int _y) {
      prevX = x;
      prevY = y;
      x = _x;
      y = _y;
      if (is_repl  &&  ++age % steps_per_food == 0  &&  --food < 0) {
         // walking too much with empty stomach damages health.
         int red, grn, blu;
         do {
            red = Random(256);
            grn = Random(256);
            blu = Random(256);
         } while (IsReplicator (red, grn, blu));
         SetType (new Color (red, grn, blu), TurnToFood);
      }
   }

   static boolean IsReplicator (int r, int g, int b) {
      HSL hsl = new HSL (r, g, b);
      return hsl.IsReplicator();
   }

   Color Mutation (Color orig_type) {
      if (Random(mut_rate) != 0)
         return orig_type;
      else {
         Byte h, s, l;
         HSL hsl = new HSL (orig_type);
         switch (Random(3)) {
            case 0:
               hsl.setHue (hsl.getHue() + MinusOneOrPlusOne());
               break;
            case 1:
               hsl.setSat (hsl.getSat() + MinusOneOrPlusOne());
               break;
            case 2:
               hsl.setLum (hsl.getLum() + MinusOneOrPlusOne());
               break;
         }
         return hsl.HSL_to_RGB();
      }
   }

   void paint (Graphics g)
   {
      int c, r;
      Color clr;
      boolean isRepl;
      if (space.smoothSteps > 1  &&  prevX != -1) {
         // Causes the Molecule to be painted in its previous location, along
         // the path from there to its present location, at snapshot
         // 'space.smoothStep' of a total of 'space.smoothSteps'.
         c = space.cell_side / 2 + prevX * space.cell_side;
         r = space.cell_side / 2 + prevY * space.cell_side;
         c += ((x - prevX) / ((double) space.smoothSteps)) * space.smoothStep * space.cell_side;
         r += ((y - prevY) / ((double) space.smoothSteps)) * space.smoothStep * space.cell_side;
         clr = prevColor;
         isRepl = prevIsRepl;
      }
      else {
         // Causes the molecule to be painted at its current location.
         c = space.cell_side / 2 + x * space.cell_side;
         r = space.cell_side / 2 + y * space.cell_side;
         clr = color;
         isRepl = IsReplicator();
      }
      g.setColor (clr);
      g.fillOval(c-space.cell_side/2, r-space.cell_side/2, space.cell_side, space.cell_side);
      if (isRepl) {
         g.setColor(Color.black);
         g.drawRect (c-space.replMarkSide/2-1, r-space.replMarkOffs, space.replMarkSide, space.replMarkSide);
      }
   }

   void PhenotypeFromGenotype ()
      // Given that the "genotype" is [hue,sat,lum], and is already determined,
      // this method derives from it the "phenotype".
   {
      MagnifyReplicatorType();

      // Now set the mutation rate & other parameters, according to the type.
      int sum = hsl.getHue() + hsl.getSat() + hsl.getLum();
      if (sum % 2 == 0) {
         steps_per_food += MinusOneOrPlusOne();
         steps_per_food = Math.max (1, steps_per_food);
      }
      else if (sum % 3 == 0) {
         enough_food_to_split += MinusOneOrPlusOne();
         enough_food_to_split = Math.max (1, enough_food_to_split);
      }
      else if (sum % 5 == 0) {
         mut_rate += MinusOneOrPlusOne();
         mut_rate = Math.max (1, mut_rate);
      }
   }

   static public int Random (int N) { return (int) (Math.random() * N); }

   void React (Molecule m, boolean self_only)
      // Causes 'this' to react given 'm'.
      // If 'self_only' is false, 'm' also reacts to 'this'.
   {
      prevIsRepl = is_repl;
      prevColor = color;

      int red1 = type.getRed();
      int grn1 = type.getGreen();
      int blu1 = type.getBlue();

      if (IsReplicator()) {
         if ( ! m.IsReplicator())
            food++;
      }
      else if (m.IsReplicator()) {
         if ( ! IsReplicator()) {
            // 'this' is food, but 'm' is a replicator.
            if (m.food >= m.enough_food_to_split) {
               // then turn 'this' into a (possibly mutated) replica of 'm'
               SetType (Mutation (m.type), TurnToRepl);
               m.food = 0;        // and make 'm' hungry again.
            }
         }
      }
      else {  // neither 'this' nor 'm' is a replicator.
         // Change slightly, and randomly, the molecule's type (color):
         do {
            // This loop simply doesn't allow replicators under demo mode 1.
            red1 = recalibrateColorComponent (red1);
            grn1 = recalibrateColorComponent (grn1);
            blu1 = recalibrateColorComponent (blu1);
         } while (space.demoMode == 1 && IsReplicator (red1, grn1, blu1));

         if (IsReplicator (red1, grn1, blu1)) {
            hsl = new HSL();
            hsl.RGB_to_HSL (red1, grn1, blu1);
            hsl.setHue ((int) discr (hsl.getHue(), ReplHueLow, ReplHueHigh, 5, true));
            hsl.setSat ((int) discr (hsl.getSat(), ReplSatLow, ReplSatHigh, 2, true));
            hsl.setLum ((int) discr (hsl.getLum(), ReplLumLow, ReplLumHigh, 2, true));
            SetType (hsl.HSL_to_RGB(), TurnToRepl);
         }
         else
            SetType (new Color (red1, grn1, blu1), DetermineByType);
      }
      if ( ! self_only)
         m.React (this, true);
   }

   int recalibrateColorComponent (int colorComponent) {
      int newComponent = colorComponent += (Random(5) + 1) * MinusOneOrPlusOne();
      if (newComponent < 0)
         newComponent += 256;
      else if (newComponent >= 256)
         newComponent -= 256;
      return newComponent;
   }

   /***
   void SetReplicatorHSL (Byte hue, Byte sat, Byte lum,
                          Byte hue_r, Byte sat_r, Byte lum_r)
      // Sets the default HSL of replicators.
   {
      ReplicatorHue = hue;
      ReplHueMargin = hue_r;
      ReplSatLow  = sat - sat_r;
      ReplSatHigh = sat + sat_r;
      ReplLumLow  = lum - lum_r;
      ReplLumHigh = lum + lum_r;
   }
   ***/

   void SetType (Color _type, int repl)
      // Sets type to '_type'.
      // If 'repl' is TurnToRepl, 'is_repl' is set to true.
      // If 'repl' is TurnToFood, 'is_repl' is set to false.
      // If 'repl' is 0, whether this is a replicator or not is decided by '_type'.
   {
      type = _type;
      hsl.RGB_to_HSL (type);
      boolean prev_is_repl = is_repl;
      if (repl == TurnToRepl)
         is_repl = true;
      else if (repl == TurnToFood)
         is_repl = false;
      else  // 'repl' is DetermineByType
         if ( ! is_repl)
            is_repl = hsl.IsReplicator();

      if (is_repl && ! prev_is_repl) {
         space.TotalRepl ++;
         space.TotalFood --;
      }
      else if ( ! is_repl && prev_is_repl) {
         space.TotalRepl --;
         space.TotalFood ++;
      }
      if ( ! is_repl)
         color = type;
      else {
         PhenotypeFromGenotype();
         //RecordPossibleNewType();
      }
      age = 0;
      food = 0;
   }
}

