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

import moonlander.library.*; 
import ddf.minim.*; 
import java.util.*; 

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




/*
 * An example of how to use
 * Processing's 3D features.
 *
 * Features:
 * - Setting up a 3D viewport
 * - Drawing plane and sphere primitives
 * - 3D-transformations
 */

// Setup Variables
int CANVAS_WIDTH = 1280;
int CANVAS_HEIGHT = 720;
double dt = 0.0f;
double lastTime = 0.0f;
Engine engine = new Engine();
Moonlander moonlander;
double beat;
double red;
double green;
double blue;
double flash;
double beatState;
PGraphics pg;
TV tv;
PImage bluescreen;
double sf2;
float carx;
float carz;

public void setup() {
  size(CANVAS_WIDTH, CANVAS_HEIGHT, P3D);
  frameRate(50);
  pg = createGraphics(CANVAS_WIDTH, CANVAS_HEIGHT, P3D);
  pg.rectMode(CENTER);
  noStroke();
  tv = new TV();
  
  bluescreen = loadImage("bluescreen.png");
  
  moonlander = Moonlander.initWithSoundtrack(this, "music.wav", 122, 12);
  moonlander.start();
  engine.init();
}

public void draw() {
  moonlander.update();
  beat = moonlander.getValue("beat");
  beatState = moonlander.getValue("beatState");
  red = moonlander.getValue("red");
  green = moonlander.getValue("green");
  blue = moonlander.getValue("blue");
  flash = moonlander.getValue("flash");
  sf2 = moonlander.getValue("snow");

  dt = (millis() - lastTime) / 1000.0f;
  engine.update(dt);
  engine.paint();
  lastTime = millis();

  if (flash > 0.0f) {
    background(255, (float)(flash * 255));
  }
  tv.paint();
}

class Camera extends GameObject {

  GameObject parent;

  public void setParent(GameObject gameObject) {
    parent = gameObject;
  }

  public void init() {
    x = width/2;
    y = height/2;
  }
  public void update(double dt) {
  }
  public void paint(Drawer drawer) {
    pg.beginCamera();
    pg.camera();
    if (parent!=null) {
      println(parent.x + " " + parent.y);
      pg.translate(parent.x, parent.y, parent.z);
    }
    pg.translate(x, y, z);
    pg.rotateX(-PI/6);
    pg.rotateY(HALF_PI);

    pg.endCamera();
  }
}

class Car extends GameObject {
  float h = 50;
  float vx = 0.0f;

  public Car() {
  }

  public Car(float x, float z) {
    this.x = x;
    this.z = z;
  }

  public void init() {
    y += - h / 2;
  }

  public void update(double dt) {
    x = (float)moonlander.getValue("speed");
  }

  public void drawWheel(Drawer drawer, float x, float z) {
    pg.pushMatrix();
    pg.translate(x, -50, z);
    pg.rotateZ(millis() / 1000.0f);
    pg.fill(5, 7, 15);
    drawer.drawCylinder(10, 100, 50);
    pg.popMatrix();
  }

