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

import processing.pdf.*; 
import processing.opengl.*; 

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

public class MIT4_112F12_Code_Ex1_PH extends PApplet {

class World {
  Unit[] units;
  ArrayList<Unit> conn; //master
  ArrayList<Unit> conn1; //0-0 ends connections
  ArrayList[] master_conn;
  int leng = 15;
  ArrayList<Unit[]> allconn;
  
  
  World(int num, float[] bum) {
    units = new Unit[num];
    conn = new ArrayList<Unit>();
    master_conn = new ArrayList[3]; //0 is 0-0, 1 is 1-0, 2 is 2-1 connections
    Unit[] blah;
    blah = new Unit[2];
    allconn = new ArrayList<Unit[]>();
    
    for( int i=0; i<3; i++)
    {
      master_conn[i]=conn;
    }
    conn1 = new ArrayList<Unit>();
    ArrayList already_conn= new ArrayList<Unit> ();
  }

  public void addUnits(int i, Unit u) {
    units[i] = u;
  }

  public void update() {
   for (int i =0; i < units.length; i++) {
      
      units[i].boundary();
      
      units[i].update();
      
      units[i].compute_acceleration();
      
   }
   for (int i =0; i < units.length; i++) {
      units[i].connections(this);
      
    }
  sticky_pairs();    
  }

  public void display() {
    //unit display
    for (int i = 0; i < units.length; i++) {
      units[i].display();
    }
    //normal conn
    for (int i = 0; i < conn.size(); i+=2) {
      for (int j=0; j < conn.get(i).posit.size(); j++) {
      int pos1 = (conn.get(i).posit.get(j))[0];
      int pos2 = (conn.get(i).posit.get(j))[1];
      PVector posA = conn.get(i).ends[pos1];
      PVector posB = conn.get(i+1).ends[pos2];
      
      stroke(0,0,0,20); //red strongest
      if(pos1==0) //green weakest
      {stroke(0,0,0,20);}
      if(pos1==1) //blue medium
      {stroke(0,0,0,20);}
      
      line(posA.x, posA.y, posB.x, posB.y);
    }
    conn.get(i).posit.clear();
    }
    conn.clear();
  }
    
    public PVector compute_attraction(PVector centerloc, PVector loc1, PVector loc2, float mass, float  _fac, float ang, float other_ang) {
    PVector acc = new PVector(0,0,0);
    //f = (((.5r)^-5)*-5) - ((r^-3)*-3) // Lennard Jones style potential
    PVector dirs = PVector.sub(loc2, loc1);
    dirs.normalize();
    float distances = PVector.dist(loc2, loc1);
    //PVector acc = new PVector(0, 0, 0);
    float force = 0;
    force = (pow(_fac * distances, -5) * -5) + (pow(distances, -3)*10); //  fac can control how "strong the bond is" will be helpful later
    acc = PVector.mult(dirs, (force / mass )*80); // dividing by mass...
    acc.limit(1);
    
    return acc;
}
  public float compute_torque(PVector centerloc, PVector loc1, PVector loc2, float mass, float  _fac, float ang, float other_ang){
  PVector dirs = PVector.sub(loc2, loc1);
  dirs.normalize();
  
  PVector orient= PVector.sub(loc2, centerloc);
  float distances = PVector.dist(loc2, loc1);
  float force = (pow(_fac * distances, -5) * -5) + (pow(distances, -3)*3);
  
  float torque = (orient.cross(PVector.mult(dirs, force))).mag();
  ang += torque/mass/1000;
  return ang;
  
    
  }
    


  public void sticky_pairs()
  {
    for(int i=0; i<allconn.size(); i++)
    {
      allconn.get(i)[0].vel=allconn.get(i)[1].vel;
      allconn.get(i)[0].angvel=allconn.get(i)[1].angvel;
    }
    allconn.clear();
    
    
  }
}

class Unit {

