package es_problems;


import evolution.*;
import java.awt.*;
import java.lang.Math;
import java.util.Random;

/** solve NxN magic squares using evolution strategy with a 
 ** problem specific mutation operator
 ** @version 0.1
 ** @author  hthadler
 **/
public class MagicSquare extends ESgui implements ESproblem
{
  static protected Random randgen = new Random();  //random-number generator
  static protected Double optimal = new Double(0); //optimum
  static protected Font   nfont   = new Font("TimesRoman",Font.PLAIN,7); //number font

  int big_n=4; //default 4x4 magic square 

  /**
   **/
  public ESpopulation startpopulation()
  {
     double [] opars = new double [big_n*big_n];
     double [] spars = new double [big_n*big_n];

     for(int i=1; i<= big_n*big_n; i++)
     {
        opars[i-1]=i;
        spars[i-1]=3;
     }

    int num_parents=10;
    ESchromosome [] s = new ESchromosome[num_parents];
    for(int i=0; i<num_parents; i++)
      s[i]=new ESchromosome( opars, spars, big_n*big_n );

    return new ESpopulation(s, num_parents, 40, ESchromosome.recombinate_id_discrete,
                            1, 1.3, ESpopulation.children_only, this, true);
   
  }

  /**
   **/
  public Dimension dimension_of_drawing_area()
  {
    int num_digits=0;  
    double how_many_digits = (big_n*(big_n*big_n+1))/2;
    for(int i=0; how_many_digits >= 1; i++)
    {
       how_many_digits/=10;
       num_digits++;
    }
    if( num_digits < 3 )
      num_digits = 3;
    int box_dim = (num_digits+1)*nfont.getSize();
    return new Dimension((big_n+2)*box_dim,(big_n+2)*box_dim); 
  }

  /** MagicSquare specific mutation operator
   **/
  public ESchromosome specific_mutation(ESchromosome c) 
  {
    double [] cd = c.get_object_parameters();
    double [] sd = c.get_strategy_parameters();
    int i,p1,p2;
    double dlen,px;
    double [] op = new double [c.length];
    double [] sp = new double [c.length];
 
    // get a copy of the Chromosome
    dlen = (double)c.length;
    for(i=0; i<c.length; i++)
    {
      op[i]=cd[i];
      sp[i]=sd[i];
    }

    // swap n numbers where n is value of sp[0]
    for( int j=0; j<sp[0]; j++)
    {
      //indices of numbers to be swapped
      p1 = (int)(dlen*randgen.nextDouble());

      // search for a number within op[p1]-sp[p1] and op[p1]+sp[p1];
      double range = sd[p1];
      do 
      {
        p2 = (int)(dlen*randgen.nextDouble());
      }while( !((op[p1]-range) <= op[p2] && op[p2] <= (op[p1]+range)) );
 
      //swap two numbers 
      if( p1 != p2 && p1 >= 0 && p1<c.length && p2 >= 0 && p2<c.length)
      {
        px=op[p1]; op[p1]=op[p2]; op[p2]=px;
      }
    }
    
    //adapt strategy-parameters
    double r=randgen.nextDouble();
    if( r > 0.66 )
      sp[0]+=1;
    else if( r > 0.33 )
      sp[0]-=1;

    if(sp[0] < 1)
      sp[0]=1;

    for(i=1; i<big_n*big_n; i++)
      sp[i]=sp[0];

    return new ESchromosome( op, sp, c.length );
  }

  /** magicSquare optimum 
   **/
  public Double optimum()
  {
    return optimal;
  }




  /** this method returns the fitness measure for a given chromosome
   ** @return fitness of chromosome
   **/
  public double calc_quality(ESchromosome c)
  {
    double [] cd   = c.get_object_parameters();
    double S       = 0.5*big_n*(big_n*big_n+1);
    double quality = 0.0;
    double s;

    //rows
    for(int i=0; i<big_n; i++)
    {
      s=0;
      for(int j=0; j<big_n; j++)
      {
        s+=cd[i*big_n+j];
      }
      quality += (S-s)*(S-s);
    }

    //columns
    for(int i=0; i<big_n; i++)
    {
      s=0;
      for(int j=0; j<big_n; j++)
      {
        s+=cd[j*big_n+i];
      }
      quality += (S-s)*(S-s);
    }
    

    //diagonal top,left - bottom,right
    s=0;
    for(int i=0; i<big_n; i++)
      s+=cd[i*big_n+i];
    quality += (S-s)*(S-s);

    //diagonal bottom,left - top,right
    s=0;
    for(int i=0; i<big_n; i++)
      s+=cd[(big_n-i-1)*big_n+i];
    quality += (S-s)*(S-s);

    return(quality);
  }



