// quantumfractals.cs - Port of eeqt to Qt#
// Author: Adam Treat <manyoso@yahoo.com>
// (c) 2002 Adam Treat
// Licensed under the terms of the GNU GPL

namespace Qf {

	using Qt;
	using System;
	using System.Threading;
	
	public class FractalViewer : QMainWindow {
		
		//Menuing
		private QMenuBar menubar;
		private QPopupMenu filemenu;
		private QPopupMenu shapemenu;
		private QPopupMenu settingsmenu;
		
		public static int Main (string[] args)
		{
			//Initialize and start the main event loop
			QApplication app = new QApplication (args);
			FractalViewer view = new FractalViewer ();
			app.SetMainWidget (view);
			view.Show ();
			return app.Exec ();
		}
		
		public FractalViewer (): base (null, "main")
		{	
			SetCaption ("Quantum Fractals");
			
			//Setup the display
			Display display = new Display (this);
			SetCentralWidget (display);
			
			//Setup the filemenu
			filemenu = new QPopupMenu (null, "filemenu");
			filemenu.InsertItem ("&Screenshot", display, SLOT ("SlotScreenshot()"));
			filemenu.InsertSeparator ();
			filemenu.InsertItem ("&Quit", qApp, SLOT ("quit()"));
			
			//Setup the shapemenu
			shapemenu = new QPopupMenu (null, "typemenu");
			shapemenu.InsertItem( "&Tetrahedron", 0);
			shapemenu.InsertItem( "&Cube", 1);
			shapemenu.InsertItem( "&Octahedron", 2);
			shapemenu.InsertItem( "&Icosahedron", 3);
			shapemenu.InsertItem( "&Dodecahedron", 4);
			shapemenu.InsertItem( "&Double Tetrahedron", 5);
			shapemenu.InsertItem( "&Icosidodecahedron", 6);
			
			//Connect the shapemenu
			QObject.Connect (shapemenu, SIGNAL ("activated(int)"),
				display, SLOT("SlotShapeMenu(int)"));
			
			//Setup the settingsmenu
			settingsmenu = new QPopupMenu (null, "settingsmenu");
			settingsmenu.InsertItem ("&Alpha", display, SLOT ("SlotSetAlpha()"));
			
			//Setup the menubar
			menubar = new QMenuBar (this, "");
			menubar.InsertItem ("&File", filemenu);
			menubar.InsertItem ("&Shape", shapemenu);
			menubar.InsertItem ("&Settings", settingsmenu);
		}
	}
	
	public class Display: QWidget, IQuantumFractal {
		
		//Labels
		QLabel count;
		QLabel shape;
		QLabel alpha;
		
		//Buttons
		QPushButton start;
		QPushButton stop;
		QPushButton reset;
		QPushButton gray;
		QPushButton intense;
		
		//Drawable region
		QPaintBuffer buffer;
		
		//Layouts
		QVBoxLayout layout;
		QHBoxLayout buttons;
		QVBoxLayout labels;
		
		//Engine controller variables
   		int[] topDensity = new int[0];
   		int[] bottomDensity = new int[0];
		int resolution = 400;
		int scale = 1;
   		double centerX = 0;
   		double centerY = 0;
		int i = 0;
		bool Grayscale = true;
		bool Intense = false;
		bool Running = false;
		bool WasRunning = false;
		
		//The engine
		QuantumFractals qf;
		Thread engine;
		