  //Data members
  PVector loc;
  PVector vel;
  float ang;
  float lnx;
  float lny;
  float leng=25;
  PVector[] ends;
  int pos = -1;
  ArrayList<int[]> posit = new ArrayList<int[]> ();
  int id;
  float angvel;
  boolean lock;
  Unit[] blah;
  
  //Constructors
  Unit(PVector loc, PVector v, float ang, int p) {
    angvel=0;
    this.loc = loc;
    vel = v;
    this.ang = ang;
    ends = new PVector[3];
    id=p;
    ends[0]=new PVector(loc.x+leng*cos(ang), loc.y+leng*sin(ang), 0);
    ends[1]=new PVector(loc.x+leng*cos(ang+2*PI/3), loc.y+leng*sin(ang+2*PI/3), 0);
    ends[2]=new PVector(loc.x+leng*cos(ang+4*PI/3), loc.y+leng*sin(ang+4*PI/3), 0);
    lock = false;
    
    blah = new Unit[2];
  }

  public void display() {
    //fill (0);
    stroke(0,0,0,15);
    line(loc.x, loc.y, 0, ends[0].x, ends[0].y, 0);
    line(loc.x, loc.y, 0, ends[1].x, ends[1].y, 0);
    line(loc.x, loc.y, 0, ends[2].x, ends[2].y, 0);
    
  }

  public void update() {
    //if (abs(vel.x)<2) vel.x+=random(-.5, .5);
    //if (abs(vel.y)<2) {vel.y+=random(-.5, .5);}
//else
    //{ 
      //if (vel.x<0) vel.x= -(abs(vel.x)+random(-1, 0));
      //else vel.x+=random(-1, 0);
      //if (vel.y<0) vel.y= -(abs(vel.y)+random(-1, 0));
      //else vel.y+=random(-1, 0);
    //}
    
    loc.x+=vel.x; //random is random movement. Also include actual "velocity" vectors
    loc.y+=+vel.y;
    ang+=angvel; 
    
    ang=ang%360.0f;
    
    
    
    //ang+=random(-.05, .05);

    ends[0]=new PVector(loc.x+leng*cos(ang), loc.y+leng*sin(ang), 0);
    ends[1]=new PVector(loc.x+leng*cos(ang+2*PI/3), loc.y+leng*sin(ang+2*PI/3), 0);
    ends[2]=new PVector(loc.x+leng*cos(ang+4*PI/3), loc.y+leng*sin(ang+4*PI/3), 0);
    
    
  }

  public void connections(World w) { // draws the lines in between close ends
    for (int i=0; i<w.units.length; i++) { //goes through list of units
      float minDist = 100000; //sets a ridiculously high number. 
      for (int j=0; j<3; j++) { //goes through the 3 ends of the unit we are looking at now
        for (int k=0; k<3; k++) { //goes through the 3 ends of a different unit
          boolean condition = ((j==0&&k==0)||(j==1&&k==1)||(j==2&&k==2));
          if (condition){
          float distance = ends[j].dist(w.units[i].ends[k]); 
          
          if (distance < minDist && distance < minDistDir[j]&& distance!=0) 
          {
            PVector orientation = PVector.sub(ends[j],loc);
            float angle_bw=100;
            float angle_bw2=100;
            //float angle_bw = PVector.dot(PVector.sub(w.units[i].ends[k],ends[j]),orientation)/(distance*leng);
            angle_bw = acos(orientation.dot(PVector.sub(w.units[i].ends[k],ends[j]))/(distance*leng));
            angle_bw2 = acos(PVector.sub(w.units[i].ends[k],w.units[i].loc).dot(PVector.sub(ends[j],w.units[i].ends[k]))/(distance*leng));
//println(angle_bw);
            //angle_bw2 = acos(w.units[i].ends[k].dot(PVector.sub(w.units[i].ends[k],ends[j]),orientation)/(distance*leng));
            if(angle_bw<PI/6&&angle_bw2<PI/6)
            {
            minDist = distance; //new minimum distance
            pos = j;            //pos is the position in the array of 3 ends for each the unit
            w.units[i].pos = k; //units[i].pos takes the units array and and tells which end to draw the line from
            int[] posi;
            posi= new int[2]; posi[0]=j; posi[1]=k;
            posit.add(posi);
            w.conn.add(this); //this unit we are looking at! 
            w.conn.add(w.units[i]);
 
            blah[0]=this;
            blah[1]=w.units[i];
            w.allconn.add(blah);
            
            
            //vel=w.units[i].vel;
          }}}
        }
      }
      //if (minDist < minimumDistance) { 
        //w.conn.add(this);
       // w.conn.add(w.units[i]);
      }
  }