  /** draw a given chromosome using Graphics object g
   ** @param g Graphics object @see java.awt.Graphics
   ** @param c chromosome to be drawn
   **/
  public void draw_single(Graphics g, ESchromosome c)
  {
    int [] colors = new int [big_n*big_n];
    int [] rows   = new int [big_n];
    int [] cols   = new int [big_n];
    int [] diag   = new int [2];
    
    int magic_sum = (big_n*(big_n*big_n+1))/2;  
    double [] cd  = c.get_object_parameters();

    // get row sums
    int s=0;
    for(int i=0; i<big_n; i++)
    {
      s=0;
      for(int j=0; j<big_n; j++)
        s+=(int)cd[i*big_n+j];
      rows[i]=s;
    }
    // get column sums
    for(int i=0; i<big_n; i++)
    {
      s=0;
      for(int j=0; j<big_n;j++)
        s+=(int)cd[j*big_n+i];
      cols[i]=s; 
    }
    // get diagonal sums
    s=0;
    for(int i=0; i<big_n; i++)
      s+=(int)cd[i*big_n+i];
    diag[0]=s;
    s=0;
    for(int i=0; i<big_n; i++)
      s+=(int)cd[i*big_n+(big_n-i-1)];
    diag[1]=s;


    // get colors
    // cols: 1 (blue), rows: 2(green), diag: 4(red)
    for(int i=0; i<big_n; i++)
    {
      if( rows[i] == magic_sum ) 
      {
        for(int j=0; j<big_n; j++)
          colors[i*big_n+j]+=2;
      }  
      if( cols[i] == magic_sum )
      {
        for(int j=0; j<big_n; j++)
          colors[j*big_n+i]+=1;
      }
    }
    if(diag[0]==magic_sum)
      for(int i=0; i<big_n; i++)
        colors[i*big_n+i]+=4;
 
    if(diag[1]==magic_sum)
      for(int i=0; i<big_n; i++)
        colors[i*big_n+(big_n-i-1)]+=4;


    // get dimension for a single number-box
    int num_digits=0;
    double how_many_digits = (double)(magic_sum);
    for(int i=0; how_many_digits >= 1; i++)
    {
       how_many_digits/=10;
       num_digits++;
    }
    if( num_digits < 3 )
      num_digits = 3;
    int box_dim = (num_digits+1)*nfont.getSize();
    int y_off   = 4;
    int x_off   = 4;

    g.drawRect(0,0,
                dimension_of_drawing_area().width-1,
                dimension_of_drawing_area().height-1);

    // draw colored boxes
    for(int i=0; i<big_n; i++)
    {
      for(int j=0; j<big_n; j++)
      {
        switch(colors[i*big_n+j])
	{
	  case 0: g.setColor(Color.white);
            break;
	  case 1: g.setColor(Color.blue);
            break;
	  case 2: g.setColor(Color.blue);
            break;
	  case 3: g.setColor(Color.green);
            break;
	  case 4:
	  case 5: 
	  case 6:
	  case 7: g.setColor(Color.red);
            break;
	  default: g.setColor(Color.red);
            break;
	}
        g.fillRect(box_dim+i*box_dim, box_dim+j*box_dim,
                   box_dim, box_dim);
        g.setColor(this.getBackground());
        g.fillRect(box_dim+i*box_dim+4, box_dim+j*box_dim+4,
                    box_dim-7,box_dim-7);
        g.setColor(this.getForeground());
        g.drawRect(box_dim+i*box_dim, box_dim+j*box_dim,
                   box_dim, box_dim);
        g.drawString(""+((int)cd[i*big_n+j]), 
                      box_dim+i*box_dim + x_off, 
                      box_dim+(j+1)*box_dim-y_off);
      }
    }

    // draw column sums
    for(int i=0; i<big_n; i++)
      g.drawString(""+cols[i], (big_n+1)*box_dim+x_off, (i+2)*box_dim-y_off);

    // draw row sums
    for(int i=0; i<big_n; i++)
      g.drawString(""+rows[i], (i+1)*box_dim+x_off,   (big_n+2)*box_dim-y_off);

    //draw diagonal sums
    g.drawString(""+diag[0], (big_n+1)*box_dim+x_off, (big_n+2)*box_dim-y_off);
    g.drawString(""+diag[1], x_off            , (big_n+2)*box_dim-y_off);


  }

  public void init()
  {
    try { big_n = (int)Integer.parseInt(getParameter("n")); }
    catch ( NumberFormatException e) { big_n = 4; } 

    super.init();
  }

  public boolean handleEvent(Event e)
  {
    //handle problem-specific events here
    return super.handleEvent(e);
  }

  /** 
  **/
  public String[][] getParameterInfo()
  {
    String[][]info=
    { // name, type, description
      {"n",  "integer", "n × n magic square"}
    };
    return info; 
  }

}