import hypermedia.net.*;

import moonlander.library.*; //<>// //<>// //<>// //<>// //<>// //<>// //<>//
import processing.opengl.*;
// Minim must be imported when using Moonlander with soundtrack.
import ddf.minim.*;
import java.awt.Robot;
import java.awt.*;
import java.awt.image.*;
import processing.net.*; 
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.TreeMap;
import hypermedia.net.*;
Moonlander moonlander;
PShape horn;
PShape star;
PShape spitor;
PImage img_animals;
PShader feedback; 
PShader raymarch; 
PImage screenShot; 
PGraphics pg;
PImage sprites[];
UDP myClient; 
boolean simulation = true;
boolean isPlayer = true;
String[] texts = {
  "the Summer of 2001",
  "IVORY",
  "INSTANSSI",
  "2023",
  "the warm rain",
  "touching my lips",
  "the smell of grass",
  "the heat of the sun",
  "thank you for the memories"

};
ArrayList<Byte> lampdata;
void startLamp()  {
  lampdata = new ArrayList<Byte>();
  
    
    lampdata.add((byte)1); // Speksin versio aina yksi

    
    lampdata.add((byte)0); //Nick tag
    
    lampdata.add((byte)101); // e
    
    lampdata.add((byte)112); // p
    
    lampdata.add((byte)101); // e
    
    lampdata.add((byte)108);// l
    
    lampdata.add((byte)105); // i
    
    lampdata.add((byte)0); // Nick lopetus
}
void endLamp()  {
  if(!simulation){
    byte[] arr = new byte[lampdata.size()];
    for(int i=0; i<lampdata.size(); i++) {
      arr[i] = lampdata.get(i);
    }
    myClient.send(arr,  "10.10.69.2",9909);
  }
}
void setLamp(int lampNum, int r, int g, int b) {
  if(!simulation){
    lampdata.add((byte)1);
    lampdata.add((byte)lampNum);
    lampdata.add((byte)0);
    lampdata.add((byte)r);
    lampdata.add((byte)g);
    lampdata.add((byte)b);
  }
}
class SyncParameter {
  String Name;
  TreeMap<Float, Float> data;
  public SyncParameter(String n) {
    Name = n;
    data = new TreeMap<Float, Float>();
  }
}
Minim minim;
AudioPlayer player;
TreeMap<String, SyncParameter> parameters = new TreeMap<String, SyncParameter>();
JSONObject json;

double getValueFromRocket(String variableName)  {
  if(!isPlayer) {
    float time = (float)moonlander.getCurrentTime();
    float value = (float)moonlander.getValue(variableName);
    if(parameters.containsKey(variableName))  {
      SyncParameter syncParam = parameters.get(variableName);
      if(!syncParam.data.containsKey(time))  {
        syncParam.data.put(time, value);
      }
    } else {
      parameters.put(variableName, new SyncParameter(variableName));
    }
    return value;
  } else  {
    SyncParameter syncParam = parameters.get(variableName);
    float currentTime = (float)player.position() / 1000.0f;
    float prevValue = 0f;
    for (Map.Entry frame : syncParam.data.entrySet()) {
      if((Float)frame.getKey()>=currentTime)  {
        float currentValue = (Float)frame.getValue();
        //if(variableName.contains("Cube")) 
        //println(currentTime + "     " + variableName + "=" + frame.getValue());
        return currentValue;
      }
    }
    
    return 0.0;
  }
}

    // Function to sort map by Key
    public static TreeMap sortbykey(Map map)
    {
        // TreeMap to store values of HashMap
        TreeMap<String, Integer> sorted = new TreeMap<>();
 
        // Copy all data from hashMap into TreeMap
        sorted.putAll(map);
 
       return sorted;     
    }
