import java.util.*;
import java.awt.*;
import java.util.Random;

/**
 * @author  Heinze, Pausch, Robl
 * @version 1.0
 */

public class PlayField
{
    /** additional drawmode: random */
    public static final int TYPE_RANDOMFOOD = -1;
    /** additional drawmode: new life */
    public static final int TYPE_NEWLIFE = -2;
    /** additional drawmode: death */
    public static final int TYPE_DEATH = -3;

    /** width and height of the playfield in units */
    public int playfield_width, playfield_height;
    /** two-dimensional array of food; allows the microbes to have a fast access to the playfield */
    public Food playfield[][];
    /** reference to the currently selected microbe (gets drawed blue) */
    private Microbe selectedmic=null;

    /** linked list of all microbes on the playfield */
    private Vector microbes=new Vector();
    // some gfx-related variables
    private Graphics gfx;
    private int scalefactor, energyloss;
    // color array of all possible life-colors to avoid making garbage y making new instances every time a color value is needed
    private Color miccolors[]=new Color[256];
    private Color backcolor=new Color(0,0,0);
    private Color bluecolor=new Color(0,0,255);

    /** random generator, for example for the random draw mode */
    private Random rnd=new Random();

    /**
    * calculates a positive random value in the rango of 0 to n-1
    * @param n upper limit for calculation
    */
    private int random(int n)
    {
      if (n<=0)
        return 0;
      int r=rnd.nextInt();
      if (r<0) r*=-1;
      return r%n;
    }

    /**
    * sets graphics context for all further graphic operations
    * @param g graphics context
    */
    public void set_graphics(Graphics g)
    {
      gfx=g;
    }

    /**
    * creates the playfield with the parameters
    * @param width width of playfield in playfield-grid-points
    * @param height height of playfield in playfield-grid-points
    * @param pixelsize size of a pixel in the playfield (quadratic, unit: pixels)
    */
    public PlayField(int width, int height, int pixelsize)
    {
      playfield_width=width;
      playfield_height=height;
      playfield=new Food [width][height];

      int x, y;
      for(y=0; y<playfield_height; y++)
        for(x=0; x<playfield_width; x++)
          playfield[x][y]=new Food();

      // initialize the color values which represent the energy-level of the microbes
      for (int i=0; i<256; i++)
        miccolors[i]=new Color(255-i,i,i);

      clear_all();
      fill_random();

      scalefactor=pixelsize;
    }

    /**
    * fills the whole playfield with the food "color" and creates the playfield borders<br>
    * the playfield borders are hardcoded into the microbes brain to be unpassable
    * @param color color (food-type) to fill the playfield with
    */
    public void fill_all(int color)
    {
      int x, y;

      if (color==TYPE_RANDOMFOOD)
      {
        fill_random();
      }
      else
      {
        for(y=0; y<playfield_height; y++)
          for(x=0; x<playfield_width; x++)
            playfield[x][y].settype(color);
      }

      y=playfield_height-1;
      for(x=0; x<playfield_width; x++)
      {
        playfield[x][0].settype(Food.TYPE_BORDER);
        playfield[x][y].settype(Food.TYPE_BORDER);
      }

      x=playfield_width-1;
      for(y=0; y<playfield_height; y++)
      {
        playfield[0][y].settype(Food.TYPE_BORDER);
        playfield[x][y].settype(Food.TYPE_BORDER);
      }
    }

    /** fills the whole playfield with the food Food.TYPE_NOTHING */
    public void clear_all()
    {
      fill_all(Food.TYPE_NOTHING);
    }

    /** lets "rain" food in random order onto the playfield */
    public void fill_random()
    {
      int x, y;

      for(y=1; y<playfield_height-1; y++)
        for(x=1; x<playfield_width-1; x++)
          if (random(2)==0)
            playfield[x][y].settype(Food.TYPE_MINFOOD+random(Food.TYPE_MAXFOOD));
    }

    /** draws a single (scaled) pixel */
    private void setpixel(int x, int y, Color color)
    {
      gfx.setColor(color);
      gfx.fillRect(x*scalefactor,y*scalefactor,scalefactor, scalefactor);
    }

    /**
    * draws a microbe by its reference
    * @param mic microbe to draw
    */
    private void draw_microbe(Microbe mic)
    {
      setpixel(mic.getx(), mic.gety(), miccolors[mic.getcolor()]);
    }

    /**
    * draws the food "type" with the brush size "size" onto the position (x,y)
    * @param x x position
    * @param y y position
    * @param type food type
    * @param size size of the drawing brush
    */
    public void manualdraw(int x, int y, int type, int size)
    {
      int xx, yy;
      size/=2;

      if (type==TYPE_DEATH) // remove microbes
      {
          Microbe mic;

          for(int i=0; i<microbes.size(); i++)
          {
              mic=(Microbe) microbes.elementAt(i);
              xx=mic.getx();
              yy=mic.gety();

              if (yy>=y-size && yy<=y+size && xx>=x-size && xx<=x+size)
                remove_microbe(xx, yy);
          }
      }
      else // all the other drawing routines
      {
          for(yy=y-size; yy<=y+size; yy++)
          {
            for(xx=x-size; xx<=x+size; xx++)
            {
              if (xx>0 && yy>0 && xx<playfield_width-1 && yy<playfield_height-1)
              {
                switch (type)
                {
                  case TYPE_RANDOMFOOD:
                    playfield[xx][yy].settype(Food.TYPE_MINFOOD+random(Food.TYPE_MAXFOOD));
                    setpixel(xx, yy, playfield[xx][yy].getcolor());
                    break;
                  case TYPE_NEWLIFE:
                    draw_microbe(new_microbe(xx, yy, 1000, energyloss));
                    break;
                  default:
                    playfield[xx][yy].settype(type);
                    setpixel(xx, yy, playfield[xx][yy].getcolor());
                    break;
                }
              }
            }
          }
      }
    }

