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

import damkjer.ocd.*; 
import moonlander.library.*; 
import java.util.logging.*; 
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 assydemo extends PApplet {



//import queasycam.*;




// Minim is needed for the music playback
// (even when using Moonlander)


float TURN = PI*2;

float worblePos = 0f;

// These control how big the opened window is.
// Before you release your demo, set these to 
// full HD resolution (1920x1080).
int CANVAS_WIDTH = 1920; //480;
int CANVAS_HEIGHT = 1080; // 360;
//int CANVAS_WIDTH = 800;
//int CANVAS_HEIGHT = 600;

int fps = 60;

// For syncing with music etc
Moonlander moonlander;

// Camera
//QueasyCam cam;
Camera camera;

PVector camPos = new PVector(20, -10, 10);
PVector focusPos = new PVector(0,-5,0);


public float sinNoise(float t, float x) {
  return 0.5f*sin(t * x) + 0.5f*cos(t * 0.31232f * x) + 0.5f*sin( (t * 0.123f + cos(t * 0.001351f)) * 0.871f * x);
}

public float shakyNoise(float time, float freq, float bass, float discant, float seed) {
  return (noise(time * 0.2f*freq, seed * 123.321f) * 2 - 1) * bass +
         (noise(time * 1*freq, seed * 887.213f) * 2 - 1) * discant; 
}

/*
 * settings() must be used when calling size with variable height and width
 */
public void settings() {
  // Set up the drawing area size and renderer (P2D / P3D).
  size(CANVAS_WIDTH, CANVAS_HEIGHT, P3D);
  fullScreen(P3D);
  randomSeed(8719);
  noiseSeed(2131);

}


public void setup() {
  
  noCursor();
  
  rectMode(CENTER);
  
  translate(width /2, height/2);
  scale(height / 1000.0f);
  
  // Setup camera
  camera = new Camera(this, -20, 0, 0);
  camera.aim(0,0,0);
  camera.feed();
  
  frameRate(fps);


  // Parameters: 
  // - PApplet
  // - soundtrack filename (relative to sketch's folder)
  // - beats per minute in the song
  // - how many rows in Rocket correspond to one beat
  moonlander = Moonlander.initWithSoundtrack(this, "babbys_first_pumpkin_patch.mp3", 222, 8);
//  moonlander.changeLogLevel(Level.FINEST);

  // Last thing in setup; start Moonlander. This either
  // connects to Rocket (development mode) or loads data 
  // from 'syncdata.rocket' (player mode).
  // Also, in player mode the music playback starts immediately.
  //moonlander.start("localhost", 9001, "syncfile");
  moonlander.start();
  
  setupPlants();


  
}



public void draw() {
  background(0);
  fill(255);
  //resetShader();

  
  // Handles communication with Rocket
  moonlander.update();

  int stopNow = moonlander.getIntValue("stopNow");
  if (stopNow >= 1) {
    exit();
  }

  // Seconds since start
  float time = (float) moonlander.getCurrentTime();
//  float time = millis() / 1000.0;
  float deltaTime = 1f / fps; 

  
  float cameraSpeed = (float)moonlander.getValue("camera:speed");
  float cameraDistance = (float)moonlander.getValue("camera:distance");
  float cameraFocusHeight = (float)moonlander.getValue("camera:focusHeight");

  camPos.x = cameraDistance * cos(TURN*time*cameraSpeed);
  camPos.z = cameraDistance * sin(TURN*time*cameraSpeed);
  camPos.y = (float)moonlander.getValue("camera:height");

  camera.jump(camPos.x, camPos.y, camPos.z);
  camera.aim(focusPos.x, focusPos.y + cameraFocusHeight, focusPos.z);
  camera.feed();
  
  // Debug lines
  noStroke();
 // stroke(0,255, 0);
 
// resetShader();
  //lights();

  // Sunlight
  float sunWorbleAmount = (float) moonlander.getValue("sun:worbleAmount");
  float sunWorbleSpeed = (float) moonlander.getValue("sun:worbleSpeed");
  worblePos += deltaTime*sunWorbleSpeed;  
  directionalLight(255, 200, 50, sin(worblePos)*sunWorbleAmount, -1, -cos(worblePos)*sunWorbleAmount);

  // Skylight
  directionalLight(20, 40, 140, 0,-1,0);
  ambientLight(30,50,80);

  // Doom Light
   directionalLight(200, 50, 10, 0.2f,0.1f,0.1f);

  // Update plants
  treeAge = (float) moonlander.getValue("plants:tree");
  singleGrassAge = (float) moonlander.getValue("plants:singleGrass");
  singleFlowerAge = (float) moonlander.getValue("plants:singleFlower");

  grassPatchAge = (float) moonlander.getValue("plants:grassPatch");
  flowerPatchAge = (float) moonlander.getValue("plants:flowerPatch");

  
  fill(255, 255, 255);

  drawPlants(time);
  
  noStroke();

}
class Apple extends PlantPart {
  
  float size = 10f;
  int youngColor = 0xff40A020;
  int oldColor = 0xffA03060;
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
    if (context.plantAge <= 0){
        drawFlower();  
        
    
    
    }  
    else{
        drawFuit(context);
    }  
    
  }
  
  
  public void drawFlower(){
    //fill(oldColor);
    //translate(5, -5,5);
    //sphere(0.5);
  }  
  
  public void drawFuit(PlantContext context){
    
    float s = size*context.age;
    translate(0, -s/2,0);
    fill(lerpColor(youngColor, oldColor, context.age));
    sphereDetail(16);
    sphere(s);
  }  
  

  
  
}
class Ball extends PlantPart {
  