void stop(){
  json= new JSONObject();
  JSONArray jsonParameters = new JSONArray();
  
  int i = 0;
  for (Map.Entry parameter : parameters.entrySet()) {
    String parameterName = (String)parameter.getKey();
    SyncParameter syncParam = (SyncParameter)parameter.getValue();
    JSONObject sync = new JSONObject();
    JSONArray syncArray = new JSONArray();
    Map<Float, Float> sorted = sortbykey(syncParam.data);
    int j = 0;
    for (Map.Entry synkki : sorted.entrySet()) {
      JSONObject syncData = new JSONObject();
      float time = (float)synkki.getKey();
      float value = (float)synkki.getValue();
      syncData.setFloat("Time",time);
      syncData.setFloat("Value",value);
      syncArray.setJSONObject(j++,syncData);
    }
    sync.setString("Name", parameterName);
    sync.setJSONArray("SyncArray", syncArray);
    jsonParameters.setJSONObject(i++,sync);
  }
  json.setJSONArray("Parameters",jsonParameters);
  saveJSONObject(json, "sync.json");
  
}
void setup() { 
  noCursor();
  PFont font;
  // The font must be located in the sketch's 
  // "data" directory to load successfully
  font = createFont("SF_Square_Head.ttf",48);
  if(!simulation){
  }
  feedback = loadShader("feedback.glsl");
  raymarch = loadShader("raymarch.glsl");
  img_animals = loadImage("animals.png");
  sprites = new PImage[7];
  for(int i = 0; i < 7; i++) {
    sprites[i] = loadImage("sprite" + i + ".png");
  }
  horn = loadShape("torvi.obj");
  star = loadShape("star.obj");
  spitor = loadShape("spirtor.obj");
  // Parameters: PApplet, filename (file should be in sketch's folder), 
  // beats per minute, rows per beat
  if(!isPlayer) {
    moonlander = Moonlander.initWithSoundtrack(this, "music.mp3", 125, 8);
    moonlander.start();
  } else  {
    minim = new Minim(this);
    player = minim.loadFile("music.mp3");
    json = loadJSONObject("sync.json");
    JSONArray jsonParameters = json.getJSONArray("Parameters");
    
    for (int i = 0; i < jsonParameters.size(); i++) {
      
      JSONObject parameter = jsonParameters.getJSONObject(i); 
  
      String name = parameter.getString("Name");
      JSONArray syncArray = parameter.getJSONArray("SyncArray");
      SyncParameter syncParam = new SyncParameter(name);
      
      for (int j = 0; j < syncArray.size(); j++) {
        
        JSONObject jsonData = syncArray.getJSONObject(j); 
    
        float time = jsonData.getFloat("Time");
        float value = jsonData.getFloat("Value");
        syncParam.data.put(time,value);
      }
      parameters.put(name, syncParam);
    }
    player.pause();
  } 
  
  
  
  pg = createGraphics(1280,720,P3D);
  //size(1280,720,P3D);
  size(displayWidth,displayHeight,P3D);
  frameRate(60);
  
  noSmooth();
  hint(DISABLE_TEXTURE_MIPMAPS);
  ((PGraphicsOpenGL)g).textureSampling(3);
  fullScreen(P3D);
  textFont(font);
  textAlign(CENTER, CENTER);
  
  
}

void drawStar() {
  float x = pg.width/2 + (float)getValueFromRocket("Trumpet Pos X");
  float y = pg.height/2 + (float)getValueFromRocket("Trumpet Pos Y");
  float z = (float)getValueFromRocket("Trumpet Pos Z");
  float rotx = (float)getValueFromRocket("Trumpet Rot X");
  float roty = (float)getValueFromRocket("Trumpet Rot Y");
  float rotz = (float)getValueFromRocket("Trumpet Rot Z");
  boolean wireframe = getValueFromRocket("Trumpet Wireframe") > 0;
  float r = (float)getValueFromRocket("Trumpet R");
  float g = (float)getValueFromRocket("Trumpet G");
  float b = (float)getValueFromRocket("Trumpet B");
  if(wireframe) {  
    pg.pushMatrix();
    pg.translate(x,y,z);
    pg.rotateX(radians(rotx));
    pg.rotateY(radians(roty));
    pg.rotateZ(radians(rotz));
    pg.noFill();
    pg.stroke(r,g,b);
    pg.scale((float)getValueFromRocket("Trumpet Size"));
    pg.strokeWeight(1.0 / (float)getValueFromRocket("Trumpet Size"));
    pg.beginShape();
    int children = star.getChildCount();
    for (int i = 0; i < children; i++) {
      PShape child = star.getChild(i);
      int total = child.getVertexCount();
      
      for (int j = 0; j < total; j++) {
        PVector v = child.getVertex(j);
        pg.vertex(v.x, v.y, v.z);
      }
    }
    pg.endShape();
    pg.popMatrix();
  } else { 
    pg.directionalLight(r*2,g*2,b*2,0,0,-1);
    pg.pushMatrix();
    pg.translate(x,y,z);
    pg.rotateX(radians(rotx));
    pg.rotateY(radians(roty));
    pg.rotateZ(radians(rotz));
    pg.noFill();
    pg.strokeWeight(0.07);
    pg.stroke(r,g,b);
    pg.scale((float)getValueFromRocket("Trumpet Size"));
    pg.fill(r,g,b);
    pg.shape(star);
    pg.popMatrix();
  }
}
void drawSpirtor() {
  float x = pg.width/2 + (float)getValueFromRocket("Trumpet Pos X");
  float y = pg.height/2 + (float)getValueFromRocket("Trumpet Pos Y");
  float z = (float)getValueFromRocket("Trumpet Pos Z");
  float rotx = (float)getValueFromRocket("Trumpet Rot X");
  float roty = (float)getValueFromRocket("Trumpet Rot Y");
  float rotz = (float)getValueFromRocket("Trumpet Rot Z");
  boolean wireframe = getValueFromRocket("Trumpet Wireframe") > 0;
  float r = (float)getValueFromRocket("Trumpet R");
  float g = (float)getValueFromRocket("Trumpet G");
  float b = (float)getValueFromRocket("Trumpet B");
  if(wireframe) {  
    pg.pushMatrix();
    pg.translate(x,y,z);
    pg.rotateX(radians(rotx));
    pg.rotateY(radians(roty));
    pg.rotateZ(radians(rotz));
    pg.noFill();
    pg.stroke(r,g,b);
    pg.scale((float)getValueFromRocket("Trumpet Size"));
    pg.strokeWeight(1.0 / (float)getValueFromRocket("Trumpet Size"));
    pg.beginShape();
    int children = spitor.getChildCount();
    for (int i = 0; i < children; i++) {
      PShape child = spitor.getChild(i);
      int total = child.getVertexCount();
      
      for (int j = 0; j < total; j++) {
        PVector v = child.getVertex(j);
        pg.vertex(v.x, v.y, v.z);
      }
    }
    pg.endShape();
    pg.popMatrix();
  } else { 
    pg.directionalLight(r*2,g*2,b*2,0,0,-1);
    pg.pushMatrix();
    pg.translate(x,y,z);
    pg.rotateX(radians(rotx));
    pg.rotateY(radians(roty));
    pg.rotateZ(radians(rotz));
    pg.noFill();
    pg.strokeWeight(0.07);
    pg.stroke(r,g,b);
    pg.scale((float)getValueFromRocket("Trumpet Size"));
    pg.fill(r,g,b);
    pg.shape(spitor);
    pg.popMatrix();
  }
}

