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

import java.util.*; 
import javafx.util.Pair; 
import ddf.minim.*; 

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


// chips dinner





float time = 0.0f;
int[] colourpalette = new int[8];
Minim minim;
AudioPlayer player;


public void stop()
{
  player.close();
  minim.stop();
  super.stop();
}

int grid_width = 64, grid_height = 64;
float[][] dists = new float[grid_width + 1][grid_height + 1];
float accum_length;
float sketch_segment_length = 10;

List<Line> lines = new ArrayList<Line>();
Map<Integer, List<Line>> loops = new HashMap<Integer, List<Line>>();

class Line
{
  public PVector v0;
  public PVector v1;
  public int loop = -1;
}

public void addLine(PVector v0, PVector v1)
{
  Line l = new Line();
  l.v0 = v0;
  l.v1 = v1;
  lines.add(l);
}

public void setup()
{
  //size(800, 600);
  
  //size(1920,1080);
  
  noFill();
  frameRate(12);
 minim = new Minim(this);
  player = minim.loadFile("frogsong.wav");
player.play();
}


public void draw()
{
  background(220);
  pushMatrix();
  //scale(float(width) / 2560.0f, float(height) / 1440.0f);
  //scale(2,2);
  translate(width / 4, 0);
  scale(PApplet.parseFloat(height) / PApplet.parseFloat(width), 1);

  lines.clear();
  loops.clear();

  accum_length = 0.0f;

  //time = (float)millis() / 1000.0f;
  time = (float)player.position() / 1000.0f;
//time += 40.0f;

  generateFieldLines(time);
  addBorder();

  float squaresAmount = constrain((time - 18.0f) / 8.0f, 0.0f, 1.0f) - constrain((time - 30.0f) / 9.0f, 0.0f, 1.0f);
  
  if(squaresAmount > 0.0f)
  {
    for(int i = 0; i < 10; ++i)
    {
      float ox = noise(i*2+0) * 2000 - 1000;
      float oy = noise(i*2+1) * 2000 - 1000;
      float t = time + i * 10.0f;
      float s = squaresAmount * (0.5f + cos(t)) * 2.0f;
      addSquare(new PVector(width / 2 - 260 * s + ox, height / 2 - 160 * s + oy), new PVector(width / 2 + 260 * s + ox, height / 2 + 160 * s + oy), 0);
    }
  }
  
  float circlesAmount = constrain((time - 40.0f) / 8.0f, 0.0f, 1.0f) - constrain((time - 49.0f) / 8.0f, 0.0f, 1.0f);
  
  if(circlesAmount > 0.0f)
  {
    randomSeed(0);
    for(int i = 0; i < 10; ++i)
    {
      float ox = random(1) * 2000;
      float oy = random(1) * 1400;
      float t = time * 2.0f + i * 10.0f;
      float r = (100.0f + 20.0f * circlesAmount * (0.5f + cos(t)) * 10.0f) * circlesAmount;
      addCircle(new PVector(ox, oy), r, 10);
    }
  }
  
  
  detectLoops();

  colourpalette[0] = color(130, 130, 160);
  colourpalette[1] = color(150, 180, 150);
  colourpalette[2] = color(160, 140, 120);
  colourpalette[3] = color(190, 190, 120);
  colourpalette[4] = color(130, 130, 160);

  strokeWeight(20);
  sketch_segment_length = 15;
  fillShapes();


  stroke(30);
  strokeWeight(16);
  sketch_segment_length = 5;

  for(List<Line> list : loops.values())
  {
    for(Line l : list)
      sketchLine(l.v0, l.v1, sketch_segment_length);
  }
  
  popMatrix();
}

public void generateFieldLines(final float t)
{
  float amount = constrain((time - 9.5f) / 3.0f, 0.0f, 1.0f) - constrain((time - 59.f) / 5.0f, 0.0f, 1.0f);
  
  for(int y = 0; y < grid_height + 1; ++y)
    for(int x = 0; x < grid_width + 1; ++x)
    {
      if(x == 0 || y == 0 || x == grid_width || y == grid_height)
        dists[x][y] = 0.0f;
      else
      {
        float u = (float)x, v = (float)y;
        dists[x][y] = sin(u * 0.1f + cos(v * 0.12f + t) ) * cos(v * 0.1f + sin(u * 0.12f + t * 0.3f)) - 0.2f;
        dists[x][y] += (cos(u * 0.02f * 8.0f) * sin(v * 0.013f * 8.0f) - 0.4f) * 0.4f + lerp(-1.0f, 0.0f, amount);
      }
    }
  
  for(int y = 0; y < grid_height; ++y)
    for(int x = 0; x < grid_width; ++x)
    {
      drawGridCell(x, y, dists[x][y], dists[x + 1][y], dists[x][y + 1], dists[x + 1][y + 1]);
    }
}

