package plots;

import java.awt.Polygon;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;

/** Tells about the transform from real to int coordinates,
	and also holds the size of the image we are drawing onto. */

public class Matrix2d {
	// the only data:
	public float m[] = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
	public int w, h;
	
	public Matrix2d(int w, int h) {
		this.w = w;
		this.h = h;
		setCorners(0, 0, w, h);
	}

	public void setDimensions(int w, int h) {
		this.w = w; this.h = h;
		// set corners in some way from old corners?
	}

	public void setCorners(float llx, float lly, float urx, float ury) {
		/* 
		   m[0]*llx + m[1]*lly + m[4] =  0
		   m[2]*llx + m[3]*lly + m[5] = h
		   m[0]*llx + m[1]*ury + m[4] =  0
		   m[2]*llx + m[3]*ury + m[5] = 0
		   
		   m[2] = m[3] = 0
		   m[0]*llx + m[4] = 0
		   m[3]*lly + m[5] = h
		   m[0]*urx + m[4] = w
		   m[3]*ury + m[5] = 0
		   
		   m[0]*urx + m[1]*lly + m[4] =  w
		   m[2]*urx + m[3]*lly + m[5] = h
		   m[0]*urx + m[1]*ury + m[4] =  w
		   m[2]*urx + m[3]*ury + m[5] = 0
		*/
		
		m[1] = m[2] = 0.0f;
		m[0] = w/(urx-llx);
		m[3] = -h/(ury-lly);
		m[4] = -m[0]*llx;
		m[5] = -m[3]*ury;
	}
	
	// llx, lly, urx, ury
	public float[] getCorners() {
		float p[] = itransform(0, h);
		float q[] = itransform(w, 0);
		float r[] = new float[4];
		r[0] = p[0]; r[1] = p[1]; r[2] = q[0]; r[3] = q[1];
		return(r);
	}
	
	public void setHeight(float H0, float H1) {
		float a = (float) w/(float) h;
		float W = 0.5f*a*(H1-H0);
		setCorners(-W, H0, W, H1);
	}

	public float[] pixel() {
		double[] p = doubleitransform(0, 0);
		double[] q = doubleitransform(1, 1);
		float[] r = { (float) Math.abs(q[0] - p[0]), (float) Math.abs(q[1] - p[1]) };
		return(r);
	}
	
	// - coordinate changes -----------------------------
	
	/* Rotations, translations, scales; four versions of each.
		First acts on the given matrix; the second and third are static, acting
		on a matrix given as argument; one changes that one, the other 
		returns the modification. */
	
	/* A matrix is as in PS
	
		m0 m1 m4
		m2 m3 m5
		 0  0  1
		 
	*/

	/* m0 m1 0 | c -s  0
	   m2 m3 0 | s  c  0
	    0  0 1 | 0  0  1 */
	
	public void rotate(float a) {
		double c = Math.cos(a);
		double s = Math.sin(a);
		float x = (float) (m[0]*c + m[1]*s);
		float y = (float) (-m[0]*s + m[1]*c);
		m[0] = x; m[1] = y;
		x = (float) (m[2]*c + m[3]*s);
		y = (float) (-m[2]*s + m[3]*c);
		m[2] = x; m[3] = y;
	}
	
	/* m0 m1 0 | 1  0  x
	   m2 m3 0 | 0  1  y
	    0  0 1 | 0  0  1 */
	    
	public void translate(float x, float y) {
		m[4] = m[4] + (float) (m[0]*x + m[1]*y);
		m[5] = m[5] + (float) (m[2]*x + m[3]*y);
	}
	
	// shifts in pixels:
	public void itranslate(int x, int y) {
		m[4] += x;
		m[5] += y;
	}
	
	/* m0 m1 0 | a  0  0
	   m2 m3 0 | 0  b  0
	    0  0 1 | 0  0  1 */

	public void scale(float a, float b) {
		m[0] *= a;
		m[1] *= b;
		m[2] *= a;
		m[3] *= b;
	}
	
	public static void rotate(float m[], float a) {
		double c = Math.cos(a);
		double s = Math.sin(a);
		float x = (float) (m[0]*c + m[1]*s);
		float y = (float) (-m[0]*s + m[1]*c);
		m[0] = x; m[1] = y;
		x = (float) (m[2]*c + m[3]*s);
		y = (float) (-m[2]*s + m[3]*c);
		m[2] = x; m[3] = y;
	}
	
	public static void translate(float m[], float x, float y) {
		m[4] = m[4] + (float) (m[0]*x + m[1]*y);
		m[5] = m[5] + (float) (m[2]*x + m[3]*y);
	}
	
	public static void itranslate(float m[], int x, int y) {
		m[4] += x;
		m[5] += y;
	}