  float size = 10f;
  int youngColor = 0xff40A020;
  int oldColor = 0xffA03060;
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
    float s = size * context.age;
    
    translate(0, -s/2,0);
    fill(lerpColor(youngColor, oldColor, context.age));
    sphereDetail(16);
    sphere(s);
  }
  
  
}
class Branch extends PlantPart {
  PlantPart tip = null;
  PlantPart branch = null;
  
  PVector endPos;
  int sideAmount = 12;
  float maxBrancHeight = 70;
  float branchMaxWidth = 0.8f;
  float minWidth = 0.05f;
  float branchChange = branchMaxWidth-minWidth;
  float maxOfset = 0.4f;
  int branchColor = color(222,184,135);
  int brancHeight = 0;
  float branchStep = 1;
  PVector branchEndPos = new PVector(0,0,0);
  int numberOfBranches = 10;


  float branchesInGrowDirection = 0.3f;
  
  
  Branch(PlantPart tip, PlantPart branch){
    this(tip, branch, 0.3f, 20f, 70f);
  }
  
  Branch(PlantPart tip, PlantPart branch, float branchesInGrowDirection, float numBranches, float branchLength){
    this.tip = tip;
    this.branch = branch;
    this.branchesInGrowDirection = branchesInGrowDirection;
    this.maxBrancHeight = branchLength;
    this.numberOfBranches = max(0, (int)numBranches);
  }  
  
   Branch(float branchLength, int branchColor, float maxWidth, float minWidth){
    this.maxBrancHeight = branchLength;
    this.numberOfBranches = 0;
    this.branchColor = branchColor;
    this.branchMaxWidth = branchMaxWidth;
    this.minWidth = minWidth;
    float branchChange = branchMaxWidth-minWidth;
    
  }  
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
    noStroke();
    drawBranch(context,random);
    pushMatrix();
    translate(branchEndPos.x, branchEndPos.y, branchEndPos.z);
    scale(-0.1f*context.age);
    