		public Display (QWidget parent): base (parent)
		{
			//Setup the sizes
			QSize size = new QSize (resolution, resolution);
			parent.SetBaseSize (size);
			
			//Some nice colors
			SetPaletteBackgroundColor (new QColor ("Black"));
			SetPaletteForegroundColor (new QColor ("LightBlue"));
			
			//Setup the buttons
			start = new QPushButton ("Start", this);
			stop = new QPushButton ("Stop", this);
			reset = new QPushButton ("Reset", this);
			gray = new QPushButton ("Color", this);
			intense = new QPushButton ("Intensity", this);
			
			//Setup the labels
			count = new QLabel (this);
			alpha = new QLabel (this);
			shape = new QLabel (this);
			
			//Setup the drawable
			buffer = new QPaintBuffer (this);
			buffer.SetMinimumSize (size);
			
			//Create the layouts
			layout = new QVBoxLayout (this);
			buttons = new QHBoxLayout (layout);
			
			//Add some buttons
			buttons.AddWidget (start);
			buttons.AddWidget (stop);
			buttons.AddWidget (reset);
			buttons.AddWidget (gray);
			buttons.AddWidget (intense);
			
			//Connect the buttons and SlotQuit
			QObject.Connect (start, SIGNAL ("clicked()"),
				this, SLOT ("SlotStart()"));
			QObject.Connect (stop, SIGNAL ("clicked()"),
				this, SLOT ("SlotStop()"));
			QObject.Connect (reset, SIGNAL ("clicked()"),
				this, SLOT ("SlotReset()"));
			QObject.Connect (gray, SIGNAL ("clicked()"),
				this, SLOT ("SlotGray()"));
			QObject.Connect (intense, SIGNAL ("clicked()"),
				this, SLOT ("SlotIntense()"));
			QObject.Connect (buffer, SIGNAL ("Painted()"),
				this, SLOT ("SlotSetLabels()"));
			QObject.Connect (qApp, SIGNAL ("lastWindowClosed ()"),
				this, SLOT ("SlotQuit ()"));
			
			//Layout labels
			labels = new QVBoxLayout (layout);
			labels.AddWidget (count);
			labels.AddWidget (shape);
			labels.AddWidget (alpha);
			
			//Layout buffer
			layout.AddWidget (buffer, 1);
			
			//Finally create the data engine
			qf = new QuantumFractals (this);
			
			//Handle resize events
			resizeEvent += new ResizeEvent (TouchResize);
		}
		
		//This is where the controller receives data from the engine
		public void UpdateData (double[] d)
		{
			i++; //Keep track of the number of points
			
			//Set the density arrays to match the resolution
			if (resolution * resolution != topDensity.Length) {
				topDensity = new int[resolution * resolution];
				bottomDensity = new int[resolution * resolution];
			}
			
			//setup the sphere
			int res = resolution;
			int res2 = res / 2;
			int x = res / 2 + (int)(res2 * scale * (d[0] - centerX));
			int y = res / 2 + (int)(res2 * scale * (d[1] - centerY));
			double z = d[2];
			
			if ((x < res) && (x >= 0) && (y >= 0) && (y < res)) {
			
				if (z >= 0)
					topDensity[y * resolution + x]++;
				else
					bottomDensity[y * resolution + x]++;
					
			}
			
			//Convert the density into a color
			int top = topDensity[y * resolution + x];
			//int bot = bottomDensity[y * resolution + x];
			top = Math.Min (top, 255);
			//bot = Math.Min (bot, 255);
			
			//Log color system not working well :(
			if (Intense) {
				top = (int)(Math.Log (top + 1));
				//bot = (int)(Math.Log (bot + 1));
			}
			
			int topdepth = RGB (top,top,top);
			//int botdepth = RGB (bot,bot,bot);
			
			//Finally draw the pixel
			SetPixel (x, y, topdepth);
			//SetPixel (x, y, botdepth);
		}
		
		//Calls the drawable
		public void SetPixel (int x, int y, int depth)
		{
			buffer.PaintPixel (x, y, depth);
		}
		
		//Convert the color into a depth
		public int RGB (int r, int g, int b)
		{
			if (!Grayscale) {
				
				r = Intensity (r < 128 ? 128 - r : 0);
				g = Intensity (128 - Math.Abs (g - 128));
				b = Intensity (b < 128 ? 0 : b - 128);
			
			} else {
			
				r = Intensity (r);
				g = Intensity (g);
				b = Intensity (b);
				
			}
	       		//Console.WriteLine ("{0} {1} {2}", r,g,b);
			return 256 * 256 * r + 256 * g + b;
		}
		
		//This provides more detail
		private int Intensity(int val)
		{
			int ret;
			
			double bases = 64;
			double scale = 256.0 / (256.0 - bases);
			ret = (int)(bases + ((double)val) / scale);
			
			//if gray then black, if color then white
			if (val == 0 && Grayscale)
				ret = 0;
			else if (val == 0)
				ret = 255;
			return ret;
		}
		
		//Draw the labels
		private void SlotSetLabels ()
		{
			count.SetText ("Count: " + i.ToString ());
			shape.SetText ("Shape: " + qf.GetPolytope ());
			alpha.SetText ("Alpha: " + qf.Alpha.ToString ());
		}
		