  public void paint(Drawer drawer) {
    pg.pushMatrix();
    pg.translate(this.x, this.y, this.z);

    pg.fill(200, 0, 0);
    pg.pushMatrix();
    pg.translate(0, -150 + ((beatState > 1) ? 50*(float)beat : 0), 0);
    pg.box(400, 200, 240);
    pg.pushMatrix();
    pg.textSize(100);
    pg.fill(0,0,0);
    
    pg.text("Badass",-170,50,121);
    pg.rotateY(PI);
    pg.textSize(40);
    pg.text("Reserved for Sponsors",-190,0,121);
    pg.popMatrix();
    //Puskurit
    pg.fill(200, 0, 0);
    pg.translate(250, 40, 0);
    pg.box(100, 150, 240);
    pg.pushMatrix();
    pg.fill(247, 219, 7);
    pg.translate(55, -80, -30);
    pg.rotateY(PI/2);
    pg.ellipse(50, 50, 50, 50 + ((beatState > 1) ? -30*(float)beat : 0));
    pg.translate(-150, 0, 0);
    pg.ellipse(50, 50, 50, 50 + ((beatState > 1) ? -30*(float)beat : 0));
    pg.popMatrix();
    pg.fill(200, 0, 0);
    pg.translate(-500, 0, 0);
    pg.box(100, 150, 240);
    pg.translate((250.0f), -185, 0);
    //Spoiler
    pg.pushMatrix();
    pg.translate(-230,0,0);
    pg.rotateZ(-PI/4);
    pg.box(30, 120, 240);
    pg.popMatrix();
    //P\u00e4\u00e4
    pg.fill(0, 0, 0);
    pg.pushMatrix();
    if (beatState > 0) {
      pg.translate(50*(float)beat, 0, 0);
    }
    pg.sphere(50);
    pg.translate(-10, -50, -40);
    pg.rotateY(PI/2);
    drawer.drawCylinder(40, 40, 40);
    pg.translate(-80, 0, 0);
    drawer.drawCylinder(40, 40, 40);
    pg.popMatrix();
    pg.fill(255, 255, 255, 50);
    pg.translate(130, 10, 0);
    pg.rotateZ(0.4f);
    pg.box(100, 20, 240);
    pg.popMatrix();

    drawWheel(drawer, 200, 120);
    drawWheel(drawer, -200, 120);
    drawWheel(drawer, 200, -120);
    drawWheel(drawer, -200, -120);

    pg.popMatrix();
  }
}

class Drawer {

  public void drawCylinderPart(int sides, float r, float h) {
    float angle = 360 / sides;
    pg.beginShape();
    for (int i = 0; i < sides; i++) {
      float x = cos( radians( i * angle ) ) * r;
      float y = sin( radians( i * angle ) ) * r;
      pg.vertex( x, y, h );
    }
    pg.endShape(CLOSE);
  }  

  public void drawCylinder(int sides, float r, float h) {
    float angle = 360 / sides;
    float halfHeight = h / 2;
    // draw top shape
    drawCylinderPart(sides, r, -halfHeight);
    // draw bottom shape
    drawCylinderPart(sides, r, halfHeight);
    // draw body
    pg.beginShape(TRIANGLE_STRIP);
    for (int i = 0; i < sides + 1; i++) {
      float x = cos( radians( i * angle ) ) * r;
      float y = sin( radians( i * angle ) ) * r;
      pg.vertex( x, y, halfHeight);
      pg.vertex( x, y, -halfHeight);
    }
    pg.endShape(CLOSE);
  }
}



class Engine {
  // GameObjects
  ArrayList<GameObject> gameObjects = new ArrayList<GameObject>();
  Drawer drawer = new Drawer();
  GameObject car;
  GameObject road;
  GameObject ground;
  public void Add(GameObject gameObject) {
    gameObjects.add(gameObject);
  }
  double sf;
  int rotation = 0;
  public void init() {
    //    Camera camera = new Camera();  
    //    Add((GameObject) camera);
    /*Add(new Tree(200, 200));
     Add(new Tree(500, 500));
     Add(new Tree(200, -200));
     Add(new Tree(-200, -200));
     Add(new Tree(-500, 200));
     Add(new Tree(-500, -500));
     */
    ground = new Ground();
    Add(ground);
    road = new Road();
    Add(road);
    car = new Car();
    Add(car);
    for (int x = 0; x < 20; x++) {
      Add(new Tree(450*x, -450, car));
      Add(new Tree(-450*x, 450, car));
      Add(new Tree(450*x, 450, car));
      Add(new Tree(-450*x, -450, car));
    }
    //camera.setParent(car);

    for (GameObject gameObject : gameObjects) {
      gameObject.init();
    }
  }

