package com.haxademic.core.draw.shapes;

import com.haxademic.core.app.P;

import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PVector;

public class MeshShapes {
	// M_3_3_02.pde
	// Mesh.pde
	// 
	// Generative Gestaltung, ISBN: 978-3-87439-759-9
	// First Edition, Hermann Schmidt, Mainz, 2009
	// Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
	// Copyright 2009 Hartmut Bohnacker, Benedikt Gross, Julia Laub, Claudius Lazzeroni
	//
	// http://www.generative-gestaltung.de
	//
	// Licensed under the Apache License, Version 2.0 (the "License");
	// you may not use this file except in compliance with the License.
	// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
	// Unless required by applicable law or agreed to in writing, software
	// distributed under the License is distributed on an "AS IS" BASIS,
	// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	// See the License for the specific language governing permissions and
	// limitations under the License.


	// ------ constants ------

	final public static int PLANE              = P.CUSTOM;
	final public static int TUBE               = 1;
	final public static int SPHERE             = 2;
	final public static int TORUS              = 3;
	final public static int PARABOLOID         = 4;
	final public static int STEINBACHSCREW     = 5;
	final public static int SINE               = 6;
	final public static int FIGURE8TORUS       = 7;
	final public static int ELLIPTICTORUS      = 8;
	final public static int CORKSCREW          = 9;
	final public static int BOHEMIANDOME       = 10;
	final public static int BOW                = 11;
	final public static int MAEDERSOWL         = 12;
	final public static int ASTROIDALELLIPSOID = 13;
	final public static int TRIAXIALTRITORUS   = 14;
	final public static int LIMPETTORUS        = 15;
	final public static int HORN               = 16;
	final public static int SHELL              = 17;
	final public static int KIDNEY             = 18;
	final public static int LEMNISCAPE         = 19;
	final public static int TRIANGULOID        = 20;
	final public static int SUPERFORMULA       = 21;


	// ------ mesh parameters ------

	int form = PARABOLOID;

	float uMin = -P.PI;
	float uMax = P.PI;
	int uCount = 50;

	float vMin = -P.PI;
	float vMax = P.PI;
	int vCount = 50;

	float[] params = {
			1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1        };

	int drawMode = P.TRIANGLE_STRIP;
	float minHue = 0;
	float maxHue = 0;
	float minSaturation = 0;
	float maxSaturation = 0;
	float minBrightness = 50;
	float maxBrightness = 50;
	float meshAlpha = 100;

	float meshDistortion = 0;

	PVector[][] points;
	
	PApplet p;


	// ------ construktors ------

	public MeshShapes() {
		form = P.CUSTOM;
		update();
	}

	public MeshShapes(int theForm) {
		if (theForm >=0) {
			form = theForm;
		}
		update();
	}

	public MeshShapes(int theForm, int theUNum, int theVNum) {
		if (theForm >=0) {
			form = theForm;
		}
		uCount = P.max(theUNum, 1);
		vCount = P.max(theVNum, 1);
		update();
	}

	public MeshShapes(int theForm, float theUMin, float theUMax, float theVMin, float theVMax) {
		if (theForm >=0) {
			form = theForm;
		}
		uMin = theUMin;    
		uMax = theUMax;    
		vMin = theVMin;    
		vMax = theVMax;    
		update();
	}

	public MeshShapes(int theForm, int theUNum, int theVNum, float theUMin, float theUMax, float theVMin, float theVMax) {
		if (theForm >=0) {
			form = theForm;
		}
		uCount = P.max(theUNum, 1);
		vCount = P.max(theVNum, 1);
		uMin = theUMin;    
		uMax = theUMax;    
		vMin = theVMin;    
		vMax = theVMax;    
		update();
	}



	// ------ calculate points ------