void drawHorn() {
  float x = pg.width/2 + (float)getValueFromRocket("Trumpet Pos X");
  float y = pg.height/2 + (float)getValueFromRocket("Trumpet Pos Y");
  float z = (float)getValueFromRocket("Trumpet Pos Z");
  float rotx = (float)getValueFromRocket("Trumpet Rot X");
  float roty = (float)getValueFromRocket("Trumpet Rot Y");
  float rotz = (float)getValueFromRocket("Trumpet Rot Z");
  boolean wireframe = getValueFromRocket("Trumpet Wireframe") > 0;
  float r = (float)getValueFromRocket("Trumpet R");
  float g = (float)getValueFromRocket("Trumpet G");
  float b = (float)getValueFromRocket("Trumpet B");
  if(wireframe) {  
    pg.pushMatrix();
    pg.translate(x,y,z);
    pg.rotateX(radians(rotx));
    pg.rotateY(radians(roty));
    pg.rotateZ(radians(rotz));
    pg.noFill();
    pg.stroke(r,g,b);
    pg.scale((float)getValueFromRocket("Trumpet Size"));
    pg.strokeWeight(1.0 / (float)getValueFromRocket("Trumpet Size"));
    pg.beginShape();
    int children = horn.getChildCount();
    for (int i = 0; i < children; i++) {
      PShape child = horn.getChild(i);
      int total = child.getVertexCount();
      
      for (int j = 0; j < total; j++) {
        PVector v = child.getVertex(j);
        pg.vertex(v.x, v.y, v.z);
      }
    }
    pg.endShape();
    pg.popMatrix();
  } else { 
    pg.directionalLight(r*2,g*2,b*2,0,0,-1);
    pg.pushMatrix();
    pg.translate(x,y,z);
    pg.rotateX(radians(rotx));
    pg.rotateY(radians(roty));
    pg.rotateZ(radians(rotz));
    pg.noFill();
    pg.strokeWeight(0.07);
    pg.stroke(r,g,b);
    pg.scale((float)getValueFromRocket("Trumpet Size"));
    pg.fill(r,g,b);
    pg.shape(horn);
    pg.popMatrix();
  }
}

void drawCube() {
  float x = pg.width/2 + (float)getValueFromRocket("Cube Pos X");
  float y = pg.height/2 + (float)getValueFromRocket("Cube Pos Y");
  float z = (float)getValueFromRocket("Cube Pos Z");
  float rotx = (float)getValueFromRocket("Cube Rot X");
  float roty = (float)getValueFromRocket("Cube Rot Y");
  float rotz = (float)getValueFromRocket("Cube Rot Z");
  float r = (float)getValueFromRocket("Cube R");
  float g = (float)getValueFromRocket("Cube G");
  float b = (float)getValueFromRocket("Cube B");
  float bSize = (float)getValueFromRocket("Cube Size");
  float weight = (float)getValueFromRocket("Cube Stroke");
  float cubeFill = (float)getValueFromRocket("Cube Fill");
  float fr = (float)getValueFromRocket("Cube Fill R");
  float fg = (float)getValueFromRocket("Cube Fill G");
  float fb = (float)getValueFromRocket("Cube Fill B");
  pg.pushMatrix();
  pg.translate(x,y,z);
  pg.rotateX(radians(rotx));
  pg.rotateY(radians(roty));
  pg.rotateZ(radians(rotz));
  pg.rectMode(CENTER);
  pg.stroke(r,g,b);
  if (cubeFill<1) {
    pg.noFill();
  } else {
    pg.fill(fr,fg,fb);
  }
  pg.strokeWeight(weight);
  pg.box(bSize);
  pg.popMatrix();
}