    if (tip != null) tip.drawPart(context,random.nextRandom());
    popMatrix();
    
    
  }  
  
  public void drawBranch(PlantContext context, RandomSequence random) {
    float xofset = 0;
    float yofset = 0;
    brancHeight = (int) mapClamp(context.age, 0f, 0.5f, 0f, maxBrancHeight);
    float prevR = context.age*(pow(1, 1.2f)*(branchChange))*(brancHeight/maxBrancHeight)+ minWidth;
    float prevOfsetx = 0;
    float prevOfsety = 0;
    float prevY  = 0;
    
    float branchingAngle = random.nextFloat(2*PI);
    final float PHI = 2.3999632f;
    
    
    PlantContext branchContext = context.copy();
    
    int stepsBetweenBranch = numberOfBranches <= 0 ? MAX_INT : (int)maxBrancHeight/numberOfBranches;
    int stepsUntilNext = stepsBetweenBranch+((int)random.nextFloat(4));
    
    for (int i = 1; i <= brancHeight; i += 1){     
      float branchToTop = ((float)(brancHeight-i)/brancHeight);
      float r =  context.age*(pow((((float)brancHeight-i)/brancHeight), 1.2f)*(branchChange))*pow(branchToTop, 1.5f)+ minWidth;
      xofset += (random.nextFloat(2*maxOfset)-maxOfset)*pow(context.age, 2)*branchToTop*branchToTop;
      yofset += (random.nextFloat(2*maxOfset)-maxOfset)*pow(context.age, 2)*branchToTop*branchToTop;
      float ystep = branchStep*context.age*pow(branchToTop,2);
      float y = prevY-ystep;
      drawLayer(prevY, y,prevR*context.age,r*context.age, prevOfsetx, prevOfsety,xofset, yofset );
      prevR = r;
      prevOfsetx = xofset;
      prevOfsety = yofset;
      
      // Add side branch
      float minAgeForChildBranches = 0.1f;
      if (stepsUntilNext <= 0 && branch != null && context.age >= minAgeForChildBranches) {
          pushMatrix();
          translate(branchEndPos.x, branchEndPos.y, branchEndPos.z);
          scale(0.9f);
          
          // Rotate around tree
          //rotateY(random.nextFloat(2*PI));
          rotateY(branchingAngle);
          branchingAngle += PHI;
          
          // Rotate out from tree
          rotateX((branchesInGrowDirection)*PI+random.nextGaussishFloat(0f, 0.4f));
          
          branchContext.age = mapClamp(context.age, 0.3f, 1f, 0.2f, 0.8f) * (pow(branchToTop, 1.5f));
          branch.drawPart(branchContext, random.nextRandom());
          stepsUntilNext = stepsBetweenBranch + random.nextInt(0, 3);
          popMatrix();
      }
      
      stepsUntilNext --;
      
      
      prevY = y;
      
    }  
    
  }
  
  
  
  
  public void drawLayer(float prevY, float y, float prevR, float r, float prevOfsetx, float prevOfsety, float xofset, float yofset){
    beginShape(TRIANGLE_STRIP);
    fill(branchColor);
    for(int i = 0; i < sideAmount+1; i +=1){
      drawVertex(i,y,r,xofset,yofset);
      drawVertex(i,prevY,prevR,prevOfsetx,prevOfsety);
    }  
    
    endShape(TRIANGLE_STRIP);
  }  
  
  public void drawVertex(float i, float y,  float r, float xofset, float yofset){
      float a = i*1/(sideAmount+0.001f);
      float x = cos(2*PI*a)*r+ xofset;
      float z = sin(2*PI*a)*r + yofset;
      
      vertex(x, y, z);     
      branchEndPos.x = x;
      branchEndPos.y = y;
      branchEndPos.z = z;
      
  }
  
  
}

class CubePart extends PlantPart {
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
    float size =context.age+0.2f;
    fill(200, 0, 70);
    translate(0, -size/2,0);
    box(size,size,size);
  }
  
  
  public void drawCylinder(){
  
  }

}
class Grass extends PlantPart {
  float stalkLength = 2.5f;
  int branchColor  =color(50,200,60);
  int sideAmount = 12;
  PVector branchEndPos = new PVector(0,0,0);
  int grassAmount = 20;
   float patchWidth = 10;
 