	public void update() {
		if(p == null) p = P.p;
		points = new PVector[vCount+1][uCount+1];

		float u, v;
		for (int iv = 0; iv <= vCount; iv++) {
			for (int iu = 0; iu <= uCount; iu++) {
				u = P.map(iu, 0, uCount, uMin, uMax);
				v = P.map(iv, 0, vCount, vMin, vMax);

				switch(form) {
				case P.CUSTOM: 
					points[iv][iu] = calculatePoints(u, v);
					break;
				case TUBE: 
					points[iv][iu] = Tube(u, v);
					break;
				case SPHERE: 
					points[iv][iu] = Sphere(u, v);
					break;
				case TORUS: 
					points[iv][iu] = Torus(u, v);
					break;
				case PARABOLOID: 
					points[iv][iu] = Paraboloid(u, v);
					break;
				case STEINBACHSCREW: 
					points[iv][iu] = SteinbachScrew(u, v);
					break;
				case SINE: 
					points[iv][iu] = Sine(u, v);
					break;
				case FIGURE8TORUS: 
					points[iv][iu] = Figure8Torus(u, v);
					break;
				case ELLIPTICTORUS: 
					points[iv][iu] = EllipticTorus(u, v);
					break;
				case CORKSCREW: 
					points[iv][iu] = Corkscrew(u, v);
					break;
				case BOHEMIANDOME: 
					points[iv][iu] = BohemianDome(u, v);
					break;
				case BOW: 
					points[iv][iu] = Bow(u, v);
					break;
				case MAEDERSOWL: 
					points[iv][iu] = MaedersOwl(u, v);
					break;
				case ASTROIDALELLIPSOID: 
					points[iv][iu] = AstroidalEllipsoid(u, v);
					break;
				case TRIAXIALTRITORUS: 
					points[iv][iu] = TriaxialTritorus(u, v);
					break;
				case LIMPETTORUS: 
					points[iv][iu] = LimpetTorus(u, v);
					break;
				case HORN: 
					points[iv][iu] = Horn(u, v);
					break;
				case SHELL: 
					points[iv][iu] = Shell(u, v);
					break;
				case KIDNEY: 
					points[iv][iu] = Kidney(u, v);
					break;
				case LEMNISCAPE: 
					points[iv][iu] = Lemniscape(u, v);
					break;
				case TRIANGULOID: 
					points[iv][iu] = Trianguloid(u, v);
					break;
				case SUPERFORMULA: 
					points[iv][iu] = Superformula(u, v);
					break;

				default:
					points[iv][iu] = calculatePoints(u, v);
					break;          
				}
			}
		}
	}


	// ------ getters and setters ------

	public int getForm() {
		return form;
	}
	public void setForm(int theValue) {
		form = theValue;
	}

	public String getFormName() {
		switch(form) {
		case P.CUSTOM: 
			return "Custom";
		case TUBE: 
			return "Tube";
		case SPHERE: 
			return "Sphere";
		case TORUS: 
			return "Torus";
		case PARABOLOID: 
			return "Paraboloid";
		case STEINBACHSCREW: 
			return "Steinbach Screw";
		case SINE: 
			return "Sine";
		case FIGURE8TORUS: 
			return "Figure 8 Torus";
		case ELLIPTICTORUS: 
			return "Elliptic Torus";
		case CORKSCREW: 
			return "Corkscrew";
		case BOHEMIANDOME: 
			return "Bohemian Dome";
		case BOW: 
			return "Bow";
		case MAEDERSOWL: 
			return "Maeders Owl";
		case ASTROIDALELLIPSOID: 
			return "Astoidal Ellipsoid";
		case TRIAXIALTRITORUS: 
			return "Triaxial Tritorus";
		case LIMPETTORUS: 
			return "Limpet Torus";
		case HORN: 
			return "Horn";
		case SHELL: 
			return "Shell";
		case KIDNEY: 
			return "Kidney";
		case LEMNISCAPE: 
			return "Lemniscape";
		case TRIANGULOID: 
			return "Trianguloid";
		case SUPERFORMULA: 
			return "Superformula";
		}
		return "";
	}

	public float getUMin() {
		return uMin;
	}
	public void setUMin(float theValue) {
		uMin = theValue;
	}

	public float getUMax() {
		return uMax;
	}
	public void setUMax(float theValue) {
		uMax = theValue;
	}

	public int getUCount() {
		return uCount;
	}
	public void setUCount(int theValue) {
		uCount = theValue;
	}

	public float getVMin() {
		return vMin;
	}
	public void setVMin(float theValue) {
		vMin = theValue;
	}

