/**
 * 
 */
package p5;
import anar.*;


// import geometry.Point3D;


import java.util.*;

import processing.core.PApplet;
import rad.*;


/**
 * @author gll
 * 
 */
public class Test01aDeployRadERandom extends PApplet implements RadObserver {

	/*
	 * Example for Anar library by Guillaume LaBelle + Julien Nembrini
	 * http://anar.ch
	 */


	Obj                myObject;

	Param              angle    = new Param(0.3f);
	Param              invAngle = new Param( -angle.get());


	ArrayList<Element> deploy   = new ArrayList<Element>();

	Transform[]        sides    = new Transform[3];

	// /////////////////////////////////
	// /////////////////////////////////
	// /////////////////////////////////

	public void setup(){

		// size(screen.width,screen.height,OPENGL);
		size(1000,500,OPENGL);
		Anar.init(this);
		Anar.drawAxis(true);

		simThread = new RadEngine(this,10f);


		// generate random deployment
		// must comply with max delta rule
		newDeploy();

		// /////////////////////////////////
		// /////////////////////////////////
		// PREPARE TRANSFORMS

		// We limit the set of transforms to three different
		// It will produce a limited set of different patterns

		// The elementary operation
		Translate modulor = new Translate(Anar.PtNull(0,0,3));


		// Create 3 subsequent Transform from this one
		// I use TransformLinear to combine them as a group
		Transform sideA = new Transform();
		sideA.add(modulor);

		Transform sideB = new Transform();
		sideB.add(modulor);
		sideB.add(modulor);

		Transform sideC = new Transform();
		sideC.add(modulor);
		sideC.add(modulor);
		sideC.add(modulor);

		// Then I have three different transforms from the first one
		// They have different lengths
		// Remark, I ends up with only one parameter


		// Combine them in a table (it will be usefull when randomized)
		// Here I need to remember that 0 is short, 1 normal and 2 is long
		sides[0] = sideA;
		sides[1] = sideB;
		sides[2] = sideC;


		// It's good for sides


		myObject = generatorDeploy(deploy);
		Face.globalRender = new RenderFaceDoubleSide(new AColor(255,180,180),new AColor(220));


	}

	// /////////////////////////////////
	// /////////////////////////////////
	// /////////////////////////////////


	public void draw(){
		if(frameCount%2==0)
			background(255);
		else
			background(230);

		myObject.draw();


		// if(frameCount%1000==750) angle.set(0);
		// if(frameCount%1000==999) angle.set(0.3f);

		if( !isSimRunning){
			myObject = growDeploy(deploy);
			simThread.simulate(myObject);
			simThread.runNow();
			isSimRunning = true;
		}

	}


	// /////////////////////////////////
	// /////////////////////////////////
	// /////////////////////////////////


	// container class for each element of the deployment
	class Element {

		int   A, B;
		float energy;

		Element(int _A, int _B, float _e){
			A = _A;
			B = _B;
			energy = _e;
		}

	}

	// start wiht two elements
	static int nElement   = 2;
	static int maxDelta   = 3;
	static int maxModulor = 3;

	// create new deployment random
	void newDeploy(){

		deploy.clear();
		// Indeed also to track the difference between both (number of times between
		// modulos)
		// At the beginning they are both aligns
		// -1 means that ptsB is from a distance 1 of modulor less than ptsA
		// 2 means that ptsB is from a distance 2 of modulor more than ptsA
		int delta = 0;

		for (int i = 0; i<nElement; i++){

			// Choose one translations from our set.
			// (we won't accept delta to be too long as we want to keep the set of
			// faces to a small set of cases
			int lengthA, lengthB; // Correspond to sides[0,1,2]

			do{
				lengthA = (int)random(maxModulor);
				lengthB = (int)random(maxModulor);
			} while (Math.abs(lengthA-lengthB+delta)>maxDelta); // Here I limit the
			// maximum sitance
			// between paths (both
			// ways)

			// Update new delta state
			delta += lengthA-lengthB;

			deploy.add(new Element(lengthA,lengthB,0f));
		}
	}

	Obj growDeploy(ArrayList<Element> d){

		// add an element to the deployment
		// in order to increase the energy gathered

		// first compute delta
		int delta = 0;
		ListIterator<Element> it = d.listIterator();
		while (it.hasNext()){
			Element e = it.next();
			delta += e.A-e.B;
		}
		// compute new element according to energy from last element
		float energieAA = d.get(nElement-2).energy;
		float energieA = d.get(nElement-1).energy;

		int lengthA = 0;
		int lengthB = 0;

		// if energy is growing increase delta trend
		if(energieAA<energieA){
			if(delta>0&&delta<maxDelta){
				lengthA = 2;
				lengthB = 1;
			}
			else
				if(delta<0&&delta> -maxDelta){
					lengthA = 1;
					lengthB = 2;
				}
				else{
					lengthA = 1;
					lengthB = 1;
				}
		}
		// if energy decreases inverse delta trend
		else{
			if(delta>0){
				lengthA = 1;
				lengthB = 2;
			}
			else
				if(delta<0){
					lengthA = 2;
					lengthB = 1;
				}
				else{
					lengthA = 1;
					lengthB = 1;
				}
		}

		d.add(new Element(lengthA,lengthB,0f));
		nElement++;
		return generatorDeploy(d);
	}

	// //////////////////////////////////
	// //////////////////////////////////
	// //////////////////////////////////