 Grass(){
   
 }
 Grass(int grassAmount, int patchWidth){
   this.grassAmount = grassAmount;
   this.patchWidth = patchWidth;
 }

  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
      //stalk
      for (int i = 0; i < grassAmount; i  ++ ){
          
          pushMatrix();
          translate((random.nextFloat()*patchWidth)- (0.5f*patchWidth),0,(random.nextFloat()*patchWidth)- (0.5f*patchWidth));
          rotateX(PI-random.nextFloat(0.25f));
          float stalkL = stalkLength*context.age*(random.nextFloat(0.5f)+0.5f);
        
          drawLayer(0, stalkL*0.5f, 0.08f, 0.05f, 0, 0, 0,0 );
          
     
          drawLayer(stalkL*0.5f, stalkL, 0.05f, 0.01f, 0, 0, random.nextFloat(0.5f)-0.25f,0 );
         
          popMatrix();
      
      }
    
    
  }
  
  
  
  
  public void drawLayer(float prevY, float y, float prevR, float r, float prevOfsetx, float prevOfsety, float xofset, float yofset){
    beginShape(TRIANGLE_STRIP);
    fill(branchColor);
    noStroke();
    for(int i = 0; i < sideAmount+1; i +=1){
      drawVertex(i,y,r,xofset,yofset);
      drawVertex(i,prevY,prevR,prevOfsetx,prevOfsety);
    }  
    
    endShape(TRIANGLE_STRIP);
    }  
  
  public void drawVertex(float i, float y,  float r, float xofset, float yofset){
      float a = i*1/(sideAmount+0.001f);
      float x = cos(2*PI*a)*r+ xofset;
      float z = sin(2*PI*a)*r + yofset;
      
      vertex(x, y, z);     
      branchEndPos.x = x;
      branchEndPos.y = y;
      branchEndPos.z = z;
      
  }
}
      

class Leaf extends PlantPart {
  
  private PVector p1 = new PVector();
  private PVector p2 = new PVector();
  private PVector p3 = new PVector();
  int leafCol = color(50,200,60);
  
  
  Leaf(){
    
  }
  
  
  Leaf(int leafColor){
    leafCol = leafColor;
  }
    
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
    int leafSegments = 6;
    float leafLength = 1;
    float leafWidth = 0.5f;
    
    float upTilt = 0.3f;

    fill(leafCol);  
    noStroke();
    
    beginShape(TRIANGLES);
    
    for (int i = 0; i < leafSegments; i++) {
      float relPos = map(i, 0, leafSegments, 0f, 1f);
      float nextRelPos = map(i + 1, 0, leafSegments, 0f, 1f);
      
      leafEdgePoint(relPos, 0.5f*leafWidth, leafLength, upTilt, p1);  
      leafCenterPoint(relPos, leafLength, p2);
      leafCenterPoint(nextRelPos, leafLength, p3);
      addTriangle(p1, p2, p3);

      leafCenterPoint(nextRelPos, leafLength, p1);
      leafEdgePoint(nextRelPos, 0.5f*leafWidth, leafLength, upTilt, p2);  
      leafEdgePoint(relPos, 0.5f*leafWidth, leafLength, upTilt, p3);  
      addTriangle(p1, p2, p3);
      
      leafCenterPoint(relPos, leafLength, p1);
      leafEdgePoint(relPos, -0.5f*leafWidth, leafLength, upTilt, p2);  
      leafCenterPoint(nextRelPos, leafLength, p3);
      addTriangle(p1, p2, p3);

      leafEdgePoint(nextRelPos, -0.5f*leafWidth, leafLength, upTilt, p1);  
      leafCenterPoint(nextRelPos, leafLength, p2);
      leafEdgePoint(relPos, -0.5f*leafWidth, leafLength, upTilt, p3);  
      addTriangle(p1, p2, p3);
      
    }
    