	public float getVMax() {
		return vMax;
	}
	public void setVMax(float theValue) {
		vMax = theValue;
	}

	public int getVCount() {
		return vCount;
	}
	public void setVCount(int theValue) {
		vCount = theValue;
	}

	public float[] getParams() {
		return params;
	}
	public void setParams(float[] theValues) {
		params = theValues;
	}

	public float getParam(int theIndex) {
		return params[theIndex];
	}
	public void setParam(int theIndex, float theValue) {
		params[theIndex] = theValue;
	}

	public int getDrawMode() {
		return drawMode;
	}
	public void setDrawMode(int theMode) {
		drawMode = theMode;
	}

	public float getMeshDistortion() {
		return meshDistortion;
	}
	public void setMeshDistortion(float theValue) {
		meshDistortion = theValue;
	}

	public void setColorRange(float theMinHue, float theMaxHue, float theMinSaturation, float theMaxSaturation, float theMinBrightness, float theMaxBrightness, float theMeshAlpha) {
		minHue = theMinHue;
		maxHue = theMaxHue;
		minSaturation = theMinSaturation;
		maxSaturation = theMaxSaturation;
		minBrightness = theMinBrightness;
		maxBrightness = theMaxBrightness;
		meshAlpha = theMeshAlpha;
	}

	public float getMinHue() {
		return minHue;
	}
	public void setMinHue(float minHue) {
		this.minHue = minHue;
	}

	public float getMaxHue() {
		return maxHue;
	}
	public void setMaxHue(float maxHue) {
		this.maxHue = maxHue;
	}

	public float getMinSaturation() {
		return minSaturation;
	}
	public void setMinSaturation(float minSaturation) {
		this.minSaturation = minSaturation;
	}

	public float getMaxSaturation() {
		return maxSaturation;
	}
	public void setMaxSaturation(float maxSaturation) {
		this.maxSaturation = maxSaturation;
	}

	public float getMinBrightness() {
		return minBrightness;
	}
	public void setMinBrightness(float minBrightness) {
		this.minBrightness = minBrightness;
	}

	public float getMaxBrightness() {
		return maxBrightness;
	}
	public void setMaxBrightness(float maxBrightness) {
		this.maxBrightness = maxBrightness;
	}

	public float getMeshAlpha() {
		return meshAlpha;
	}
	public void setMeshAlpha(float meshAlpha) {
		this.meshAlpha = meshAlpha;
	}


	// ------ functions for calculating the mesh points ------

	public PVector calculatePoints(float u, float v) {
		float x = u;
		float y = v;
		float z = 0;

		return new PVector(x, y, z);
	}

	public PVector defaultForm(float u, float v) {
		float x = u;
		float y = v;
		float z = 0;

		return new PVector(x, y, z);
	}

	public PVector Tube(float u, float v) {
		float x = (P.sin(u));
		float y = params[0] * v;
		float z = (P.cos(u));

		return new PVector(x, y, z);
	}

	public PVector Sphere(float u, float v) {
		v /= 2;
		v += P.HALF_PI;
		float x = 2 * (P.sin(v) * P.sin(u));
		float y = 2 * (params[0] * P.cos(v));
		float z = 2 * (P.sin(v) * P.cos(u));

		return new PVector(x, y, z);
	}

	public PVector Torus(float u, float v) {
		float x = 1 * ((params[1] + 1 + params[0] * P.cos(v)) * P.sin(u));
		float y = 1 * (params[0] * P.sin(v));
		float z = 1 * ((params[1] + 1 + params[0] * P.cos(v)) * P.cos(u));

		return new PVector(x, y, z);
	}

	public PVector Paraboloid(float u, float v) {
		float pd = params[0]; 
		if (pd == 0) {
			pd = 0.0001f; 
		}
		float x = power((v/pd),0.5f) * P.sin(u);
		float y = v;
		float z = power((v/pd),0.5f) * P.cos(u);

		return new PVector(x, y, z);
	}


	public PVector SteinbachScrew(float u, float v) {
		float x = u * P.cos(v);
		float y = u * P.sin(params[0] * v);
		float z = v * P.cos(u);

		return new PVector(x, y, z);
	}