public void addBorder()
{
  int w = width - 20, h = height - 20;
  float x = constrain((time - 0.0f) / 4.9f, 0.0f, 0.5f) - constrain((time - 63.0f) / 6.0f, 0.0f, 0.5f);
  float rotAmount = constrain((time - 29.0f) / 12.0f, 0.0f, 1.0f) * PI;
  addSquare(new PVector((float)(width / 2) - (float)w * x, (float)(height / 2) - (float)h * x),
            new PVector((float)(width / 2) + (float)w * x, (float)(height / 2) + (float)h * x), rotAmount);
           
}

public void detectLoops()
{
  int loop = -1;
  for(Line l0 : lines)
  {
    if(l0.loop == -1)
    {
      ++loop;
      l0.loop = loop;
      
      for(;;)
      {
        boolean found = false;
        
        for(Line l1 : lines)
        {
          if(l0 != l1 && l1.loop != l0.loop && (pointsAreEqual(l0.v0, l1.v0) || pointsAreEqual(l0.v1, l1.v0) ||
             pointsAreEqual(l0.v0, l1.v1) || pointsAreEqual(l0.v1, l1.v1)))
          {
            l1.loop = l0.loop;
            found = true;
            l0 = l1;
            break;
          }
        }
       
        if(!found)
          break;
      }
    }
  }

  for(Line l : lines)
  {
    if(!loops.containsKey(l.loop))
      loops.put(l.loop, new ArrayList<Line>());
     
    List<Line> list = loops.get(l.loop);
    list.add(l); 
  }
}

public boolean pointsAreEqual(final PVector v0, final PVector v1)
{
  return PVector.dist(v0, v1) < 1;
}

public void addCircle(final PVector center, float radius, int segs)
{
  float ox = center.x, oy = center.y;
  for(int i=0;i<segs;++i)
  {
    float th0 = PApplet.parseFloat(i + 0) * TWO_PI / PApplet.parseFloat(segs);
    float th1 = PApplet.parseFloat(i + 1) * TWO_PI / PApplet.parseFloat(segs);
    PVector v0 = new PVector(ox + cos(th0) * radius * 16.0f/9.0f, oy + sin(th0) * radius);
    PVector v1 = new PVector(ox + cos(th1) * radius * 16.0f/9.0f, oy + sin(th1) * radius);
    addLine(v0, v1);
  }
}

public void addSquare(PVector v0, PVector v1, float rotate)
{
  PVector v2 = new PVector(v0.x, v1.y);
  PVector v3 = new PVector(v1.x, v0.y);
  if(rotate != 0.0f)
  {
    PVector center = new PVector((v0.x+v1.x)/2.0f,(v0.y+v1.y)/2.0f);
    v0 = new PVector(v0.x - center.x, v0.y - center.y);
    v1 = new PVector(v1.x - center.x, v1.y - center.y);
    v2 = new PVector(v2.x - center.x, v2.y - center.y);
    v3 = new PVector(v3.x - center.x, v3.y - center.y);
    v0.rotate(rotate);
    v1.rotate(rotate);
    v2.rotate(rotate);
    v3.rotate(rotate);
    v0 = new PVector(center.x + v0.x, center.y + v0.y);
    v1 = new PVector(center.x + v1.x, center.y + v1.y);
    v2 = new PVector(center.x + v2.x, center.y + v2.y);
    v3 = new PVector(center.x + v3.x, center.y + v3.y);
  }
  addLine(v2, v0);
  addLine(v1, v2);
  addLine(v3, v1);
  addLine(v0, v3);
}