    endShape();

  }
  
  public void leafCenterPoint(float relPos, float leafLen, PVector pos) {
      float y = mix(relPos, 0, -leafLen);
      pos.x = 0;
      pos.y = y;
      pos.z = 0;
  }
  
  public void leafEdgePoint(float relPos, float leafR, float leafLen, float upTilt, PVector pos) {
       float cutoverPoint = 0.8f;
      float baseW = pow(mapClamp(relPos, 0, cutoverPoint, 0, 1), 0.5f);
      float tipW = 0.5f*(1+cos(TURN*mapClamp(relPos, cutoverPoint, 1, 0, 1)*0.5f));
      float w = mix(relPos, baseW, tipW);
      
      float x = 0.5f*leafR * w;
      float z = 0 + upTilt * abs(leafR) * sin(0.5f*TURN*min(relPos, 1-relPos));
      float y = mix(relPos, 0, -leafLen);
      
      pos.x = x;
      pos.y = y;
      pos.z = z;
 }
  
}

class Plant {
  PVector groundPos;
  PVector growDirection;

  long seed;

  PlantPart part;
  
  PlantContext context = new PlantContext();
  
  private RandomSequence random = new RandomSequence();
  
  Plant(PVector pos, long seed, PlantPart part){
     this.seed = seed;
     this.part = part;
     groundPos = pos.copy();
     growDirection = new PVector(0, 0, 1);
  }  
  
  public void drawPlant(){
    random.setSeed(seed);

    pushMatrix();
    translate(groundPos.x, groundPos.y, groundPos.z);
    
    rotateY(random.nextFloat(0, 2*PI));
    
    noFill();
    stroke(255);

    part.drawPart(context, random);
    
    popMatrix();
  }  
  
  public void init(RandomSequence random) {
    part.init(random);
  }
   
}

// Info about a plant that affects parts.
class PlantContext {
  
  
  float age = 0.5f;
  float plantAge = 0.5f;
  
  PlantContext(float age){
     this.age = age; 
     plantAge = age;
  }
  
  PlantContext(){
  }
  
  public PlantContext copy() {
    PlantContext copy = new PlantContext(age);
    copy.plantAge = plantAge;
    copy.age = age;
    return copy;
  }
}

/**
* Something that a plant is built from.
*/
class PlantPart {
  
  private ArrayList<PlantPart> childParts = new ArrayList<PlantPart>();
  
  public ArrayList<PlantPart> getChildParts() {
    return childParts;
  }
  
  public void addChildPart(PlantPart part) {
    childParts.add(part);
  }
  
  public final void init(RandomSequence random) {    
    doInit(random.nextRandom());

    for (PlantPart part : getChildParts()) {
      part.init(random.nextRandom());
    }
  }  
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
  }
  

}

ArrayList<Plant> flowers = new ArrayList<Plant>();
float treeAge = 1;
float singleGrassAge = 1;
float singleFlowerAge = 1;
float grassPatchAge = 1;
float flowerPatchAge = 1;
Plant tree;
Plant singleGrass;
Plant singleFlower;
Plant grassPatch;

public void setupPlants(){
  RandomSequence random = new RandomSequence(21348);
  
 tree = new Plant(new PVector(0,0,0), 42, 
                new Branch(
                   new Leaf(), 
                   new Branch(
                     new Leaf(), 
                     new Branch(

                       new Leaf(), 
                       new Leaf()))));
                        
  singleFlower = new Plant(new PVector(0,0,0), 42, 
                new Flower(
                  new Leaf(color(100,100,200)),
           
                  new Leaf(), new PVector(0,0,0)
                )
              );
              
   grassPatch = new Plant(new PVector(0,0,0), 42, new Grass(100, 30));    
    singleGrass = new Plant(new PVector(0,0,0), 42, new Grass(1, 0));          


  for (int i = 0; i < 75; i++) {
    flowers.add(new Plant(new PVector(0,0,0), 42, 
                new Flower(
                  new Leaf(color(random(50, 255),random(50, 255),random(50, 255))),
           
                  new Leaf(), new PVector(((random(1)*25)- (0.5f*25)),0,(random(1)*25)- (0.5f*25))
                )
              ));
              
     
  }


} 