	public PVector Sine(float u, float v) {
		float x = 2 * P.sin(u);
		float y = 2 * P.sin(params[0] * v);
		float z = 2 * P.sin(u+v);

		return new PVector(x, y, z);
	}


	public PVector Figure8Torus(float u, float v) {
		float x = 1.5f * P.cos(u) * (params[0] + P.sin(v) * P.cos(u) - P.sin(2*v) * P.sin(u) / 2f);
		float y = 1.5f * P.sin(u) * (params[0] + P.sin(v) * P.cos(u) - P.sin(2*v) * P.sin(u) / 2f) ;
		float z = 1.5f * P.sin(u) * P.sin(v) + P.cos(u) * P.sin(2*v) / 2;

		return new PVector(x, y, z);
	}

	public PVector EllipticTorus(float u, float v) {
		float x = 1.5f * (params[0] + P.cos(v)) * P.cos(u);
		float y = 1.5f * (params[0] + P.cos(v)) * P.sin(u) ;
		float z = 1.5f * P.sin(v) + P.cos(v);

		return new PVector(x, y, z);
	}

	public PVector Corkscrew(float u, float v) {
		float x = P.cos(u) * P.cos(v);
		float y = P.sin(u) * P.cos(v);
		float z = P.sin(v) + params[0] * u;

		return new PVector(x, y, z);
	}

	public PVector BohemianDome(float u, float v) {
		float x = 2 * P.cos(u);
		float y = 2 * P.sin(u) + params[0] * P.cos(v);
		float z = 2 * P.sin(v);

		return new PVector(x, y, z);
	}

	public PVector Bow(float u, float v) {
		u /= P.TWO_PI;
		v /= P.TWO_PI;
		float x = (2 + params[0] * P.sin(P.TWO_PI * u)) * P.sin(2 * P.TWO_PI * v);
		float y = (2 + params[0] * P.sin(P.TWO_PI * u)) * P.cos(2 * P.TWO_PI * v);
		float z = params[0] * P.cos(P.TWO_PI * u) + 3 * P.cos(P.TWO_PI * v);

		return new PVector(x, y, z);
	}

	public PVector MaedersOwl(float u, float v) {
		float x = 0.4f * (v * P.cos(u) - 0.5f*params[0] * power(v,2) * P.cos(2 * u));
		float y = 0.4f * (-v * P.sin(u) - 0.5f*params[0] * power(v,2) * P.sin(2 * u));
		float z = 0.4f * (4 * power(v,1.5f) * P.cos(3 * u / 2) / 3);

		return new PVector(x, y, z);
	}

	public PVector AstroidalEllipsoid(float u, float v) {
		u /= 2;
		float x = 3 * power(P.cos(u)*P.cos(v),3*params[0]);
		float y = 3 * power(P.sin(u)*P.cos(v),3*params[0]);
		float z = 3 * power(P.sin(v),3*params[0]);

		return new PVector(x, y, z);
	}

	public PVector TriaxialTritorus(float u, float v) {
		float x = 1.5f * P.sin(u) * (1 + P.cos(v));
		float y = 1.5f * P.sin(u + P.TWO_PI / 3 * params[0]) * (1 + P.cos(v + P.TWO_PI / 3 * params[0]));
		float z = 1.5f * P.sin(u + 2*P.TWO_PI / 3 * params[0]) * (1 + P.cos(v + 2*P.TWO_PI / 3 * params[0]));

		return new PVector(x, y, z);
	}

	public PVector LimpetTorus(float u, float v) {
		float x = 1.5f * params[0] * P.cos(u) / (P.sqrt(2) + P.sin(v));
		float y = 1.5f * params[0] * P.sin(u) / (P.sqrt(2) + P.sin(v));
		float z = 1.5f * 1 / (P.sqrt(2) + P.cos(v));

		return new PVector(x, y, z);
	}

	public PVector Horn(float u, float v) {
		u /= P.PI;
		//v /= PI;
		float x = (2*params[0] + u * P.cos(v)) * P.sin(P.TWO_PI * u);
		float y = (2*params[0] + u * P.cos(v)) * P.cos(P.TWO_PI * u) + 2 * u;
		float z = u * P.sin(v);

		return new PVector(x, y, z);
	}