public void fillShapes()
{
  PVector stroke_dir = new PVector(-30, 2);
  stroke_dir.normalize();
  PVector stroke_norm = new PVector(stroke_dir.y, -stroke_dir.x);
  float mind = 1e6f, maxd = -1e6f;
  float step_size = 20.0f;

  mind = 50.0f;
  maxd = lerp(50.0f, +1680.0f, constrain((time - 4.9f) / 4.5f, 0.0f, 1.0f));

  for (float x = mind; x < maxd; x += step_size)
  {
    PVector ro = PVector.mult(stroke_norm, x);
    PVector rd = stroke_dir;

    List<Pair<Float, Line>> intersections = new ArrayList<Pair<Float, Line>>();

    for (Line l : lines)
    {
      PVector its = clipRay(ro, rd, l.v0, l.v1);

      if (its != null)
      {
        float t = PVector.dot(PVector.sub(its, ro), rd);
        Pair<Float, Line> pair = new Pair<Float, Line>(t, l);
        intersections.add(pair);
      }
    }
    
    Collections.sort(intersections, new Comparator() { public int compare(Object o0, Object o1)
      {
        Pair<Float, Line> p0 = (Pair<Float, Line>)o0;
        Pair<Float, Line> p1 = (Pair<Float, Line>)o1;
        return p0.getKey() < p1.getKey() ? -1 : p0.getKey() > p1.getKey() ? +1 : 0;
      } } );

    boolean inside = false;
    int count = 0;
    int paletteindex = 0;
    float pf = -4e3f;
    for(Pair<Float, Line> p : intersections)
    {
      float f = p.getKey();
      Line l = p.getValue();
      inside = !inside;
      
      float d = PVector.dot(stroke_norm, PVector.sub(l.v1, l.v0));
              
      if(paletteindex>=0 && paletteindex < colourpalette.length)
      {
        stroke(colourpalette[paletteindex]);
        
        float t0 = pf + 20.0f, t1 = f - 20.0f;
             
        if(t1 > t0 && count > 0)
        {
          sketchLine(PVector.add(ro, PVector.mult(rd, t0)),
                     PVector.add(ro, PVector.mult(rd, t1)), sketch_segment_length);
        }
      }

      paletteindex += d > 0.0f ? +1 : -1;

      ++count;
      pf = f;
    }

  }  

}

public PVector clipRay(PVector ro, PVector rd, PVector pa, PVector pb)
{
  PVector pd = PVector.sub(pb, pa);
  PVector pn = new PVector(pd.y, -pd.x);
  pn.normalize();

  float d = PVector.dot(rd, pn);
  
  if(d == 0.0f)
    return null;
  
  float t = -PVector.dot(PVector.sub(ro, pa), pn) / d;
  PVector its = PVector.add(ro, PVector.mult(rd, t));
  float p = PVector.sub(its, pa).dot(pd) / pd.mag();

  if (p >= 0.0f && p <= pd.mag()) 
    return its;
  else
    return null;
}


public void drawGridCell(final int x, final int y,
      final float d00, final float d10, final float d01, final float d11)
{
  int code = 0;
  
  if(d00 <= 0)
    code |= 1;

  if(d10 <= 0)
    code |= 2;

  if(d11 <= 0)
    code |= 4;

  if(d01 <= 0)
    code |= 8;

  final int margin = 40;

  int x0 = margin + (x + 0) * (width - margin * 2) / grid_width,
      y0 = margin + (y + 0) * (height - margin * 2) / grid_height;

  int x1 = margin + (x + 1) * (width - margin * 2) / grid_width,
      y1 = margin + (y + 1) * (height - margin * 2) / grid_height;

  float[] ds = { d00, d10, d11, d01 };
  
  int[][] ps = { { x0, y0 }, { x1, y0 },
                 { x1, y1 }, { x0, y1 } };

  switch(code)
  {
    case 1:
      drawGridCell_1(ds, ps, 0);
      break;

    case 2:
      drawGridCell_1(ds, ps, 1);
      break;

    case 3:
      drawGridCell_2(ds, ps, 0);
      break;

    case 4:
      drawGridCell_1(ds, ps, 2);
      break;
      
    case 5:
      drawGridCell_3(ds, ps, 2);
      break;
      
    case 6:
      drawGridCell_2(ds, ps, 1);
      break;
      
    case 7:
      drawGridCell_4(ds, ps, 1);
      break;

    case 8:
      drawGridCell_1(ds, ps, 3);
      break;

    case 9:
      drawGridCell_2(ds, ps, 3);
      break;

    case 10:
      drawGridCell_3(ds, ps, 1);
      break;
      
    case 11:
      drawGridCell_4(ds, ps, 0);
      break;      

    case 12:
      drawGridCell_2(ds, ps, 2);
      break; 

    case 13:
      drawGridCell_4(ds, ps, 3);
      break;

    case 14:
      drawGridCell_4(ds, ps, 2);
      break;
  }
}


// one corner
public void drawGridCell_1(float[] ds, int[][] ps, int rot)
{
  int i0 = (rot + 0) & 3, i1 = (rot + 1) & 3,
      i2 = (rot + 2) & 3, i3 = (rot + 3) & 3;
  
  float t0 = isopoint(ds[i0], ds[i1]),
        t1 = isopoint(ds[i0], ds[i3]);
  
  PVector v0 = new PVector(ps[i0][0] + t0 * (ps[i1][0] - ps[i0][0]),
                           ps[i0][1] + t0 * (ps[i1][1] - ps[i0][1]));
  
  PVector v1 = new PVector(ps[i0][0] + t1 * (ps[i3][0] - ps[i0][0]),
                           ps[i0][1] + t1 * (ps[i3][1] - ps[i0][1]));

  addLine(v0, v1);
}