  public void boundary() {
    if (loc.x>width+20 || loc.x<0-20) vel.x=-(vel.x); //this changes
    if (loc.y>height+20 || loc.y<0-20) vel.y=-(vel.y);
  }
  
  public void compute_acceleration() {
    float factor = 1;
    int angtemp=0;
    PVector acc = new PVector(0, 0,0);
    PVector other_loc = new PVector(0, 0, 0);
    for (int i = 0 ; i < num-1; i ++) {
      for (int j = 0; j< 3; j++) {
        for (int k = 0; k<3; k++) {
      if (id != i) {
        if (j==pairings[0]&&k==pairings[1]) {factor = .5f;} //weakest green
        if (j==pairings[2]&&k==pairings[3]) {factor = .08f;} //medium blue
        if (j==pairings[4]&&k==pairings[5]) {factor = .028f;} //strongest red
        other_loc = w.units[i].ends[k];
        float other_ang = w.units[i].ang;
        angtemp += w.compute_torque(loc, other_loc, ends[j], 5, factor, ang, other_ang);
        acc.add(w.compute_attraction(loc, other_loc, ends[j], 5, factor, ang, other_ang));
      }
    } } }
    angvel +=angtemp;
    angvel = angvel%360.0f;
    vel.add(acc);
    println(angvel);
}}


//Attempt 22** - we try to keep pernament bonds. we manage to get some nice things. I like this program! Keep this and the original hairball. 
//Attempt 10* - original code without history. ** 
//Attempt 21** - original with history, no pernament bonds but restricted connections
//Attempt 9 - is identical to 10.. 
//Attempt 7 - original from midreview

//Change number? Change conditions! Fix the PDF converting part. 

 
// or  import processing.dxf.*;
int num = 100;
World w;
float minimumDistance = 20;
float minimumDistance1=10; //between 0 end and 0 end
float[] minDistDir; 
int[] pairings;
int number = 0;
boolean record = true;
boolean endrecord = false;

int counter=0;

public void setup() {
  size(1000, 750, OPENGL);
  background(255);
  smooth();
  
  minDistDir= new float[3];
  minDistDir[0]=70; minDistDir[1]=50; minDistDir[2]=30;
  //green, blue, red
  
  pairings = new int[6];
  pairings[0]=0;pairings[1]=0;
  pairings[2]=0;pairings[3]=1;
  pairings[4]=2;pairings[5]=2;

  w = new World(num, minDistDir);
 
  for (int i = 0; i < num; i++) {
    PVector loc = new PVector(random(0, width), random(0, height), 0);
    PVector vel = new PVector(random(-.5f, .5f), random(-.5f, .5f));
    float angle = random(0, 2*PI);
    int dir = 1;
    if (random(-1, 1)<0) dir=-1;

    w.addUnits(i, new Unit(loc, vel, dir*angle,i));
  }
}

public void draw() { 
  //if (record) {
    //number++;
    //beginRaw(PDF, "output"+str(number)+".pdf");
  //if(counter%50==0){
  //background(255);
  counter=0;
  //}
  w.update();
  w.display();
  if (endrecord) {
  endRaw();
  endrecord=false;
  exit(); }
  counter++;
}

public void keyPressed() {
 if (key == 'q') {
 record=true; }
 
 if (key == 'p') {
   endrecord = true;}
}
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "--full-screen", "--bgcolor=#666666", "--stop-color=#cccccc", "MIT4_112F12_Code_Ex1_PH" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
