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

import processing.serial.*; 

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 LEDTreeDataMap extends PApplet {

// Mapping utility for Silicon Chip Stackable Tree
// Use mouse to construct tree and toggle LEDs
// if a matching board layout is connected via
// serial, it can be controlled too.



Serial TreeControl;
int serialIndex=0;
String portName="-port-";
boolean portConnect=false;

int x_window=640;
int y_window=640;
int tx1=-59;
int ty1=-124;
int tr1=-1;
int tx2=0;
int ty2=-149;
int tr2=0;
int tx3=59;
int ty3=-124;
int tr3=1;
float d1=137;        //distance
float a1=-0.4441f;    //angle
float d2=149;        //distance
float a2=0;          //angle
float d3=137;        //distance
float a3=0.4441f;     //angle
float d4=100;         //distance
float a4=0;          //angle
float[] ledd= new float[8];    //distance of LED
float[] leda= new float[8];    //angle of LED
PImage tree;

int[] parent = new int [100];
int[] child1 = new int [100];
int[] child2 = new int [100];
int[] child3 = new int [100];
int[] treex = new int [100];    //relative to coordinate of 'root'
int[] treey = new int [100];    //relative to coordinate of 'root'
int[] treer = new int [100];    //# of octants from 'up'
int[] treeseq = new int [100];  //position in shift sequence
int[] seqid = new int [100];    //inverse of above
int[] treedepth =  new int [100];  //depth(height) of tree
boolean[] ledOn = new boolean[800];  //whether LED is on via serial
char hex[]= {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};   //for hex output

int nexttree=1;
boolean lastmouse;
boolean lastkey;
int printcode=-1;
int printkey =-1;
boolean showhelp=true;
float zoom =1;
String serialOutput="";
int lastt;

public void setup() {
    
  surface.setResizable(true);    //doesn't prevent maximise
  ledd[0]=22.8254244210267f;
  ledd[1]=54.571054598569f;
  ledd[2]=80.7774721070176f;
  ledd[3]=115.446957517295f;
  ledd[4]=125.003999936002f;
  ledd[5]=123.081273961558f;
  ledd[6]=76.6550715869472f;
  ledd[7]=31.0161248385416f;
  leda[0]=0.502843210927861f;
  leda[1]=0.240534247945904f;
  leda[2]=0.262994731680919f;
  leda[3]=0.244978663126864f;
  leda[4]=-0.00799982933988663f;
  leda[5]=-0.288343964065125f;
  leda[6]=-0.263963723625705f;
  leda[7]=-0.362544237264508f;
  treeseq[0]=1;
  seqid[1]=0;
  treedepth[0]=0;
  tree = loadImage("tree.png");
  background(color(192,192,192));      //cls
  imageMode(CORNER);
  treex[0]=x_window/2;
  treey[0]=y_window-1;
  treer[0]=0;
  noStroke();    //transparent stroke
  textSize(32); 
  if(Serial.list().length>0){portName=Serial.list()[0];}
  lastmouse=mousePressed;
  lastkey=keyPressed;
  lastt=millis();
}

public void draw(){
  int c,n,j,r;
  boolean thismouse,thiskey;
  //if((width!=640)||(height!=640)){surface.setSize(640, 640);}
  background(color(192,192,192));      //cls
  fill(color(0,0,0));
  text("Trees:"+str(nexttree),width-150,30);
  text("LEDs:"+str(nexttree*8),width-150,60);
  if(portConnect){
    fill(color(0,192,0));
  }else{
    fill(color(0,0,0));
  }
  text(portName,width-150,90);
  translate((width/2-320*zoom),(height-640*zoom));
  scale(zoom);
  for(j=0;j<nexttree;j++){
    drawTree(j);
    fill(color(255,255,255));
    for(r=0;r<8;r++){
      if(ledOn[j*8+r]){
        ellipse(treex[j]+PApplet.parseInt(ledd[r]*sin(radians(treer[j]*45)+leda[r])),treey[j]-PApplet.parseInt(ledd[r]*cos(radians(treer[j]*45)+leda[r])),10,10);
      }
    }
  }
  thismouse=mousePressed;
  thiskey=keyPressed;
  if((thiskey==true)&&(lastkey!=true)){
    printcode=keyCode;
    printkey=key;
    if(printkey=='h'){showhelp=!showhelp;}      
    if(printkey=='c'){generateOutput('c');}    //copy  
    //if(printkey=='d'){generateOutput('d');}    //debug print
    if(printkey=='t'){GClip.copy(serialOutput);}    //9600 baud HEX code copy to clipboard
    if(printkey=='q'){exit();}                  //quit!
    if(printcode==39){surface.setSize(width+20,height);if(width<100){surface.setSize(100,height);}}  
    if(printcode==37){surface.setSize(width-20,height);}  
    if(printcode==38){surface.setSize(width,height-20);if(height<100){surface.setSize(width,100);}}  
    if(printcode==40){surface.setSize(width,height+20);}  
    if(printkey==61){zoom=zoom*1.1f;if(zoom>3){zoom=3;}}  
    if(printkey==45){zoom=zoom/1.1f;if(zoom<0.1f){zoom=0.1f;}}  
    if(printkey=='.'&&portConnect==false){serialIndex++;}
    if(printkey==','&&portConnect==false){serialIndex--;if(serialIndex<0){serialIndex=0;}}
    if(serialIndex>Serial.list().length-1){serialIndex=Serial.list().length-1;}
    if(Serial.list().length>0){portName=Serial.list()[serialIndex];}
    if(printkey=='s'){
      if(portConnect==false){
        try{
          TreeControl=new Serial(this, portName, 9600);
          portConnect=true;      
        } catch(Exception e){
          portConnect=false;              //failed to connect        
        }
      }else{
        TreeControl.stop();
        portConnect=false;              
      }      
    }
  }
  lastkey=thiskey;
  if(thismouse  ==  true){
    c=color(0,0,0);
  }else{
    c=color(0,255,0);
  }
  //fill(c);  
  //text(str(mouseX)+","+str(mouseY)+":"+str(nexttree)+":"+str(printcode)+":"+str(printkey),100,100);    //debug data
  for(j=0;j<nexttree;j++){
    fill(color(0,255,0));
    n=checkTree(PApplet.parseInt((mouseX-width/2)/zoom+320),PApplet.parseInt((mouseY-height)/zoom+640),j);
    if((child1[j]!=0)&&(n==1)){n=0;}
    if((child2[j]!=0)&&(n==2)){n=0;}
    if((child3[j]!=0)&&(n==3)){n=0;}
    switch(n){
      case 1:
        ellipse(treex[j]+PApplet.parseInt(d1*sin(radians(treer[j]*45)+a1)),treey[j]-PApplet.parseInt(d1*cos(radians(treer[j]*45)+a1)),30,30);
        break;
      case 2:
        ellipse(treex[j]+PApplet.parseInt(d2*sin(radians(treer[j]*45)+a2)),treey[j]-PApplet.parseInt(d2*cos(radians(treer[j]*45)+a2)),30,30);
        break;
      case 3:
        ellipse(treex[j]+PApplet.parseInt(d3*sin(radians(treer[j]*45)+a3)),treey[j]-PApplet.parseInt(d3*cos(radians(treer[j]*45)+a3)),30,30);
        break;
      case 4:
        ellipse(treex[j]+PApplet.parseInt(d4*sin(radians(treer[j]*45)+a4)),treey[j]-PApplet.parseInt(d4*cos(radians(treer[j]*45)+a4)),30,30);
        break;
      case 5:        
      case 6:        
      case 7:        
      case 8:        
      case 9:        
      case 10:        
      case 11:        
      case 12:        
        if(ledOn[j*8+n-5]){fill(color(255,255,255));}
        ellipse(treex[j]+PApplet.parseInt(ledd[n-5]*sin(radians(treer[j]*45)+leda[n-5])),treey[j]-PApplet.parseInt(ledd[n-5]*cos(radians(treer[j]*45)+leda[n-5])),15,15);
        break;        
    }    
    if((thismouse==true)&&(lastmouse!=true)){        //clicked!
      if((mouseButton == LEFT)&&(nexttree<99)){          //left click, add, unless out of array space
      if(n==1){
        treex[nexttree]=treex[j]+PApplet.parseInt(d1*sin(radians(treer[j]*45)+a1));
        treey[nexttree]=treey[j]-PApplet.parseInt(d1*cos(radians(treer[j]*45)+a1));
        treer[nexttree]=treer[j]-1;  
        child1[j]=nexttree;
        parent[nexttree]=j;
        treedepth[nexttree]=treedepth[j]+1;
        nexttree++;
        rebuildTree();      //redo sequence
        }
        if(n==2){
        treex[nexttree]=treex[j]+PApplet.parseInt(d2*sin(radians(treer[j]*45)+a2));
        treey[nexttree]=treey[j]-PApplet.parseInt(d2*cos(radians(treer[j]*45)+a2));
        treer[nexttree]=treer[j];  
        child2[j]=nexttree;
        parent[nexttree]=j;
        treedepth[nexttree]=treedepth[j]+1;
        nexttree++;
        rebuildTree();      //redo sequence
        }
        if(n==3){
        treex[nexttree]=treex[j]+PApplet.parseInt(d3*sin(radians(treer[j]*45)+a3));
        treey[nexttree]=treey[j]-PApplet.parseInt(d3*cos(radians(treer[j]*45)+a3));
        treer[nexttree]=treer[j]+1;  
        child3[j]=nexttree;
        parent[nexttree]=j;
        treedepth[nexttree]=treedepth[j]+1;
        nexttree++;
        rebuildTree();      //redo sequence
        }
        if((n>4)&&(n<13)){
          ledOn[j*8+n-5]=!ledOn[j*8+n-5];
        }
      }
    }
  }
  if((thismouse==true)&&(lastmouse!=true)){        //clicked!
    if((mouseButton == RIGHT)&&(nexttree>1)){      //right click, delete, except root
    if(child1[parent[nexttree-1]]==nexttree-1){child1[parent[nexttree-1]]=0;}
    if(child2[parent[nexttree-1]]==nexttree-1){child2[parent[nexttree-1]]=0;}
    if(child3[parent[nexttree-1]]==nexttree-1){child3[parent[nexttree-1]]=0;}
    parent[nexttree-1]=0;
    for(r=0;r<8;r++){
      ledOn[(nexttree-1)*8+r]=false;    //turn off LED
    }
    nexttree--;
    rebuildTree();      //redo sequence
    }
  }
  lastmouse=thismouse;
  if(showhelp){        //show help over top
    fill(color(192,192,192),192);
    rect(10,10,620,620);
    fill(color(0,0,0));
    textSize(48); 
    text("HELP and TIPS:",30,50);
    textSize(26); 
    text("Hover over the top of a branch to see which",30,100);
    text("nodes are available.",30,130);
    text("Left click to add a node.",30,160);
    text("Right click to remove the last node.",30,190);
    text("The tree numbers are automatically updated.",30,220);
    text("Press 'h' to toggle help.",30,260);
    text("Press 'c' to copy Arduino code to the clipboard.",30,290);
    text("Use arrow keys to resize window.",30,320);
    text("Use +(=)/- to zoom.",30,350);
    text("Use <(,) and >(.) to cycle through serial ports.",30,390);
    text("Press 's' to start/stop a serial port connection.",30,420);
    text("Click on individual LEDs to toggle the LEDs off",30,450);
    text("and on (output using 9600 baud HEX mode).",30,480);
    text("Press 't' to copy serial command data",30,510);
    text("to the clipboard.",30,540);
    text("Press 'q' to quit.",30,570);
    text("If the layouts don't match, the LEDs won't!",30,610);
    textSize(32); 
  }
  serialOutput="v0000000000000000";      //purge any extra attached trees
  for(j=nexttree;j>0;j--){      //work backwards, last output is bottom tree
    for(n=0;n<nexttree;n++){
      if(treeseq[n]==j){
        serialOutput=serialOutput+hex[PApplet.parseInt(ledOn[n*8+4])*1+PApplet.parseInt(ledOn[n*8+5])*2+PApplet.parseInt(ledOn[n*8+6])*4+PApplet.parseInt(ledOn[n*8+7])*8];
        serialOutput=serialOutput+hex[PApplet.parseInt(ledOn[n*8+0])*1+PApplet.parseInt(ledOn[n*8+1])*2+PApplet.parseInt(ledOn[n*8+2])*4+PApplet.parseInt(ledOn[n*8+3])*8];        
      }
    }
  }  
  serialOutput=serialOutput+"V\r\n";
  if(millis()-lastt>250){
    if(portConnect){
      TreeControl.write(serialOutput);
    }
    lastt=millis();    
  }
}

public void generateOutput(int k){
  int i,j;
  String out="";
  out=out+"#define LED_BOARD_COUNT " + str(nexttree) + "\r\n";
  
  out=out+"int led_board_rotation[LED_BOARD_COUNT]={";
  for(i=1;i<nexttree+1;i++){out=out+str(treer[seqid[i]])+",";}
  out=out.substring(0,out.length()-1);    //drop trailing comma
  out=out+"};"+"\r\n";
  
  out=out+"int led_board_depth[LED_BOARD_COUNT]={";
  for(i=1;i<nexttree+1;i++){out=out+str(treedepth[seqid[i]])+",";}
  out=out.substring(0,out.length()-1);    //drop trailing comma
  out=out+"};\r\n";  
  
  out=out+"int led_board_x_coord[LED_BOARD_COUNT]={";
  for(i=1;i<nexttree+1;i++){out=out+str(treex[seqid[i]])+",";}
  out=out.substring(0,out.length()-1);    //drop trailing comma
  out=out+"};\r\n";  
  
  out=out+"int led_board_y_coord[LED_BOARD_COUNT]={";
  for(i=1;i<nexttree+1;i++){out=out+str(treey[seqid[i]])+",";}
  out=out.substring(0,out.length()-1);    //drop trailing comma
  out=out+"};\r\n\r\n";  
  
  out=out+"#define LED_PIXEL_COUNT " + str(nexttree*8) + "\r\n";

  out=out+"int led_pixel_x_coord[LED_PIXEL_COUNT]={";
  for(i=1;i<nexttree+1;i++){
    if((i&1)==1){out=out+"\r\n\t";}      //pad lines
    for(j=0;j<8;j++){
      out=out+str(treex[seqid[i]]+PApplet.parseInt(ledd[j]*sin(radians(treer[seqid[i]]*45)+leda[j])))+",";
    }
  }
  out=out.substring(0,out.length()-1);    //drop trailing comma
  out=out+"\r\n\t};\r\n";  
  
  out=out+"int led_pixel_y_coord[LED_PIXEL_COUNT]={";
  for(i=1;i<nexttree+1;i++){
    if((i&1)==1){out=out+"\r\n\t";}      //pad lines
    for(j=0;j<8;j++){
      out=out+str(treey[seqid[i]]-PApplet.parseInt(ledd[j]*cos(radians(treer[seqid[i]]*45)+leda[j])))+",";
    }
  }
  out=out.substring(0,out.length()-1);    //drop trailing comma
  out=out+"\r\n\t};\r\n\r\n";  

  if(k=='c'){GClip.copy(out);}
  if(k=='d'){print(out);}
}

public void drawTree(int n){
  pushMatrix();
  translate(treex[n],treey[n]);
  rotateZ(radians(treer[n]*45));
  image(tree,0-(tree.width)/2,0-tree.height);
  translate(60,110);
  if(treeseq[n]>9){translate(-10,0);}      //room for 2 digits
  fill(color(0,0,0));
  text(str(treeseq[n]),0-(tree.width)/2,0-tree.height);
  popMatrix();
}

public void rebuildTree(){      //redo sequence
  int n=1, tree=0,k=0;       //start with tree0=node1, k for timeout
  int i;
  for(i=0;i<100;i++){treeseq[i]=0;}    //reset all
  treeseq[tree]=n;                     //root is always 1 
  while((n<nexttree)&&(k<300)){     //do all the trees
    k++;
    if((child1[tree]>0)&&(treeseq[child1[tree]]==0)){
      n++;
      tree=child1[tree];
      treeseq[tree]=n;
      seqid[n]=tree;
      continue;
    }
    if((child2[tree]>0)&&(treeseq[child2[tree]]==0)){
      n++;
      tree=child2[tree];
      treeseq[tree]=n;
      seqid[n]=tree;
      continue;
    }
    if((child3[tree]>0)&&(treeseq[child3[tree]]==0)){
      n++;
      tree=child3[tree];
      treeseq[tree]=n;
      seqid[n]=tree;
      continue;
    }
    tree=parent[tree];    //back up
  }
}

public int checkTree(int x, int y, int n){    //check if x,y is near tree n's extremity
  long thresh=15*15;    //radius^2 of detection
  long x0,y0;
  int retval=0;
  int r;
  x0=treex[n]+PApplet.parseInt(d1*sin(radians(treer[n]*45)+a1));
  y0=treey[n]-PApplet.parseInt(d1*cos(radians(treer[n]*45)+a1));
  if((x-x0)*(x-x0)+(y-y0)*(y-y0)<thresh){retval=1;}
  x0=treex[n]+PApplet.parseInt(d2*sin(radians(treer[n]*45)+a2));
  y0=treey[n]-PApplet.parseInt(d2*cos(radians(treer[n]*45)+a2));
  if((x-x0)*(x-x0)+(y-y0)*(y-y0)<thresh){retval=2;}
  x0=treex[n]+PApplet.parseInt(d3*sin(radians(treer[n]*45)+a3));
  y0=treey[n]-PApplet.parseInt(d3*cos(radians(treer[n]*45)+a3));
  if((x-x0)*(x-x0)+(y-y0)*(y-y0)<thresh){retval=3;}
  for(r=0;r<8;r++){
    x0=treex[n]+PApplet.parseInt(ledd[r]*sin(radians(treer[n]*45)+leda[r]));
    y0=treey[n]-PApplet.parseInt(ledd[r]*cos(radians(treer[n]*45)+leda[r]));
    if((x-x0)*(x-x0)+(y-y0)*(y-y0)<thresh/4){retval=5+r;}
  }

  return retval;
}
  public void settings() {  size(640, 640,P3D); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "LEDTreeDataMap" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