	public PVector Shell(float u, float v) {
		float x = params[1] * (1 - (u / P.TWO_PI)) * P.cos(params[0]*u) * (1 + P.cos(v)) + params[3] * P.cos(params[0]*u);
		float y = params[1] * (1 - (u / P.TWO_PI)) * P.sin(params[0]*u) * (1 + P.cos(v)) + params[3] * P.sin(params[0]*u);
		float z = params[2] * (u / P.TWO_PI) + params[0] * (1 - (u / P.TWO_PI)) * P.sin(v);

		return new PVector(x, y, z);
	}

	public PVector Kidney(float u, float v) {
		u /= 2;
		float x = P.cos(u) * (params[0]*3*P.cos(v) - P.cos(3*v));
		float y = P.sin(u) * (params[0]*3*P.cos(v) - P.cos(3*v));
		float z = 3 * P.sin(v) - P.sin(3*v);

		return new PVector(x, y, z);
	}

	public PVector Lemniscape(float u, float v) {
		u /= 2;
		float cosvSqrtAbsSin2u = P.cos(v)*P.sqrt(P.abs(P.sin(2*params[0]*u)));
		float x = cosvSqrtAbsSin2u*P.cos(u);
		float y = cosvSqrtAbsSin2u*P.sin(u);
		float z = 3 * (power(x,2) - power(y,2) + 2 * x * y * power(P.tan(v),2));
		x *= 3;
		y *= 3;
		return new PVector(x, y, z);
	}

	public PVector Trianguloid(float u, float v) {
		float x = 0.75f * (P.sin(3*u) * 2 / (2 + P.cos(v)));
		float y = 0.75f * ((P.sin(u) + 2 * params[0] * P.sin(2*u)) * 2 / (2 + P.cos(v + P.TWO_PI)));
		float z = 0.75f * ((P.cos(u) - 2 * params[0] * P.cos(2*u)) * (2 + P.cos(v)) * ((2 + P.cos(v + P.TWO_PI/3))*0.25f));

		return new PVector(x, y, z);
	}

	public PVector Superformula(float u, float v) {
		v /= 2;

		// Superformel 1
		float a = params[0];
		float b = params[1];
		float m = (params[2]);
		float n1 = (params[3]);
		float n2 = (params[4]);
		float n3 = (params[5]);
		float r1 = P.pow(P.pow(P.abs(P.cos(m*u/4)/a), n2) + P.pow(P.abs(P.sin(m*u/4)/b), n3), -1/n1);

		// Superformel 2
		a = params[6];
		b = params[7];
		m = (params[8]);
		n1 = (params[9]);
		n2 = (params[10]);
		n3 = (params[11]);
		float r2 = P.pow(P.pow(P.abs(P.cos(m*v/4)/a), n2) + P.pow(P.abs(P.sin(m*v/4)/b), n3), -1/n1);

		float x = 2 * (r1*P.sin(u) * r2*P.cos(v));
		float y = 2 * (r2*P.sin(v));
		float z = 2 * (r1*P.cos(u) * r2*P.cos(v));

		return new PVector(x, y, z);
	}




	// ------ definition of some mathematical functions ------

	// the processing-function pow works a bit differently for negative bases
	float power(float b, float e) {
		if (b >= 0 || (int)e == e) {
			return P.pow(b, e);
		} 
		else {
			return -P.pow(-b, e);
		}
	}

	float logE(float v) {
		if (v >= 0) {
			return P.log(v);
		} 
		else{
			return -P.log(-v);
		}
	}

	float sinh(float a) {
		return (P.sin(P.HALF_PI/2f-a));
	}

	float cosh(float a) {
		return (P.cos(P.HALF_PI/2-a));
	}

	float tanh(float a) {
		return (P.tan(P.HALF_PI/2-a));
	}



	// ------ draw mesh ------