public void drawPlants(float time){
  tree.context.age = treeAge;
  singleGrass.context.age = singleGrassAge;
  singleFlower.context.age = singleFlowerAge;
  grassPatch.context.age = grassPatchAge;
  //println(testPlant.context.age);
  
  tree.drawPlant(); 
  
  grassPatch.drawPlant();
  
  singleGrass.drawPlant(); 
  pushMatrix();
  translate(3, 0,0);
  singleFlower.drawPlant();
  popMatrix();
  
  
  for (Plant plant : flowers) {
    plant.context.age = flowerPatchAge;
    pushMatrix();
    plant.drawPlant();
    popMatrix();
  }
  
}
  
  

// Random Sequence
class RandomSequence {
  long state0 = 0;
  long state1 = 0;
  
  boolean haveExtraGaussian = false;
  private double extraGaussian;
  
  RandomSequence() {
    setHashedSeed(hash(System.nanoTime()));        
  }
  
  RandomSequence(long seed) {
    setHashedSeed(hash(seed));        
  }
  
  public void setHashedSeed(long seed) {
     state0 = seed;

     // Derive second seed from the original using a hash function
     state1 = hash(hash(seed));

     // Replace zero seeds with arbitrary seeds
     if (state0 == 0L) state0 = 9045324987L;
     if (state1 == 0L) state1 = 3912353188L;
  }
  
  public void setSeed(double seed, double ... additionalSeeds) {
    // Hash the user provided seeds to spread them out over the long space.
    // Combine several seeds by hashing and xoring them together
    // Maps double bits to longs
  
    long hashedSeed = hash(java.lang.Double.doubleToLongBits(seed));
  
    for (double additionalSeed : additionalSeeds) {
        hashedSeed = hash(hashedSeed);
        hashedSeed = hashedSeed ^ hash(java.lang.Double.doubleToLongBits(additionalSeed));
    }
  
    hashedSeed = hash(hashedSeed);
  
    // Clear any cached generated gaussian
    haveExtraGaussian = false;
  
    setHashedSeed(hashedSeed);
  }
  
    public boolean nextBoolean(double probabilityForTrue) {
        return nextDouble() < probabilityForTrue;
    }

    public boolean nextBoolean(float probabilityForTrue) {
        return nextFloat() < probabilityForTrue;
    }

    public int nextInt() {
        return (int)(nextLong() >> 32); // Use signed right shift
    }
    
    public int nextInt(int minInt, int maxInt) {
      return nextInt() % (maxInt - minInt) + minInt;
    }
    
    public int nextInt(int maxInt) {
      return nextInt(0, maxInt);
    }
    
    public float nextFloat() {
      return nextBits(24) / (float)(1 << 24);
    }
    
    public float nextFloat(float max) {
        if (max < 0) throw new IllegalArgumentException("max should not be negative, but it was " + max);

        return nextFloat() * max;
    }

    public float nextFloat(float min, float max) {
      return min(min, max) + nextFloat(abs(max-min));
    }

    public float nextGaussishFloat(float mean, float stdDev) {
      return nextFloat(-1f, 1f) *
             nextFloat(-1f, 1f) *
             nextFloat(-1f, 1f) *
             nextFloat(-1f, 1f) *
             stdDev + mean;
    }


/*
    double nextGaussian() {
        if (haveExtraGaussian) {
            // Use earlier extra gaussian if available
            haveExtraGaussian = false;
            return extraGaussian;
        } else {
            double linearRandom1;
            double linearRandom2;
            double squaredDistance;
            do {
                // Generate two linear random numbers in -1 .. 1 range
                linearRandom1 = 2 * nextDouble() - 1;
                linearRandom2 = 2 * nextDouble() - 1;
                squaredDistance = linearRandom1 * linearRandom1 + linearRandom2 * linearRandom2;
            } while (squaredDistance >= 1 || squaredDistance == 0.0);

            double multiplier = StrictMath.sqrt(-2 * StrictMath.log(squaredDistance) / squaredDistance);

            double gaussian = linearRandom1 * multiplier;

            // Store the extra random gaussian for future use
            extraGaussian = linearRandom2 * multiplier;
            haveExtraGaussian = true;

            return gaussian;
        }
    }

    float nextGaussianFloat() {
        return (float)nextGaussian();
    }

    float nextGaussianFloat(float mean, float standardDeviation) {
        return (float) nextGaussian() * standardDeviation + mean;
    }


    double nextGaussian(double mean, double standardDeviation) {
        return nextGaussian() * standardDeviation + mean;
    }

*/
    public double nextDouble() {
        return nextBits(53) / (double)(1L << 53);
    }