  public void update(double dt) {
    
    carx = car.x;
    carz = car.z;
    
    for (GameObject gameObject : gameObjects) {
      gameObject.update(dt);
    }
    
    if (sf2 > 0 && sf > 0.03f) {
      for (int i2 = 1; i2 <= 10; i2++) {
        Add(new Snowflake(gameObjects));
      }
      sf = 0;
    } else {
      sf += dt;
    }
    
    pg.pushMatrix();
    road.update(car.x);
    //ground.update(car.x);
    pg.popMatrix();
  }
  public void paint() {
    pg.beginDraw();
    pg.noStroke();
    pg.clear();
    pg.pushMatrix();


    pg.background(54, 191, 255);

    int status = moonlander.getIntValue("status");
    float cameraZ = (float)moonlander.getValue("cameraZ");
    float cameraX = (float)moonlander.getValue("cameraX");
    float cameraY = (float)moonlander.getValue("cameraY");
    float centerX = (float)moonlander.getValue("centerX");
    float centerZ = (float)moonlander.getValue("centerZ");
    float centerY = (float)moonlander.getValue("centerY");

    // Center the Scene
    pg.translate(width/2, height/2, 0);
    //    // Move up and backwards - away from the origin
    pg.translate(0, 200, -400);
    //    // Rotate the viewport a bit with mouse
    pg.rotateY((mouseX - width/2) * 0.01f);
    pg.rotateX((mouseY - height/2) * -0.01f);

    //cinematic mode
    if (status == 1 || status > 10) {
      pg.camera(car.x, -200, 250, car.x + 50000, 0, car.z -25000, 0, 1, 0);
    } else if (status == 2) {
      pg.camera(car.x + cameraX, -200 - cameraY, 250 -cameraZ, car.x + 50000-centerX, 0-centerY, -25000 +centerZ, 0, 1, 0);
    } else if (status == 3 || status == 5) {
      pg.camera(car.x + cameraX, -200 - cameraY, 250 -cameraZ, car.x, car.y, car.z, 0, 1, 0);
    } else {
      pg.camera(900+car.x, -800, 0, car.x, car.y, car.z, 0, 1, 0);
    }
    //    // Rotate the viewport a bit with mouse
    //rotateY((mouseX - width/2) * 0.01);
    //rotateX((mouseY - height/2) * -0.01);

    pg.fill(255);

    // Draw the ground plane
    pg.ambientLight(140, 140, 120);
    pg.directionalLight(150, 150, 150, 0, 1, -1);

    for (GameObject gameObject : gameObjects) {
      gameObject.paint(drawer);
    }
    pg.popMatrix();
    pg.endDraw();
  }
}

class GameObject {
  float x;
  float y;
  float z;
  public void init() {
  }
  public void update(double dt) {
  };
  public void paint(Drawer drawer) {
  };
}

class Ground extends GameObject {
  public void init() {
  }
  int x = 0;
  public void update(double dt) {
    //x = (int) dt; 
  }

  public void paint(Drawer drawer) {
    pg.pushMatrix();
    // Rotate the plane by 90 degrees so it's laying on the ground 
    // instead of facing the camera. Try to use `secs` instead and 
    // see what happens :)
    pg.rotateX(PI/2);
    pg.scale(6.0f);
    // Draw the plane
    //pg.translate(x,0,0);
    pg.fill((int) red, (int) green, (int) blue);
    pg.rect(-1000, -1000, 10000, 10000);
    pg.popMatrix();
  }
}


class Road extends Ground {
  int x = 0;
  public void update(double dt) {
    x = (int) dt; 
  }
  
  
  public void paint(Drawer drawer) {
    pg.pushMatrix();
    // Rotate the plane by 90 degrees so it's laying on the ground 
    // instead of facing the camera. Try to use `secs` instead and 
    // see what happens :)
    pg.rotateX(PI/2);
    pg.fill(186,185,179);
    // Draw the plane
    pg.translate(x,0,2);
    pg.rect(-7000, -300, 100000, 600);
    pg.popMatrix();
  }
}
class Snowflake extends GameObject {
  
  ArrayList<GameObject> gameObjects;
  
  public int snow_color = color(200);
  public boolean draw_snow = true;
  public float snow_v = 1000;
  int snow_color2 = color(random(256), random(256), random(256));
  