		//Start the engine
		private void SlotStart ()
		{
			engine = new Thread(new ThreadStart(qf.Start));
			engine.Start ();
			Running = true;
		}
		
		//Stop the engine
		private void SlotStop ()
		{
			if (engine != null)
				if (engine.IsAlive)
					engine.Abort ();
			Running = false;
		}
		
		//Reset everything
		private void SlotReset ()
		{
			SlotStop ();
			ResetBuffer ();
			SlotStart ();
		}
		
		//Reset the drawable
		private void ResetBuffer ()
		{
			i = 0;
			SlotSetLabels ();
			topDensity = new int[0];
   			bottomDensity = new int[0];
			buffer.Reset ();
		}
		
		//Toggles the color scheme
		private void SlotGray ()
		{
			Grayscale = !Grayscale;
		}
		
		//Toggles log color scheme
		//Not working so well :(
		private void SlotIntense ()
		{
			Intense = !Intense;
		}
		
		//Change the platonic shape
		private void SlotShapeMenu (int item)
		{
			WasRunning = Running ? true : false;
				
			SlotStop ();
			ResetBuffer ();
			
			switch(item) {
			
				case 0:
					qf.SetPolytope (0);
					break;
				case 1:
					qf.SetPolytope (1);
					break;
				case 2:
					qf.SetPolytope (2);
					break;
				case 3:
					qf.SetPolytope (3);
					break;
				case 4:
					qf.SetPolytope (4);
					break;
				case 5:
					qf.SetPolytope (5);
					break;
				case 6:
					qf.SetPolytope (6);
					break;
				Default:
					qf.SetPolytope (0);
					break;
			}
			
			if (WasRunning)
				SlotStart ();
		}
		
		//Save the drawable as a screenshot
		private void SlotScreenshot ()
		{
			WasRunning = Running ? true : false;
			
			SlotStop ();
			string filename = QFileDialog.GetSaveFileName (
				
				QDir.HomeDirPath (), "*", this, "save",
				"Save Screenshot", "*.png", true
			);

                        if (filename != null)
				buffer.Save (filename);
			
			if (WasRunning)
				SlotStart ();
		}
		
		//Set the alpha engine variable
		private void SlotSetAlpha ()
		{
			WasRunning = Running ? true : false;
			
			SlotStop ();
			qf.Alpha = QInputDialog.GetDouble (
            		
				"Set Alpha", "Alpha: ", qf.Alpha, 0, 2, 32
	    		);
			
			if (WasRunning) 
				SlotStart ();
			else
				SlotSetLabels ();
		}
		
		//Make sure to quit all threads upon exit
		private void SlotQuit ()
		{
			SlotStop ();
			buffer.Stop ();
		}
		
		//Need to reset the resolution upon resize
		private void TouchResize (QResizeEvent e)
		{
			int height = buffer.Size ().Height ();
			int width = buffer.Size ().Width ();
			
			resolution = height > width ? width : height;
		}
	}

	[DeclareQtSignal ("Painted()")]
	public class QPaintBuffer : QFrame {
		
		//Drawables
		private QPixmap buffer;
		private QImage image;
		
		//Timer
		private TimerCallback call;
		private Timer timer;

		public QPaintBuffer (QWidget parent) : base (parent)
		{
			SetBackgroundMode (Qt.BackgroundMode.NoBackground);
			
			//Create drawables
			buffer = new QPixmap ();
			image = new QImage (Size (), 32);
			
			//Setup the event handlers
			paintEvent += new PaintEvent (TouchPaint);
			resizeEvent += new ResizeEvent (TouchResize);
			focusInEvent += new FocusInEvent (TouchFocus);
			focusOutEvent += new FocusOutEvent (TouchFocus);
			
			//Start the timer
			call = new TimerCallback(PaintImage);
			timer = new Timer(call, null, 1000, 1000);
			
		}
		
		//Resets the drawables
		public void Reset ()
		{
			buffer = new QPixmap ();
			image = new QImage (Size (), 32);
			PaintImage (null);
		}
		
		//Paints a pixel to the image
		public void PaintPixel (int x, int y, int depth)
		{
			lock (this) {
				if (x < image.Width () && y < image.Height ())
					image.SetPixel (x, y, (uint)depth);
			}
		}
		