	public void draw(PGraphics pg) {
		int iuMax, ivMax;

		if (drawMode == P.QUADS || drawMode == P.TRIANGLES) {
			iuMax = uCount-1;
			ivMax = vCount-1;
		}
		else{
			iuMax = uCount;
			ivMax = vCount-1;
		}

		// store previously set colorMode
		pg.pushStyle();
		pg.colorMode(P.HSB, 360, 100, 100, 100);

		float minH = minHue;
		float maxH = maxHue;
		if (P.abs(maxH-minH) < 20) maxH = minH;
		float minS = minSaturation;
		float maxS = maxSaturation;
		if (P.abs(maxS-minS) < 10) maxS = minS;
		float minB = minBrightness;
		float maxB = maxBrightness;
		if (P.abs(maxB-minB) < 10) maxB = minB;


		for (int iv = 0; iv <= ivMax; iv++) {
			if (drawMode == P.TRIANGLES) {

				for (int iu = 0; iu <= iuMax; iu++) {
					pg.fill(p.random(minH, maxH), p.random(minS, maxS), p.random(minB, maxB), meshAlpha);
					pg.beginShape(drawMode);
					float r1 = meshDistortion * p.random(-1, 1);
					float r2 = meshDistortion * p.random(-1, 1);
					float r3 = meshDistortion * p.random(-1, 1);
					pg.vertex(points[iv][iu].x+r1, points[iv][iu].y+r2, points[iv][iu].z+r3);
					pg.vertex(points[iv+1][iu+1].x+r1, points[iv+1][iu+1].y+r2, points[iv+1][iu+1].z+r3);
					pg.vertex(points[iv+1][iu].x+r1, points[iv+1][iu].y+r2, points[iv+1][iu].z+r3);
					pg.endShape();

					pg.fill(p.random(minH, maxH), p.random(minS, maxS), p.random(minB, maxB), meshAlpha);
					pg.beginShape(drawMode);
					r1 = meshDistortion * p.random(-1, 1);
					r2 = meshDistortion * p.random(-1, 1);
					r3 = meshDistortion * p.random(-1, 1);
					pg.vertex(points[iv+1][iu+1].x+r1, points[iv+1][iu+1].y+r2, points[iv+1][iu+1].z+r3);
					pg.vertex(points[iv][iu].x+r1, points[iv][iu].y+r2, points[iv][iu].z+r3);
					pg.vertex(points[iv][iu+1].x+r1, points[iv][iu+1].y+r2, points[iv][iu+1].z+r3);
					pg.endShape();
				}       

			}
			else if (drawMode == P.QUADS) {
				for (int iu = 0; iu <= iuMax; iu++) {
					pg.fill(p.random(minH, maxH), p.random(minS, maxS), p.random(minB, maxB), meshAlpha);
					pg.beginShape(drawMode);

					float r1 = meshDistortion * p.random(-1, 1);
					float r2 = meshDistortion * p.random(-1, 1);
					float r3 = meshDistortion * p.random(-1, 1);
					pg.vertex(points[iv][iu].x+r1, points[iv][iu].y+r2, points[iv][iu].z+r3);
					pg.vertex(points[iv+1][iu].x+r1, points[iv+1][iu].y+r2, points[iv+1][iu].z+r3);
					pg.vertex(points[iv+1][iu+1].x+r1, points[iv+1][iu+1].y+r2, points[iv+1][iu+1].z+r3);
					pg.vertex(points[iv][iu+1].x+r1, points[iv][iu+1].y+r2, points[iv][iu+1].z+r3);

					pg.endShape();
				}        
			}
			else{
				// Draw Strips
				pg.fill(p.random(minH, maxH), p.random(minS, maxS), p.random(minB, maxB), meshAlpha);
				pg.beginShape(drawMode);

				for (int iu = 0; iu <= iuMax; iu++) {
					float r1 = meshDistortion * p.random(-1, 1);
					float r2 = meshDistortion * p.random(-1, 1);
					float r3 = meshDistortion * p.random(-1, 1);
					pg.vertex(points[iv][iu].x+r1, points[iv][iu].y+r2, points[iv][iu].z+r3);
					pg.vertex(points[iv+1][iu].x+r1, points[iv+1][iu].y+r2, points[iv+1][iu].z+r3);
				}  

				pg.endShape();
			}
		}

		pg.popStyle();
	}

}