	// z = magnification, t = new origin
	public void zoom(float z, float c[]) {
		scale(z, z);
		double x = m[0]*c[0] + m[2]*c[1];
		double y = m[1]*c[0] + m[3]*c[1];
		// x + t[0] = w/2, y + t[1] = h/2 ;
		m[4] = (float) (w/2 - x);
		m[5] = (float) (h/2 - y);
	}

	public static void scale(float m[], float a, float b) {
		m[0] *= a;
		m[1] *= b;
		m[2] *= a;
		m[3] *= b;
	}
	
	public static float[] rotated(float m[], float a) {
		float M[] = new float[6];
		double c = Math.cos(a);
		double s = Math.sin(a);
		M[0] = (float) (m[0]*c + m[1]*s);
		M[1] = (float) (-m[0]*s + m[1]*c);
		M[2] = (float) (m[2]*c + m[3]*s);
		M[3] = (float) (-m[2]*s + m[3]*c);
		M[4] = m[4]; M[5] = m[5];
		return(M);
	}
	
	public static float[] translated(float m[], float x, float y) {
		float M[] = new float[6];
		M[0] = m[0];
		M[1] = m[1];
		M[2] = m[2];
		M[3] = m[3];
		M[4] = m[4] + (float) (m[0]*x + m[1]*y);
		M[5] = m[5] + (float) (m[2]*x + m[3]*y);
		return(M);
	}
	
	public static float[] itranslated(float m[], int x, int y) {
		float M[] = new float[6];
		M[0] = m[0];
		M[1] = m[1];
		M[2] = m[2];
		M[3] = m[3];
		M[4] = m[4] + x;
		M[5] = m[5] + y;
		return(M);
	}
	
	public static float[] scaled(float m[], float a, float b) {
		float M[] = new float[6];
		M[0] = m[0]*a;
		M[1] = m[1]*b;
		M[2] = m[2]*a;
		M[3] = m[3]*b;
		M[4] = m[4];
		M[5] = m[5];
		return(M);
	}
	
	public float[] rotated(float a) {
		float M[] = new float[6];
		double c = Math.cos(a);
		double s = Math.sin(a);
		M[0] = (float) (m[0]*c + m[1]*s);
		M[1] = (float) (-m[0]*s + m[1]*c);
		M[2] = (float) (m[2]*c + m[3]*s);
		M[3] = (float) (-m[2]*s + m[3]*c);
		M[4] = m[4]; M[5] = m[5];
		return(M);
	}
	
	public float[] translated(float x, float y) {
		float M[] = new float[6];
		M[0] = m[0];
		M[1] = m[1];
		M[2] = m[2];
		M[3] = m[3];
		M[4] = m[4] + (float) (m[0]*x + m[1]*y);
		M[5] = m[5] + (float) (m[2]*x + m[3]*y);
		return(M);
	}
	
	public float[] itranslated(int x, int y) {
		float M[] = new float[6];
		M[0] = m[0];
		M[1] = m[1];
		M[2] = m[2];
		M[3] = m[3];
		M[4] = m[4] + x;
		M[5] = m[5] + y;
		return(M);
	}
	
	public float[] scaled(float a, float b) {
		float M[] = new float[6];
		M[0] = m[0]*a;
		M[1] = m[1]*b;
		M[2] = m[2]*a;
		M[3] = m[3]*b;
		M[4] = m[4];
		M[5] = m[5];
		return(M);
	}
	
	// - transforms ------------------------------------------
	
	final static int INFTY = 16183;
	/* Probably only necessary in graphing singular functions.
	   But we don't really have a good way of dealing with them. */
	public static int overflow(float z) {
		if (-INFTY < z && z < INFTY) {
			return((int) Math.round(z));
		} else if (z <= -INFTY) {
			// now x0 <= -INFTY
			return(-INFTY);
		} else {
			return(INFTY);
		}
	}
	
	public static int[] transform(float m[], float x, float y) {
		int q[] = new int[2];
		q[0] = (int) (m[0]*x + m[1]*y + m[4]);
		q[1] = (int) (m[2]*x + m[3]*y + m[5]);
		return(q);
	}

	public int[] transform(float x, float y) {
		int q[] = new int[2];
		q[0] = (int) (m[0]*x + m[1]*y + m[4]);
		q[1] = (int) (m[2]*x + m[3]*y + m[5]);
		return(q);
	}

	public int[][] transform(float[][] x) {
		int[][] q = new int[x.length][2];
		for (int i=0;i<x.length;i++) {
			float X = x[i][0], Y = x[i][1];
			q[i][0] = (int) (m[0]*X + m[1]*Y + m[4]);
			q[i][1] = (int) (m[2]*X + m[3]*Y + m[5]);
		}
		return(q);
	}

	public void transform(int[] q, float x, float y) {
		q[0] = (int) (m[0]*x + m[1]*y + m[4]);
		q[1] = (int) (m[2]*x + m[3]*y + m[5]);
	}
	