// two corners adjacent
public void drawGridCell_2(float[] ds, int[][] ps, int rot)
{
  int i0 = (rot + 0) & 3, i1 = (rot + 1) & 3,
      i2 = (rot + 2) & 3, i3 = (rot + 3) & 3;
  
  float t0 = isopoint(ds[i1], ds[i2]),
        t1 = isopoint(ds[i0], ds[i3]);

  PVector v0 = new PVector(ps[i1][0] + t0 * (ps[i2][0] - ps[i1][0]),
                           ps[i1][1] + t0 * (ps[i2][1] - ps[i1][1]));
  
  PVector v1 = new PVector(ps[i0][0] + t1 * (ps[i3][0] - ps[i0][0]),
                           ps[i0][1] + t1 * (ps[i3][1] - ps[i0][1]));

  addLine(v0, v1);
}

// two corners opposite
public void drawGridCell_3(float[] ds, int[][] ps, int rot)
{
  int i0 = (rot + 0) & 3, i1 = (rot + 1) & 3,
      i2 = (rot + 2) & 3, i3 = (rot + 3) & 3;
  
  float t0 = isopoint(ds[i0], ds[i1]),
        t1 = isopoint(ds[i1], ds[i2]),
        t2 = isopoint(ds[i2], ds[i3]),
        t3 = isopoint(ds[i3], ds[i0]);
  
  PVector v0 = new PVector(ps[i0][0] + t0 * (ps[i1][0] - ps[i0][0]),
                           ps[i0][1] + t0 * (ps[i1][1] - ps[i0][1]));
  
  PVector v1 = new PVector(ps[i3][0] + t3 * (ps[i0][0] - ps[i3][0]),
                           ps[i3][1] + t3 * (ps[i0][1] - ps[i3][1]));

  PVector v2 = new PVector(ps[i1][0] + t1 * (ps[i2][0] - ps[i1][0]),
                           ps[i1][1] + t1 * (ps[i2][1] - ps[i1][1]));
  
  PVector v3 = new PVector(ps[i2][0] + t2 * (ps[i3][0] - ps[i2][0]),
                           ps[i2][1] + t2 * (ps[i3][1] - ps[i2][1]));

  addLine(v0, v1);
  addLine(v2, v3);
}



// three corners
public void drawGridCell_4(float[] ds, int[][] ps, int rot)
{
  int i0 = (rot + 0) & 3, i1 = (rot + 1) & 3,
      i2 = (rot + 2) & 3, i3 = (rot + 3) & 3;
  
  float t0 = isopoint(ds[i1], ds[i2]),
        t1 = isopoint(ds[i2], ds[i3]);
  
  PVector v0 = new PVector(ps[i1][0] + t0 * (ps[i2][0] - ps[i1][0]),
                           ps[i1][1] + t0 * (ps[i2][1] - ps[i1][1]));
  
  PVector v1 = new PVector(ps[i2][0] + t1 * (ps[i3][0] - ps[i2][0]),
                           ps[i2][1] + t1 * (ps[i3][1] - ps[i2][1]));

  addLine(v0, v1);
}

public float isopoint(float d0, float d1)
{
  return abs(d0) / (abs(d0) + abs(d1));
}

public void sketchLine(PVector va, PVector vb, float segment_length)
{
  PVector vd = PVector.sub(vb, va);
  PVector vt = new PVector(vd.y, -vd.x);

  vt.normalize();

  float len = vd.mag();

  int num_segments = (int)ceil(len / segment_length);

  PVector pvc = null;

  for (int i = 0; i <= num_segments; ++i)
  {
    float t = sketchCurve((float)i / (float)num_segments, len);

    PVector vc = sketchPoint(t, va, vb, vt);

    if (pvc != null)
    {
      line(pvc.x, pvc.y, vc.x, vc.y);
      accum_length += PVector.dist(pvc, vc);
    }

    pvc = vc;
  }
}

public float sketchCurve(float t, float len)
{
  return ((t * len) + cos(t * len * 0.5f + accum_length) * 20.0f) / len;
}

public PVector sketchPoint(float t, PVector va, PVector vb, PVector vt)
{
  PVector vc = lerpVectors(va, vb, t);
  float f = 0.05f;
  vc = PVector.add(vc, PVector.mult(vt, (noise(accum_length * f) - 0.5f) * 9.0f + (noise(accum_length * f * 0.05f) - 0.5f) * 18.0f));
  return vc;
}

public PVector lerpVectors(PVector a, PVector b, float x)
{
  return PVector.add(PVector.mult(a, 1.0f - x), PVector.mult(b, x));
}
  public void settings() {  fullScreen();  noSmooth(); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "sketchyiso" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