    private long nextBits(int numberOfBits) {
        return nextLong() >>> (64 - numberOfBits); // Use unsigned right shift, most significant bits will be zero
        // and the number will be positive if numberOfBits > 0
    }



  public long nextLong() {

        long result = state0 + state1;

        long t1 = state1 ^  state0;
        state0 = (java.lang.Long.rotateLeft(state0, 55) ^ t1) ^ (t1 << 14); // a, b
        state1 = java.lang.Long.rotateLeft(t1, 36); // c

        return result;
    }

  

    public RandomSequence nextRandom() {
        return new RandomSequence(nextLong());
    }

  
    public long hash(long input) {
        long value = input;

        // If the input is zero, replace it with an arbitrary non-zero constant
        if (value == 0L) value = 8726312;

        value = value ^ (value >>> 33);
        value *= CONSTANT_A;
        value = value ^ (value >>> 33);
        value *= CONSTANT_B;
        return value ^ (value >>> 33);
    }
    
   public static final long CONSTANT_A = 0xff51afd7ed558ccdL;
    public static final long CONSTANT_B = 0xc4ceb9fe1a85ec53L;

}

// Interpolated value that changes with plant age.
class Value {
  float start = 0f;
  float end = 1f;
  boolean smooth = false;

  Value() {
  }

  Value(boolean smooth) {
    this.smooth = smooth;
  }

  Value(float start, float end, boolean smooth) {
    this.start = start;
    this.end = end;
    this.smooth = smooth;
  }

  Value(float start, float end) {
    this(start, end, false);
  }

  public float get() {
    return get(0.5f);
  }

  public float get(PlantContext context) {
    return get(context.age);
  }

  public float get(float pos) {
    if (smooth) {
      return smoothMix(pos, start, end);
    }
    else {
      return mix(pos, start, end);
    }
  }
}
class Flower extends PlantPart {
  int petalAmount = 7;
  PlantPart petal;
  PlantPart stalk;
  PlantPart leaf;
  float petalLenght = 1.5f;
  float stalkLength = 3;
  int branchColor  =color(50,200,60);
  int sideAmount = 12;
  PVector branchEndPos = new PVector(0,0,0);
  PVector pos = new PVector(0,0,0);
  float flowerRot = 0;
  
  Flower(PlantPart petal,  PlantPart leaf, PVector pos){
    this.petal = petal;
    this.pos = pos;
    this.petalLenght = 1 + random(1);
    this.stalkLength = 2.5f + random(1);
    this.leaf = leaf;
    flowerRot = random(-1, 1);
  }  
  
  public void doInit(RandomSequence random) {
  }
  
  public void drawPart(PlantContext context, RandomSequence random) {
    pushMatrix();
    translate(pos.x, pos.y, pos.z);
      //stalk
    pushMatrix();
      rotateX(PI);
        drawLayer(0, stalkLength*context.age, 0.1f, 0.05f, 0, 0, 0,0 );
      popMatrix();
    
    pushMatrix();
      
      translate(0,-stalkLength*context.age, 0);
      
      
      scale(petalLenght*pow(context.age,2));
      
      rotateX((0.5f*PI));
      rotateY(0.5f+flowerRot);
      
      //float rotateStep = radians(137.508);
      float rotateStep = 2*PI/petalAmount;
      for (int i = 0; i < petalAmount; i++){
         
          rotateZ(rotateStep);
         petal.drawPart(context,random.nextRandom());
         
      }  
      popMatrix();
      pushMatrix();
        translate(0,-stalkLength*context.age*0.5f, 0);
        
        scale(petalLenght*pow(context.age,2));
        rotateY(0.5f);
        rotateX((0.35f*PI));
        
        leaf.drawPart(context,random.nextRandom());
        
      popMatrix();
      pushMatrix();
      translate(0,-stalkLength*context.age*0.5f, 0);
        
        scale(petalLenght*pow(context.age,2));
        rotateY(0.5f);
        rotateX((0.5f*PI));
        rotateZ(PI);
        rotateX((-0.15f*PI));
        leaf.drawPart(context,random.nextRandom());
      popMatrix();
      popMatrix();
  }
  
  
  