		//Saves the image to a file
		public void Save (string filename)
		{
			image.Save (filename, "PNG");
		}
		
		//Paints the image to the screen and emits Painted
		private void PaintImage (object state)
		{
			buffer.ConvertFromImage (image);
			PerformPaint ();
			Emit ("Painted()");
		}
		
		//The actual bitblt to the screen
		private void PerformPaint ()
		{
			BitBlt(this, 0, 0, buffer,
				0, 0, -1, -1, RasterOp.CopyROP, false);
		}
		
		//Receive focus events
		private void TouchFocus (QFocusEvent e)
		{
			PerformPaint ();
		}
			
		//Receive paint events
		private void TouchPaint (QPaintEvent e)
		{	
			PerformPaint (); 
		}
		
		//Receive resize events
		private void TouchResize (QResizeEvent e)
		{
			image = new QImage (e.Size (), 32);
			buffer.Resize (e.Size());
			buffer.Fill (new QColor("black"));
			BitBlt (buffer, 0, 0, new QPixmap (buffer),
				0, 0, -1, -1, RasterOp.CopyROP, false);
		}
		
		//Dispose of the timer
		public void Stop ()
		{
			timer.Dispose ();
		}
	}

	public interface IQuantumFractal {
	
		void UpdateData (Double [] data);
	}
	
	//Polytope types
	public enum Shapes {
		TETRAHEDRON = 0,
		CUBE = 1,
		OCTAHEDRON = 2,
		ICOSAHEDRON = 3,
		DODECAHEDRON = 4,
		DOUBLE_TETRAHEDRON = 5,
		ICOSIDODECAHEDRON = 6
	}
	
	public class QuantumFractals {
		
		private int t = 0;
		private double[] p; //Detector probabilities
		private double[] fp; //Fractal point
		private double[][] n; //Detector points
		private double[] counter; //Detect counter
		private double alpha = 0.61803398874989288039384209090709; //Initialize to 1/phi
		
		private Random random;
		private Shapes polytope;
		private IQuantumFractal consumer;
		
		public QuantumFractals (IQuantumFractal consumer)
   		{
			this.consumer = consumer;
			SetPolytope (0);
			Init ();
		}
		
		public double Alpha
		{
			get { return alpha; }
			set { alpha = value; }
		}

		private void Init ()
		{
			random = new Random ();
			
			//Default values
			t = 0;

			counter = new double[n.Length]; //Detect counter
			fp = new double[3]; //Fractal point
			p = new double[n.Length];

			//Initial state
			fp[0] = random.NextDouble () -0.5;
			fp[1] = random.NextDouble () -0.5;
			fp[2] = random.NextDouble () -0.5;
			
			double sum = Math.Sqrt (Product (fp, fp));
			
			fp[0] = fp[0] / sum;
			fp[1] = fp[1] / sum;
			fp[2] = fp[2] / sum;
		}
		
		//Main fractal generator loop
		public void Start ()
		{
			Init ();

			//double n1 = (1.0) / n.Length as double;
			double n1 = (1.0) / n.Length;
			
			double alpha12 = 2 * alpha / (n.Length * (1 + alpha * alpha));

			do {
				//Increase t
				t++;

				//Calculate detector click probabilities
				for (int i = 0; i < p.Length; i++)
					p[i] = n1 + alpha12 * Product (n[i], fp);
				
				//Get next random number
				double r = random.NextDouble ();

				//Check which detector that clicked
				double ptmp = 0;
				double[] detector = null;
				
				for (int i = 0; i < p.Length; i++) {
				
					ptmp += p[i];

					if (r <= ptmp) {
						//We found which detector clicked
						detector = n[i];
						counter[i]++;
						break;
					}
				}

				if (detector == null)
					detector = n[p.Length - 1];

				//Project
				double sc = Product (fp, detector);

				for (int j = 0; j < 3; j++)
					fp[j]= (1 - alpha * alpha) * fp[j] + 2 * alpha * (1 + alpha * sc) * detector[j];

				//Normalize
				double norm = Math.Sqrt (Product (fp, fp));
				
				for (int j=0; j<3; j++)
					fp[j] /= norm;

				consumer.UpdateData (fp);
					
			} while (true);
		}
		
		
		//Calculate the scalar product of two vectors
		private double Product (double[] v1, double[] v2)
		{
			double sc = 0;
      			for(int i=0; i < v1.Length; i++)
				sc += v1[i] * v2[i];
			return sc;
		}
		