	public int[] transform(float p[]) {
		int q[] = new int[2];
		q[0] = (int) (m[0]*p[0] + m[1]*p[1] + m[4]);
		q[1] = (int) (m[2]*p[0] + m[3]*p[1] + m[5]);
		return(q);
	}
	
	public static float[] ftransform(float m[], float x, float y) {
		float q[] = new float[2];
		q[0] = (m[0]*x + m[1]*y + m[4]);
		q[1] = (m[2]*x + m[3]*y + m[5]);
		return(q);
	}
	
	public float[] ftransform(float x, float y) {
		float q[] = new float[2];
		q[0] = (m[0]*x + m[1]*y + m[4]);
		q[1] = (m[2]*x + m[3]*y + m[5]);
		return(q);
	}
	
	public float[] ftransform(float p[]) {
		float q[] = new float[2];
		q[0] = (m[0]*p[0] + m[1]*p[1] + m[4]);
		q[1] = (m[2]*p[0] + m[3]*p[1] + m[5]);
		return(q);
	}
	
	public static int[] xpoints(float m[], float p[][]) {
		int x[] = new int[p.length];
		for (int i=0;i<p.length;i++) {
			x[i] = (int) (m[0]*p[i][0] + m[1]*p[i][1] + m[4]);
		}
		return(x);
	}
	
	public static int[] ypoints(float m[], float p[][]) {
		int y[] = new int[p.length];
		for (int i=0;i<p.length;i++) {
			y[i] = (int) (m[2]*p[i][0] + m[3]*p[i][1] + m[5]);
		}
		return(y);
	}
	
	public int[] xpoints(float p[][]) {
		int x[] = new int[p.length];
		for (int i=0;i<p.length;i++) {
			x[i] = (int) (m[0]*p[i][0] + m[1]*p[i][1] + m[4]);
		}
		return(x);
	}
	
	public int[] ypoints(float p[][]) {
		int y[] = new int[p.length];
		for (int i=0;i<p.length;i++) {
			y[i] = (int) (m[2]*p[i][0] + m[3]*p[i][1] + m[5]);
		}
		return(y);
	}
	
	public int[] xpoints(double p[][]) {
		int x[] = new int[p.length];
		for (int i=0;i<p.length;i++) {
			x[i] = (int) (m[0]*p[i][0] + m[1]*p[i][1] + m[4]);
		}
		return(x);
	}
	
	public int[] ypoints(double p[][]) {
		int y[] = new int[p.length];
		for (int i=0;i<p.length;i++) {
			y[i] = (int) (m[2]*p[i][0] + m[3]*p[i][1] + m[5]);
		}
		return(y);
	}

	public Polygon polygon(float p[][]) {
		return(new Polygon(xpoints(p), ypoints(p), p.length));
	}
	
	public Polygon polygon(double p[][]) {
		return(new Polygon(xpoints(p), ypoints(p), p.length));
	}
	
	public Polygon rectangle(float llx, float lly, float urx, float ury) {
		float r[][] = new float[5][2];
		r[0][0] = llx; r[0][1] = lly;
		r[1][0] = urx; r[1][1] = lly;
		r[2][0] = urx; r[2][1] = ury;
		r[3][0] = llx; r[3][1] = ury;
		r[4][0] = llx; r[4][1] = lly;
		return(polygon(r));
	}

	public static Polygon polygon(float m[], float p[][]) {
		return(new Polygon(xpoints(m, p), ypoints(m, p), p.length));
	}
	
	public float[] itransform(int x, int y) {
		y += 1;
		float d = m[0]*m[3] - m[1]*m[2];
		float x0 = (float) x - m[4];
		float y0 = (float) y - m[5];
		float x1 =  m[3]*x0 - m[1]*y0;
		x1 /= d;
		 x0 = (float) x - m[4];
		 y0 = (float) y - m[5] + 1;
		float y1 = -m[2]*x0 + m[0]*y0;
		y1 /= d;
		float q[] = new float[2];
		q[0] = x1; q[1] = y1;
		return(q);
	}

	public double[] doubleitransform(int x, int y) {
		y += 1;
		double d = m[0]*m[3] - m[1]*m[2];
		double x0 = x - m[4];
		double y0 = y - m[5];
		double x1 =  m[3]*x0 - m[1]*y0;
		x1 /= d;
		 x0 = (float) x - m[4];
		 y0 = (float) y - m[5] + 1;
		double y1 = -m[2]*x0 + m[0]*y0;
		y1 /= d;
		double q[] = new double[2];
		q[0] = x1; q[1] = y1;
		return(q);
	}

	// -------------------------------------------------

	public String toString() {
		String s = new String("");
		for (int i=0;i<6;i++) {
			s += (" " + m[i]);
		}
		return(s);
	}

}
