import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class programming extends PApplet {

//Declare the object: path, drawPolygon and brush

Path path;
drawPolygon pol;
boolean change [] = {true, true, true, true, true, true, true, true};
boolean reset = true;
ArrayList<Brush> brush;

  float colorChange_1;
  float colorChange_2;
  float sideChange; 
  float max_distance;
  int[] x_pos ={width/2,width*118/20};
  int[] y_pos ={height/2,height*3/2,height*5/2,height*7/2};



public void setup(){
  
  newPath();
  //pol = new drawPolygon();
  brush = new ArrayList<Brush>();
  for (int i = 0; i<5*height/8; i++){
  newBrush(width/2, height);
  }
}


// Construct the object and setting up the mouseClicked field to trigger different change of the shapes

public void draw() {
  
  //saveFrame("pol_#####.jpg");
   if (mouseX>height/4 && mouseX<height){
  reset = true;
  }else{
   reset = false;
  }
  
  
   if (mouseX<height/4 && mouseY<height/4){
  change[0] = true; 
 }else {
  change[0] = false;
 }  
 
  if (mouseX<height/4 && mouseY>height/4 && mouseY<height/2){
  change[1] = true; 
 }else {
  change[1] = false;
 } 
 
  if (mouseX<height/4 && mouseY>height/2 && mouseY<3*height/4){
  change[2] = true;
 }else {
  change[2] = false;
 }
 
  if (mouseX<height/4 && mouseY>3*height/4){
  change[3] = true; 
 }else {
  change[3] = false;
 }  
 
  if (mouseX>54*height/40 && mouseY<height/4){
  change[7] = true; 
 }else {
  change[7] = false;
 } 
 
  if (mouseX>54*height/40 && mouseY>height/4 && mouseY<2*height/4){
  change[6] = true; 
 }else {
  change[6] = false;
 } 
 
  if (mouseX>54*height/40 && mouseY>height/2 && mouseY<3*height/4){
  change[5] = true; 
 }else {
  change[5] = false;
 } 
 
  if (mouseX>54*height/40 && mouseY>3*height/4){
  change[4] = true; 
 }else {
  change[4] = false;
 }
 
  background(25);
  path.display();
  //pol.display();
  
  noFill();
  float sideChange1 = map(mouseY, height/8, 7*height/8, 3, 6);
  float sideChange2 = map(mouseY, 7*height/8, height/8, 7, 10);
  float colorChange_1 = map(mouseY, 0, height, 100, 200);
  float colorChange_2 = map(mouseX, 0, 12*height/8, 200, 100);
  max_distance = dist(0, 0, width, height);
  fill(colorChange_2, colorChange_1, 200);
  noStroke();
  //stroke will change the color with mouse move
  for(int i = 0; i <=1; i++){
    for(int j = 0; j<=3; j++){
     float size = dist(mouseX, mouseY, x_pos[i], y_pos[j])/10; 
      size = max_distance/size;
      if (size>height/10){
       size=height/10;
      }
      if (size<height/20){
       size=height/20;
      }
      drawPolygon temp = new drawPolygon(x_pos[0], y_pos[j], size, sideChange1);
      temp.display();
      drawPolygon temp2 = new drawPolygon(x_pos[1], y_pos[j], size, sideChange2);
      temp2.display();
    }
  }
  
  for (Brush br : brush){
    br.applyBehaviors(brush,path);
    br.run();
  }

 fill(255);
}


// Define the position of Point in the path

public void newPath() {
  path = new Path();
  float angle = radians(180);
  float PolRadius =height*3/8;
  for (float a=0; a<radians(360); a=a+angle){
  path.addPoint(width/2+cos(a)*PolRadius,height/2+sin(a)*PolRadius);
  }
}


// Define the action after the mouseclick

public void mouseClicked(){
  
  if (reset == true){
      path = new Path();
  float angle = radians(180);
  float PolRadius =height*3/8;
  for (float a=0; a<radians(360); a=a+angle){
  path.addPoint(width/2+cos(a)*PolRadius,height/2+sin(a)*PolRadius);
  }
}  
  
  
  for (int c=0; c<=7; c++){
  if (change[c] ==true){
  path = new Path();
  float[] angle = {radians(360)/3, radians(360)/4, radians(360)/5, radians(360)/6, radians(360)/7, radians(360)/8, radians(360)/9, radians(360)/10};
  float PolRadius =height*3/8;
  int i=c;
  for (float a=0; a<radians(360); a=a+angle[i]){
  path.addPoint(width/2+cos(a)*PolRadius,height/2+sin(a)*PolRadius);
      }
    }
  }
}
  

public void newBrush(float x, float y) {
  float maxspeed = random(2,10);
  float maxforce = 0.3f;
  brush.add(new Brush(new PVector(x,y),maxspeed,maxforce));
}

class Brush{
  
  PVector location;
  PVector velocity;
  PVector acceleration;
  float r;
  float maxforce;
  float maxspeed;
  
  Brush(PVector l, float ms, float mf){
    location = l.get();
    r = 5;
    maxspeed = ms;
    maxforce = mf;
    acceleration = new PVector (0, 0);
    velocity = new PVector(0, 0);
  }
  
 public void applyBehaviors(ArrayList brush, Path path){
   PVector f = follow(path);
   PVector s = separate(brush);
   f.mult(1);
   s.mult(0.5f);
   applyForce(f);
   applyForce(s);
 }
 
 public void applyForce(PVector force){
   acceleration.add(force);
 }
 
 public void run(){
  update();
  borders();
  render();
 }
  
  public PVector follow(Path p) {
    PVector predict =velocity.get();
    predict.normalize();
    predict.mult(15);
    PVector predictLoc = PVector.add(location, predict);
    
    PVector normal = null;
    PVector target = null;
    float worldRecord = 1000000;
    
    for (int i =0; i<p.points.size(); i++){
      
      PVector a = p.points.get(i);
      PVector b = p.points.get((i+1)%p.points.size());
      
      PVector normalPoint = getNormalPoint(predictLoc, a, b);
      
      PVector dir = PVector.sub(b, a);
      
      if (normalPoint.x < min(a.x,b.x) || normalPoint.x > max(a.x,b.x) || normalPoint.y < min(a.y,b.y) || normalPoint.y > max(a.y,b.y) ){
        normalPoint = b.get();
        
        a = p.points.get((i+1)%p.points.size());
        b = p.points.get((i+2)%p.points.size());
        dir = PVector.sub(b, a);
      }
      
      float d = PVector.dist(predictLoc, normalPoint);
      
      if (d < worldRecord){
        worldRecord = d;
        normal =normalPoint;
        
        dir.normalize();
        dir.mult(10);
        target = normal.get();
        target.add(dir);
      }
    } 
       
      if (worldRecord > p.radius) {
      return seek(target);
    } 
    else {
      return new PVector(0, 0);
      }
    }
    
    public PVector getNormalPoint(PVector p, PVector a, PVector b){
      
      PVector ap = PVector.sub(p, a);
      PVector ab = PVector.sub(b, a);
      
      ab.normalize();
      ab.mult(ap.dot(ab));
      PVector normalPoint = PVector.add(a, ab);
      return normalPoint;
    }
      
   public PVector separate (ArrayList boids) {
     float desiredseparation = r*1.25f;
     PVector steer = new PVector(0, 0);
     int count = 0;
     
     for (int i = 0; i < boids.size(); i++){
       Brush other = (Brush) boids.get(i);
       float d = PVector.dist(location, other.location);
       if ((d>0) && (d<desiredseparation)){
         PVector diff = PVector.sub(location, other.location);
         diff.normalize();
         diff.div(d);
         steer.add(diff);
         count++;
       }
     }
     
     if (count>0){
       steer.div((float)count);
     }
     
     if (steer.mag() > 0) {
      steer.normalize();
      steer.mult(maxspeed);
      steer.sub(velocity);
      steer.limit(maxforce);
     }
     return steer;
    }


  
    public void update() {
      velocity.add(acceleration);
      velocity.limit(maxspeed);
      location.add(velocity);
      acceleration.mult(0);
    }


    public PVector seek(PVector target) {
      PVector desired = PVector.sub(target, location);  
      desired.normalize();
      desired.mult(maxspeed);
      PVector steer = PVector.sub(desired, velocity);
      steer.limit(maxforce);  
      return steer;
    }


    public void render() {
      fill(255);
      noStroke();
      pushMatrix();
      translate(location.x, location.y);
      ellipse(0, 0, r, r/2);
      popMatrix();
    }

    public void borders() {
      if (location.x < -r) location.x = width+r;
      if (location.x > width+r) location.x = -r;
   }
  }
  
  class Path{
  
  ArrayList<PVector> points;
  float radius;
  
  Path() {
    radius = height/20;
    points = new ArrayList<PVector>();
  }
  
  public void addPoint(float x, float y) {
    PVector point = new PVector (x,y);
    points.add(point);
  }
 
  public void display(){
    noFill();
    noStroke();
    strokeWeight(radius*2);
    beginShape();
    for (PVector v : points) {
      vertex(v.x, v.y);
    }
    endShape(CLOSE);
  }
 
} 

class drawPolygon{

float x;
float y;
float radius;
float points_number;

drawPolygon(float _x, float _y, float _r, float _points)
{
  x = _x;
  y = _y;
  radius = _r;
  points_number = _points;
}

public void display()
{
  float angle = radians(360)/points_number;
  beginShape();
  for (float a=0; a<radians(360); a=a+angle){
  float sx = x +cos(a)*radius; 
  float sy = y +sin(a)*radius;
  vertex(sx, sy);
  }
  endShape(CLOSE);
}
}
  

      
      
//Each polygon will change the size with mouse move
     
//build up polygon 0, 4, 12, 20 and 24 which can change the number of sides with the mouse move
//the rotating polygon 12 will pause rotating when the mouse press, while it releases the rotation will keep going.
  public void settings() {  size(640, 400); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "programming" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