  public Snowflake(ArrayList<GameObject> gameObjects) {
    
    int status = moonlander.getIntValue("status");
    float cameraZ = (float)moonlander.getValue("cameraZ");
    float cameraX = (float)moonlander.getValue("cameraX");
    
    if (status == 1) {
      this.x = random(3000) - 1500 + carx;
      this.z = random(3000) - 1500 + 250;
    } else if (status == 2){
      this.x = random(3000) - 1500 + carx + cameraX;
      this.z = random(3000) - 1500 + 250 - cameraZ;
    }else if(status == 3 || status == 5){
      this.x = random(3000) - 1500 +  carx + cameraX;
      this.z = random(3000) - 1500 + 250 - cameraZ;
    }else{
      this.x = random(3000) - 1500 + carx + 900;
      this.z = random(3000) - 1500;
    }
    this.y = -1000;
    this.gameObjects = gameObjects;
  }
  
  public void init() {
    
  }
  
  public void update(double dt) {
    if (sf2 > 10) snow_color = snow_color2;
    if (draw_snow == true) {
      y += snow_v * dt;
      if (y > 5) draw_snow = false; //gameObjects.remove(this);//
    }
  }
  
  public void paint(Drawer drawer)
  {
    if (draw_snow == true) {
      pg.pushMatrix();
      pg.noLights();
      pg.translate(this.x, this.y, this.z);
      pg.fill(snow_color);
      pg.sphere(9);
      pg.popMatrix();
      pg.lights();  
    }    
  };
  
}
class TV {

  int CANVAS_DEPTH = 500;
  int rotation = 1;
  float ASPECT_RATIO = (float)CANVAS_WIDTH/CANVAS_HEIGHT;
  int status;
  int status2;

  public void paint() {
    status = moonlander.getIntValue("tvstatus");
    status2 = moonlander.getIntValue("status");
    float cameraZ = (float)moonlander.getValue("tvcamera");
    float cameraX = (float)moonlander.getValue("tvcameraX");
    float centerX = (float)moonlander.getValue("tvcenterX");

    if (status > 0) {
      camera(CANVAS_WIDTH/2.0f -cameraX, CANVAS_HEIGHT/2.0f, (CANVAS_HEIGHT/2.0f) / tan(PI*30.0f / 180.0f) - cameraZ, CANVAS_WIDTH/2.0f - centerX, CANVAS_HEIGHT/2.0f, 0, 0, 1, 0);
    }
    resolution();
  }