		public string GetPolytope ()
		{
			string ret = String.Empty;
			switch (polytope) {
				case Shapes.TETRAHEDRON:
					ret = "Tetrahedron";
					break;
				case Shapes.CUBE:
					ret = "Cube";
					break;
				case Shapes.OCTAHEDRON:
					ret = "Octahedron";
					break;
				case Shapes.ICOSAHEDRON:
					ret = "Icosahedron";
					break;
				case Shapes.DODECAHEDRON:
					ret = "Dodecahedron";
					break;
				case Shapes.DOUBLE_TETRAHEDRON:
					ret = "Double Tetrahedron";
					break;
				case Shapes.ICOSIDODECAHEDRON:
					ret = "Icosidodecahedron";
					break;
				Default:
					ret = "Unknown";
					break;
			}
			return ret;
		}
		
		public void SetPolytope (int type)
		{		
			polytope = (Qf.Shapes)type;
			
			switch (type) {
			
				case 0: {
				
					n = new double[4][];
					n[0] =	new double[] {0,0,1.0};
					n[1] =	new double[] {0.9428090415820634,0,-0.3333333333333333};
					n[2] =	new double[] {-0.4714045207910317,0.816496580927726,-0.3333333333333333};
					n[3] =	new double[] {-0.4714045207910317, -0.816496580927726, -0.3333333333333333};

					break;
				}

				case 1: {
				
					n = new double[8][];
					n[0] =	new double[] {0, 0, 1.0};
					n[1] =	new double[] {0.9428090415820634, 0, 0.3333333333333333};
					n[2] =	new double[] {-0.4714045207910317, 0.816496580927726, 0.3333333333333333};
					n[3] =	new double[] {-0.4714045207910317, -0.816496580927726, 0.3333333333333333};
					n[4] =	new double[] {0.4714045207910317, 0.816496580927726, -0.3333333333333333};
					n[5] =	new double[] {0.4714045207910317, -0.816496580927726, -0.3333333333333333};
					n[6] =	new double[] {-0.9428090415820634, 0, -0.3333333333333333};
					n[7] =	new double[] {0, 0, -1.0};
					break;
				}

				case 2: {
				
					n = new double[6][];
					n[0] =	new double[] {0, 0, 1.0};
					n[1] =	new double[] {1.0, 0, 0};
					n[2] =	new double[] {0, 1.0, 0};
					n[3] =	new double[] {-1.0, 0, 0};
					n[4] =	new double[] {0, -1.0, 0};
					n[5] =	new double[] {0, 0, -1.0};

					break;
				}

				case 3: {
				
					n = new double[12][];
					n[0] =	new double[] {0, 0, 1.0};
					n[1] =	new double[] {0.8944271909999159, 0, 0.4472135954999579};
					n[2] =	new double[] {0.276393202250021, 0.85065080835204, 0.4472135954999579};
					n[3] =	new double[] {-0.723606797749979, 0.5257311121191336, 0.4472135954999579};
					n[4] =	new double[] {-0.723606797749979, -0.5257311121191336, 0.4472135954999579};
					n[5] =	new double[] {0.276393202250021, -0.85065080835204, 0.4472135954999579};
					n[6] =	new double[] {0.723606797749979, 0.5257311121191336, -0.4472135954999579};
					n[7] =	new double[] {0.723606797749979, -0.5257311121191336, -0.4472135954999579};
					n[8] =	new double[] {-0.276393202250021, 0.85065080835204, -0.4472135954999579};
					n[9] =	new double[] {-0.8944271909999159, 0, -0.4472135954999579};
					n[10] =	new double[] {-0.276393202250021, -0.85065080835204, -0.4472135954999579};
					n[11] =	new double[] {0, 0, -1.0};

					break;
				}

				case 4: {
				
					n = new double[20][];
					n[0] =	new double[] {0, 0, 1.0};
					n[1] =	new double[] {0.6666666666666666, 0, 0.7453559924999299};
					n[2] =	new double[] {-0.3333333333333333, 0.5773502691896257, 0.7453559924999299};
					n[3] =	new double[] {-0.3333333333333333, -0.5773502691896257, 0.7453559924999299};
					n[4] =	new double[] {0.7453559924999299, 0.5773502691896257, 0.3333333333333333};
					n[5] =	new double[] {0.7453559924999299, -0.5773502691896257, 0.3333333333333333};
					n[6] =	new double[] {-0.8726779962499649, 0.35682208977308993, 0.3333333333333333};
					n[7] =	new double[] {0.12732200375003502, 0.9341723589627157, 0.3333333333333333};
					n[8] =	new double[] {0.12732200375003502, -0.9341723589627157, 0.3333333333333333};
					n[9] =	new double[] {-0.8726779962499649, -0.35682208977308993, 0.3333333333333333};
					n[10] =	new double[] {0.8726779962499649, 0.35682208977308993, -0.3333333333333333};
					n[11] =	new double[] {0.8726779962499649, -0.35682208977308993, -0.3333333333333333};
					n[12] =	new double[] {-0.7453559924999299, 0.5773502691896257, -0.3333333333333333};
					n[13] =	new double[] {-0.12732200375003502, 0.9341723589627157, -0.3333333333333333};
					n[14] =	new double[] {-0.12732200375003502, -0.9341723589627157, -0.3333333333333333};
					n[15] =	new double[] {-0.7453559924999299, -0.5773502691896257, -0.3333333333333333};
					n[16] =	new double[] {0.3333333333333333, 0.5773502691896257, -0.7453559924999299};
					n[17] =	new double[] {0.3333333333333333, -0.5773502691896257, -0.7453559924999299};
					n[18] =	new double[] {-0.6666666666666666, 0, -0.7453559924999299};
					n[19] =	new double[] {0, 0, -1.0};
					break;
				}

				case 5: {
				
					n = new double[8][];
					n[0] =	new double[] {0,0,1.0};
					n[1] =	new double[] {0.9428090415820634,0,-0.3333333333333333};
					n[2] =	new double[] {-0.4714045207910317,0.816496580927726,-0.3333333333333333};
					n[3] =	new double[] {-0.4714045207910317, -0.816496580927726, -0.3333333333333333};
					n[4] =	new double[] {0,0,-1.0};
					n[5] =	new double[] {-0.9428090415820634,0,0.3333333333333333};
					n[6] =	new double[] {0.4714045207910317,-0.816496580927726,0.3333333333333333};
					n[7] =	new double[] {0.4714045207910317, 0.816496580927726, 0.3333333333333333};

					break;
				}

				case 6: {
				
					double u=0.5;
					double v=0.8090169943749475; // (1/2)*phi
					double w=0.3090169943749474; // (1/2)/phi

					n = new double[30][];
					n[0] =	new double[] {1,0,0};
					n[1] =	new double[] {-1,0,0};
					n[2] =	new double[] {0,1,0};
					n[3] =	new double[] {0,-1,0};
					n[4] =	new double[] {0,0,1};
					n[5] =	new double[] {0,0,-1};
					n[6] =	new double[] {u,v,w};
					n[7] =	new double[] {-u,v,w};
					n[8] =	new double[] {u,-v,w};
					n[9] =	new double[] {u,v,-w};
					n[10] =	new double[] {-u,-v,w};
					n[11] =	new double[] {u,-v,-w};
					n[12] =	new double[] {-u,v,-w};
					n[13] =	new double[] {-u,-v,-w};
					n[14] =	new double[] {v,w,u};
					n[15] =	new double[] {v,w,-u};
					n[16] =	new double[] {-v,w,u};
					n[17] =	new double[] {v,-w,u};
					n[18] =	new double[] {-v,w,-u};
					n[19] =	new double[] {-v,-w,u};
					n[20] =	new double[] {v,-w,-u};
					n[21] =	new double[] {-v,-w,-u};
					n[22] =	new double[] {w,u,v};
					n[23] =	new double[] {w,-u,v};
					n[24] =	new double[] {w,u,-v};
					n[25] =	new double[] {-w,u,v};
					n[26] =	new double[] {w,-u,-v};
					n[27] =	new double[] {-w,u,-v};
					n[28] =	new double[] {-w,-u,v};
					n[29] =	new double[] {-w,-u,-v};
					break;
				}

				Default:
					break;
			}
		}
	}
}