double recordTimer = 0.0;
boolean isgoing = false;
boolean asked = false;
void keyPressed() {
  /*
  feedback = loadShader("feedback.glsl");
  raymarch = loadShader("raymarch.glsl");
  */
  if(asked) return;
  if (key == CODED) {
    if (keyCode == UP) {
      
      asked = true;
    } else if (keyCode == DOWN) {
      simulation = false;
      myClient = new UDP(this, 9909); 
      asked = true;
    } 
    } 
    
}
int valoupdate=0;
void draw() {
  if(!asked)   {
    background(0);
    text("at home press UP", width/2, 100); 
    text("at Instanssi press DOWN", width/2, 300); 
    text("autostart at: " + (int)(20-recordTimer), width/2, 600); 
    recordTimer += 0.01;
    if(recordTimer>20) 
      asked = true;
    return;
  }
  
  
  if(!isPlayer)  {
    // Handles communication with Rocket
    moonlander.update();
  }
  
  if(isPlayer && second()>1 && !isgoing) {
    player.play();
    isgoing = true;
  } 
  pg.beginDraw();
  pg.background(
    (float)getValueFromRocket("Background R"),
    (float)getValueFromRocket("Background G"),(float)getValueFromRocket("Background B"));
  drawCube();
  int framei = (int)getValueFromRocket("Animal Frame");
  if(framei==0)
  drawHorn();
  if(framei==1)
  drawStar();
  if(framei==2)
  drawSpirtor();
  
  pg.noSmooth();
  pg.directionalLight(255,255,255,0,0,-1);
  pg.filter(feedback);
  /*
  float x = width/2 + (float)getValueFromRocket("Animal X");
  float y = height/2 + (float)getValueFromRocket("Animal Y");
  float w = (sprites[framei % sprites.length].width * (float)getValueFromRocket("Animal Width"));
  float h = (sprites[framei % sprites.length].height * (float)getValueFromRocket("Animal Height"));
  pg.textureMode(NORMAL);
  
  
  ((PGraphicsOpenGL)g).textureSampling(3);
  pg.translate(x, y);
  pg.rotate(PI * (float)getValueFromRocket("Animal Rotation"));
  ((PGraphicsOpenGL)g).textureSampling(2);
  pg.image(sprites[framei % sprites.length],-w/2,-h/2,w,h);
  */
  pg.resetShader();
  
  /*
  pg.shader(raymarch);  
  pg.rect(0, 0, 320, 180); 
  raymarch.set("iResolution", new PVector(320,200));
  raymarch.set("iTime", 0.0f);*/
  pg.endDraw();
  
  //image(pg,0,0,1280,720);
  image(pg, 0, 0,width,height);
  int fi = (int)getValueFromRocket("Text Index");
  int fl = (int)getValueFromRocket("Text Length");
  float fx = (float)getValueFromRocket("Text Pos X");
  float fy = (float)getValueFromRocket("Text Pos Y");
  fill(32);
  text(texts[fi % texts.length].substring(0,fl), width/2-fx, height/2 - fy);
  fill(255);
  text(texts[fi % texts.length].substring(0,fl), width/2-fx, height/2 - fy + 2);
  PImage prevFrame = pg.get();
  feedback.set("prevFrame", prevFrame);
  feedback.set("blur", (float)getValueFromRocket("Blur"));
  feedback.set("blurScale", (float)getValueFromRocket("Blur Scale"));
  
  startLamp();
  int lamps0 = (int)getValueFromRocket("LampsStart");
  int lamps1 = (int)getValueFromRocket("LampsEnd");
  int LampR = (int)getValueFromRocket("LampsR");
  int LampG = (int)getValueFromRocket("LampsG");
  int LampB = (int)getValueFromRocket("LampsB");
    for(int i = lamps0; i <lamps1; i++){
      setLamp(i, LampR,LampG,LampB);
    }
  endLamp();
  if(!isPlayer)  {
    //if(moonlander.getCurrentRow() > 100) {
    if(moonlander.getCurrentRow() > 2198) {
      stop();
      exit();
    }
  } else if(player.length()-player.position()<=16000) {
    exit();
  }
  
  
}