  public void resolution() {
    float xPoint = (CANVAS_WIDTH - 0.25f*CANVAS_WIDTH);
    float yPoint = (CANVAS_HEIGHT - 0.25f*CANVAS_HEIGHT);
    double times = moonlander.getValue("tvtimes");
    float x1 = xPoint - (float)times*xPoint;
    float x2 = xPoint + (float)times*(0.25f*CANVAS_WIDTH);
    float y1 = yPoint - (float)times*yPoint;
    float y2 = yPoint + (float)times*(0.25f*CANVAS_HEIGHT);
    int textColor = moonlander.getIntValue("tvcolor");

    background(255, 255, 255, 0);


    pushMatrix();
    translate(0, 0, -3);
    rect(0, 0, (float)CANVAS_WIDTH, (float)CANVAS_HEIGHT);
    popMatrix();
    translate(CANVAS_WIDTH/2, CANVAS_HEIGHT/2, 0);
    pushMatrix();
    translate(-pg.width/2, -pg.height/2, -1);
    if (beatState > 3) {
      image(bluescreen, 0, 0);
    } else {
      image(pg, 0, 0);
      popMatrix();

      // Text
      pushMatrix();
      fill(255);
      translate(-pg.width/2, -pg.height/2, 0);
      if (status2 == 5) {  
        fill(255);
        textSize(100);
        text("This is 4K",CANVAS_WIDTH/2 -600, CANVAS_HEIGHT/2 +300,0);
      }else if(status2 == 12) {
        fill(255);
        textSize(100);
        text("In4K presents",CANVAS_WIDTH/2 -350, CANVAS_HEIGHT/2 +50 ,0);
      }else if(status2 == 13) {
        fill(255);
        textSize(100);
        text("Long Ass Intro",CANVAS_WIDTH/2 -350, CANVAS_HEIGHT/2 +50,0);
      }
      stroke(255, 255, 255, 255);
      line(x1, yPoint, x2, yPoint);
      line(xPoint, y1, xPoint, y2);

      stroke(255, 255, 255, 255 -textColor);
      line(xPoint, 0, xPoint + 10, 10);
      line(xPoint - 10, 10, xPoint, 0);
      line(xPoint, CANVAS_HEIGHT -1, xPoint +10, CANVAS_HEIGHT - 11);
      line(xPoint - 10, CANVAS_HEIGHT -11, xPoint, CANVAS_HEIGHT -1);
      line(0, yPoint, 10, yPoint +10);
      line(10, yPoint -10, 0, yPoint);
      line(CANVAS_WIDTH -1, yPoint, CANVAS_WIDTH -11, yPoint + 10);
      line(CANVAS_WIDTH -11, yPoint -10, CANVAS_WIDTH -1, yPoint);
      textSize(32);
      fill(255, 255, 255, 255 -textColor);
      text("4096", 7, yPoint -10);
      fill(255, 255, 255, 255 -textColor);
      text("2160 ", xPoint + 5, 32);
      fill(255, 255, 255, 255-textColor);
      noStroke();

      // Flash
      if (flash > 0.0f) {
        fill(255, 255, 255, (int)(flash * 255));
        rect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
      }
    }
    popMatrix();

    fill(textColor);
    pushMatrix();
    translate(0, CANVAS_HEIGHT/2+5, 0);
    box(CANVAS_WIDTH + 20, 10, 20);
    popMatrix();
    pushMatrix();
    translate(0, -CANVAS_HEIGHT/2-5, 0);
    box(CANVAS_WIDTH + 20, 10, 20);
    popMatrix();
    pushMatrix();
    translate(CANVAS_WIDTH/2+5, 0, 0);
    box(10, CANVAS_HEIGHT, 20);
    popMatrix();
    pushMatrix();
    translate(-CANVAS_WIDTH/2-5, 0, 0);
    box(10, CANVAS_HEIGHT, 20);
    popMatrix();
    pushMatrix();
    translate(CANVAS_WIDTH/2, CANVAS_HEIGHT/2 + 24, - 10);
    rotateZ(0.4f);
    box(20, 10, 50);
    rotateZ(-0.4f);
    translate(-CANVAS_WIDTH, 0, 0);
    rotateZ(0.4f);
    box(10, 10, 50);
    popMatrix();
    textSize(120);
    fill(0, 0, 0, status);
    rotateY(-0.4f);
    text("Sony", 900, 0);
    rotateY(-0.2f);
    fill(248, 0, 40, status);
    text("4K", 900, 120);
    fill(0, 0, 0, status);
    text("Bravia", 1100, 120);
  }
}

class Tree extends GameObject {
  float sphereRadius = 100;
  float h = 300;
  int r = 0;
  int g = 0;
  int b = 0;
  double last;
  GameObject car;

  public Tree(float x, float z, GameObject cars) {
    this.x = x;
    this.z = z;
    car = cars;
  }

  public void init() {
    y += - h / 2;
  }

  public void update(double dt) {
    sphereRadius = 200 + (int) ((beatState > 2) ? 60*(float)beat : 0);
    if (car.x - this.x > 10000) {
      this.x = car.x + 10000;
    }
  }

  public void paint(Drawer drawer) {
    float cameraX = (float)moonlander.getValue("cameraX");
    int status = moonlander.getIntValue("status");
    pg.pushMatrix();
    if (status == 1) {
      pg.translate(this.x, this.y +h*cameraX -2*cameraX, this.z);
    } else {
      pg.translate(this.x, this.y, this.z);
    }
    pg.fill(176, 80, 1);

    pg.box(40, 300, 40);
    if (status == 1) {
      pg.translate(0, -h + sphereRadius / 1.8f + h*cameraX, 0);
    } else {
      pg.translate(0, -h + sphereRadius / 1.8f, 0);
    }
    if (beatState > 2.5f) {
      if (beat - last > 0.7f) {
        r = (int)random(256);
        g = (int)random(256);
        b = (int)random(256);
      }
      last = beat;
    } else {
      r = (int)red;
      g = (int)green;
      b = (int)blue;
    }
    pg.fill(r, g, b);
    pg.sphere(sphereRadius);
    pg.popMatrix();
  }
}

  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "in4k" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