	Obj generatorDeploy(ArrayList<Element> d){

		// /////////////////////////////////
		// /////////////////////////////////
		// INIT SOME CONTAINERS
		Obj outputFmz = new Obj();


		// Now let's create a rotation (let's keep it simple with only one rotation)
		// On the chantier, it correspond to uniforms clips between panels

		// I need to create a RotateZ as it will be used inside Allingn
		// Allign will allign an axis to Z coordinate then apply a transform and
		// Come back to initial state
		RotateZ myRotation = new RotateZ(0.1f);
		RotateZ myRotation2 = new RotateZ(0);
		RotateZ myRotation3 = new RotateZ( -0.1f);


		// /////////////////////////////////
		// /////////////////////////////////
		// ASSIGN TRANSFORMS

		// I<ll create to lines and combine them later
		Pts ptsA = new Pts();
		Pts ptsB = new Pts();

		// I need two initial points
		// This is where I set the side length of the whole thing
		Pt originA = Anar.Pt(0,0,0,"originA");
		Pt originB = Anar.Pt(0,10,0,"originB");

		// Add them to the list
		ptsA.add(originA);
		ptsB.add(originB);

		// (Update) We need those POints to orient the translation
		PtDER originAA = Anar.Pt(originA);
		PtDER originBB = Anar.Pt(originB);

		originAA.apply(sides[0]);
		originBB.apply(sides[0]);

		// Add them to the list
		ptsA.add(originAA);
		ptsB.add(originBB);


		// As the form is an inerplay between aNewPoint and a previousPt
		// I<ll create two fields to track them
		// Note, it's not derrived, it is the point itself (Pt previousA =
		// Anar.Pt(originA))
		Pt previousA = originAA;
		Pt previousB = originBB;

		Pt previousAA = originA;
		Pt previousBB = originB;

		for (int i = 0; i<d.size(); i++){
			// Create a points from previous (i label them - optional)
			PtDER newPtA = Anar.Pt(previousA,"A"+i);
			PtDER newPtB = Anar.Pt(previousB,"B"+i);

			// Choose one translations from our set.


			// Create Rotation from an axis (eachPoint is different
			// allign need an axis of rotation (defined here by the two old resulting
			// points (previuos)
			// axis = previousA,previousB
			Transform axisRotateA, axisRotateB;
			if(random(2)>1){
				axisRotateA = new Transform(previousA,previousB,myRotation);
				axisRotateB = new Transform(previousA,previousB,myRotation);
			}
			else{
				axisRotateA = new Transform(previousA,previousB,myRotation3);
				axisRotateB = new Transform(previousA,previousB,myRotation3);
			}

			// Create a Translation alligned with the previous
			// Remember that we don't know how is oriented the last face
			Transform orientedTranslationA = new Transform(previousAA,previousA,sides[d.get(i).A]);
			Transform orientedTranslationB = new Transform(previousBB,previousB,sides[d.get(i).B]);


			// Apply to rotation to the translation
			Transform comboA = new Transform();
			comboA.add(orientedTranslationA);
			comboA.add(axisRotateA); // From the beginning


			Transform comboB = new Transform();
			comboB.add(orientedTranslationB);
			comboB.add(axisRotateB); // From the beginning


			// Here's where evrything is set together pt with transform
			newPtA.apply(comboA);
			newPtB.apply(comboB);

			// Alternative (if we don't want to apply the rotation
			// Use this to see only the translation effect on a plane
			// newPtA.set(sides[lengthA]);
			// newPtB.set(sides[lengthB]);


			// Put all that in that containers
			ptsA.add(newPtA);
			ptsB.add(newPtB);

			// Swap Previuos
			previousAA = previousA;
			previousBB = previousB;

			previousA = newPtA;
			previousB = newPtB;
		}


		// /////////////////////////////////
		// /////////////////////////////////
		// RETURN EVRYTHING

		// cREATE FACES FROM TWO LINES WITH SAME NUMBERS OF POINTS
		outputFmz = new SweepTwoPaths(ptsA,ptsB);

		// Cosmetik
		print(ptsA);
		print(ptsB);
//		print(outputFmz.primitiveToString( -1));
		TextIO.write("test.lsp",outputFmz.toAutocad());

		ptsA.color(new AColor(0,0,255));
		ptsB.color(new AColor(255,150,150));

		return outputFmz;

	}


	// /////////////////////////////////
	// /////////////////////////////////
	// /////////////////////////////////
	// So Rad
	boolean   isSimRunning = true;

	RadEngine simThread;



	@Override
	public void radSimDone() {
		isSimRunning = false;
		// SwitchRender
		// MetaRad.switchToAverageRender(myObject);

		// here integrate energy results into deployment
		ListIterator<Face> it = myObject.faces.listIterator();
		int count = 0;
		Face dummy = it.next();
		while (it.hasNext()){
			deploy.get(count).energy = ((RenderRadSim)it.next().render).energyDensityFront;
			count++;
		}


		// myObject = transformatorDeploy(deploy);
	}


	public void keyPressed(){
		switch(key){
		case 'q':
			simThread.simulate(myObject);
			simThread.runNow();
			isSimRunning = true;
			break;
		case ' ':
			// myObject = generatorDeploy();
			break;
		case 'w':
			//        MetaRad.switchRender(myObject);
			break;
		case 'e':
			//        MetaRad.switchToAverageRender(myObject);
			break;
		case 'p':
			Scene.autoSeek = false;
			break;
		case 'r':
			angle.set(0);
			invAngle.set( -angle.get());
			break;
		case 't':
			angle.set(0.3f);
			invAngle.set( -angle.get());
			break;

		}
	}

	// /////////////////////////////////
	// /////////////////////////////////
	// /////////////////////////////////


	public static void main(String[] args){
		PApplet.main(new String[]{Test01aDeployRadERandom.class.getName()});
	}

}