  public void drawLayer(float prevY, float y, float prevR, float r, float prevOfsetx, float prevOfsety, float xofset, float yofset){
    beginShape(TRIANGLE_STRIP);
    fill(branchColor);
    noStroke();
    for(int i = 0; i < sideAmount+1; i +=1){
      drawVertex(i,y,r,xofset,yofset);
      drawVertex(i,prevY,prevR,prevOfsetx,prevOfsety);
    }  
    
    endShape(TRIANGLE_STRIP);
    }  
  
  public void drawVertex(float i, float y,  float r, float xofset, float yofset){
      float a = i*1/(sideAmount+0.001f);
      float x = cos(2*PI*a)*r+ xofset;
      float z = sin(2*PI*a)*r + yofset;
      
      vertex(x, y, z);     
      branchEndPos.x = x;
      branchEndPos.y = y;
      branchEndPos.z = z;
      
  }
      
      
      
      
  

}

public float mix(float t, float a, float b) {
  return (b - a) * t + a;
}

public double mix(double t, double a, double b) {
  return (b - a) * t + a;
}

public float smoothMix(float t, float a, float b) {
  float relPos = 0.5f*(1.0f+cos(map(t, 0, 1, PI, 2*PI)));
  return mix(relPos, a, b);
}

public float mapClamp(float value, float sourceStart, float sourceEnd, float destStart, float destEnd) {
  return map(constrain(value, sourceStart, sourceEnd), sourceStart, sourceEnd, destStart, destEnd);
  
}

public float smoothMap(float value, float sourceStart, float sourceEnd, float destStart, float destEnd) {
  float relPos = 0.5f*(1.0f+cos(map(value, sourceStart, sourceEnd, PI, 2*PI)));
  return mix(relPos, destStart, destEnd);
}

public float smoothMapClamp(float value, float sourceStart, float sourceEnd, float destStart, float destEnd) {
  float relPos = 0.5f*(1.0f+cos(mapClamp(value, sourceStart, sourceEnd, PI, 2*PI)));
  return mix(relPos, destStart, destEnd);
}

public void normal(PVector n) {
  normal(n.x, n.y, n.z);
}

public void vertex(PVector v) {
  vertex(v.x, v.y, v.z);
}

public void translate(PVector v) {
  translate(v.x, v.y, v.z);
}


private PVector t1 = new PVector();
private PVector t2 = new PVector();
public PVector faceNormal(PVector p1, PVector p2, PVector p3, PVector normal) {
  t1.set(p1);
  t1.sub(p2);
  
  t2.set(p1);
  t2.sub(p3);

  t1.cross(t2, normal);
  return normal;
}

private PVector tNormal = new PVector();

public void addTriangle(PVector p1, PVector p2, PVector p3) {
  faceNormal(p1, p2, p3, tNormal);
  normal(tNormal);
  vertex(p1);
  vertex(p2);
  vertex(p3);
}

public void addTriangle(PVector p1, PVector p2, PVector p3, int c) {
  addTriangle(p1, p2, p3, c, c, c);
}

public void addTriangle(PVector p1, PVector p2, PVector p3, int c1, int c2, int c3) {
  faceNormal(p1, p2, p3, tNormal);
  normal(tNormal);

  fill(c1);
  vertex(p1);

  fill(c2);
  vertex(p2);

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