    /**
    * sets the energyloss per stepp for every microbe
    * @param new_ernergyloss new energyloss per step value
    */
    public void setenergyloss(int new_energyloss)
    {
      Microbe mic;

      energyloss=new_energyloss;
      for(int i=0; i<microbes.size(); i++)
      {
        mic=(Microbe) microbes.elementAt(i);
        mic.setenergyloss(energyloss);
      }
    }

    /**
    * returns the food type at the position (x,y) in the playfield
    * @param x x position
    * @param y y position
    * @return food type
    */
    public int getfoodtype(int x, int y)
    {
      if (x>=0 && y>=0 && x<playfield_width && y<playfield_height)
        return playfield[x][y].gettype();
      return 0;
    }

    /** draws the whole playfield (without microbes!) */
    public void draw_all()
    {
      int x, y;

      for(y=0; y<playfield_height; y++)
        for(x=0; x<playfield_width; x++)
          setpixel(x,y,playfield[x][y].getcolor());
    }

    /** calculates one step of every microbe and draws the changes */
    public void refresh()
    {
      Microbe mic;
      int i;

      // first, remove all microbes from the playfield
      for(i=0; i<microbes.size(); i++)
      {
        mic=(Microbe) microbes.elementAt(i);
        setpixel(mic.getx(), mic.gety(), backcolor);
      }

      // now calculate a step for every microbe
      for(i=0; i<microbes.size(); i++)
      {
        mic=(Microbe) microbes.elementAt(i);
        if (!mic.step())
        { // microbe died
          if (selectedmic==mic)
            selectedmic=null;
          microbes.removeElementAt(i--);
        }
      }

      // draw the new microbe pixels
      for(i=0; i<microbes.size(); i++)
        draw_microbe((Microbe) microbes.elementAt(i));

      // paint the selected microbe with blue color (if there is a selected one!)
      if (selectedmic!=null)
        setpixel(selectedmic.getx(), selectedmic.gety(), bluecolor);
    }


    // -------------------- methods for the microbes (in german: "Methoden für die Mikroben" ;) ) --------------------

    /**
    * creates a new microbe
    * @param x x position
    * @param y y position
    * @param start_energy start energy for thge new microbe
    * @param start_energyloss energy loss per step
    * @return new microbe
    */
    public Microbe new_microbe(int x, int y, int start_energy, int start_energyloss)
    {
      if (x<1) x=1;
      if (y<1) y=1;
      if (x>=playfield_width-1) x=playfield_width-2;
      if (y>=playfield_height-1) y=playfield_height-2;

      Microbe mic=new Microbe(this, x, y, start_energy, start_energyloss);
      microbes.addElement((Object) mic);

      return mic;
    }

    /**
    * removes a microbe by its reference
    * @param mic microbe to remove
    */
    private void remove_microbe(Microbe mic)
    {
      if (mic!=null)
      {
        if (mic==selectedmic)
          selectedmic=null;
        setpixel(mic.getx(), mic.gety(), backcolor);
        microbes.removeElement(mic);
      }
    }

    /**
    * removes the microbe at the position (x,y) on the playfield
    * @param x x position
    * @param y y position
    */
    public void remove_microbe(int x, int y)
    {
      Microbe mic;
      int i;

      for(i=0; i<microbes.size(); i++)
      {
        mic=(Microbe) microbes.elementAt(i);
        if (mic.getx()==x && mic.gety()==y)
          remove_microbe(mic);
      }
    }

    /** removes all microbes */
    void remove_allmicrobes()
    {
      while (microbes.size()>0)
        remove_microbe((Microbe) microbes.firstElement());
    }

    /**
    * returns the selected microbe
    * @return selected microbe
    */
    public Microbe get_selectedmic()
    {
      return selectedmic;
    }

    /**
    * selects the microbe which is next to the position (x,y)
    * @param x x position
    * @param y y position
    */
    void set_selectedmic(int x, int y)
    {
      Microbe mic, minmic=null;
      int mindiff= new Integer(new Integer(0).MAX_VALUE).intValue();
      int diff=0;

      for(int i=0; i<microbes.size(); i++)
      {
        mic=(Microbe) microbes.elementAt(i);
        diff=Math.abs(mic.getx()-x)+Math.abs(mic.gety()-y);
        if (diff<mindiff)
        {
          mindiff=diff;
          minmic=mic;
        }
      }

      if (minmic!=null)
      {
        if (selectedmic!=null) // if there was an other microbe selected, draw its normal state
          draw_microbe(selectedmic);

        selectedmic=minmic;

        setpixel(selectedmic.getx(), selectedmic.gety(), bluecolor); // paint the selected microbe with blue color
      }
    }

    /**
    * returns the current number of microes
    * @return microbe count
    */
    int get_microbe_count()
    {
      return microbes.size();
    }
}
