using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using skyscraper5.Skyscraper.Plugins;
using Color = System.Drawing.Color;

namespace skyscraper8.UI.MonoGame.Screenhacks
{
	[PluginPriority(2)]
	[DisplayName("Substrate")]
	internal class _2_Substrate : Processing, IScreenhack
	{
		int dimx = 250;
		int dimy = 250;
		int num = 0;
		int maxnum = 100;

		// grid of cracks
		int[] cgrid;
		Crack[] cracks;

		// color parameters
		int maxpal = 512;
		int numpal = 0;
		private Color[] goodcolor;

		// sand painters
		SandPainter[] sands;

		private void Begin()
		{
			// erase crack grid
			for (int y = 0; y < dimy; y++)
			{
				for (int x = 0; x < dimx; x++)
				{
					cgrid[y * dimx + x] = 10001;
				}
			}

			// make random crack seeds
			for (int k = 0; k < 16; k++)
			{
				int i = (Random(dimx * dimy - 1));
				cgrid[i] = (Random(360));
			}

			// make just three cracks
			num = 0;
			for (int k = 0; k < 3; k++)
			{
				MakeCrack();
			}
			//Background(Color.FromArgb(255, 255, 255, 255));
		}

		private void MakeCrack()
		{
			if (num < maxnum)
			{
				// make a new crack instance
				cracks[num] = new Crack(this);
				num++;
			}
		}

		private void TakeColor()
		{
			uint[] srcArrays =
			{
				0x201F21, 0x262C2E, 0x352626, 0x372B27,
				0x302C2E, 0x392B2D, 0x323229, 0x3F3229,
				0x38322E, 0x2E333D, 0x333A3D, 0x473329,
				0x40392C, 0x40392E, 0x47402C, 0x47402E,
				0x4E402C, 0x4F402E, 0x4E4738, 0x584037,
				0x65472D, 0x6D5D3D, 0x745530, 0x755532,
				0x745D32, 0x746433, 0x7C6C36, 0x523152,
				0x444842, 0x4C5647, 0x655D45, 0x6D5D44,
				0x6C5D4E, 0x746C43, 0x7C6C42, 0x7C6C4B,
				0x6B734B, 0x73734B, 0x7B7B4A, 0x6B6C55,
				0x696D5E, 0x7B6C5D, 0x6B7353, 0x6A745D,
				0x727B52, 0x7B7B52, 0x57746E, 0x687466,
				0x9C542B, 0x9D5432, 0x9D5B35, 0x936B36,
				0xAA7330, 0xC45A27, 0xD95223, 0xD85A20,
				0xDB5A23, 0xE57037, 0x836C4B, 0x8C6B4B,
				0x82735C, 0x937352, 0x817B63, 0x817B6D,
				0x927B63, 0xD9893B, 0xE49832, 0xDFA133,
				0xE5A037, 0xF0AB3B, 0x8A8A59, 0xB29A58,
				0x89826B, 0x9A8262, 0x888B7C, 0x909A7A,
				0xA28262, 0xA18A69, 0xA99968, 0x99A160,
				0x99A168, 0xCA8148, 0xEB8D43, 0xC29160,
				0xC29168, 0xD1A977, 0xC9B97F, 0xF0E27B,
				0x9F928B, 0xC0B999, 0xE6B88F, 0xC8C187,
				0xE0C886, 0xF2CC85, 0xF5DA83, 0xECDE9D,
				0xF5D294, 0xF5DA94, 0xF4E784, 0xF4E18A,
				0xF4E193, 0xE7D8A7, 0xF1D4A5, 0xF1DCA5,
				0xF4DBAD, 0xF1DCAE, 0xF4DBB5, 0xF5DBBD,
				0xF4E2AD, 0xF5E9AD, 0xF4E3BE, 0xF5EABE,
				0xF7F0B6, 0xD9D1C1, 0xE0D0C0, 0xE7D8C0,
				0xF1DDC6, 0xE8E1C0, 0xF3EDC7, 0xF6ECCE,
				0xF8F2C7, 0xEFEFD0, 0
			};
			for (int i = 0; i < srcArrays.Length; i++)
			{
				srcArrays[i] |= 0xff000000;
				goodcolor[i] = Color.FromArgb((int)srcArrays[i]);
			}

			numpal = srcArrays.Length;
		}

		class Crack
		{
			private readonly _2_Substrate _parent;
			private float x, y, t;
			private SandPainter sp;

			public Crack(_2_Substrate parent)
			{
				_parent = parent;
				FindStart();
				sp = new SandPainter(_parent);
			}

			private void FindStart()
			{
				// pick random point
				int px = 0;
				int py = 0;

				// shift until crack is found
				bool found = false;
				int timeout = 0;
				while ((!found) || (timeout++ > 1000))
				{
					px = (_parent.Random(_parent.dimx));
					py = (_parent.Random(_parent.dimy));
					if (_parent.cgrid[py * _parent.dimx + px] < 10000)
					{
						found = true;
					}
				}

				if (found)
				{
					// start crack
					int a = _parent.cgrid[py * _parent.dimx + px];
					if (_parent.Random(100) < 50)
					{
						a -= 90 + (int)(_parent.Random(-2.0f, 2.1f));
					}
					else
					{
						a += 90 + (int)(_parent.Random(-2.0f, 2.1f));
					}

					StartCrack(px, py, a);
				}
				else
				{
					//println("timeout: "+timeout);
				}
			}

			private void StartCrack(int X, int Y, int T)
			{
				x = X;
				y = Y;
				t = T; //%360;
				x += 0.61f * (float)Math.Cos(t * Math.PI / 180.0);
				y += 0.61f * (float)Math.Sin(t * Math.PI / 180.0);
			}

			public void Move()
			{
				// continue cracking
				x += 0.42f * (float)Math.Cos(t * Math.PI / 180);
				y += 0.42f * (float)Math.Sin(t * Math.PI / 180);

				// bound check
				float z = 0.33f;
				int cx = (int)(x + _parent.Random(-z, z)); // add fuzz
				int cy = (int)(y + _parent.Random(-z, z));

				// draw sand painter
				RegionColor();

				// draw black crack
				_parent.Stroke(0.0f, 85.0f);
				_parent.Point(x + _parent.Random(-z, z), y + _parent.Random(-z, z));


				if ((cx >= 0) && (cx < _parent.dimx) && (cy >= 0) && (cy < _parent.dimy))
				{
					// safe to check
					if ((_parent.cgrid[cy * _parent.dimx + cx] > 10000) ||
					    (Math.Abs(_parent.cgrid[cy * _parent.dimx + cx] - t) < 5))
					{
						// continue cracking
						_parent.cgrid[cy * _parent.dimx + cx] = (int)(t);
					}
					else if (Math.Abs(_parent.cgrid[cy * _parent.dimx + cx] - t) > 2)
					{
						// crack encountered (not self), stop cracking
						FindStart();
						_parent.MakeCrack();
					}
				}
				else
				{
					// out of bounds, stop cracking
					FindStart();
					_parent.MakeCrack();
				}
			}

			private void RegionColor()
			{
				// start checking one step away
				float rx = x;
				float ry = y;
				bool openspace = true;

				// find extents of open space
				while (openspace)
				{
					// move perpendicular to crack
					rx += 0.81f * (float)Math.Sin(t * Math.PI / 180.0);
					ry -= 0.81f * (float)Math.Cos(t * Math.PI / 180.0);
					int cx = (int)(rx);
					int cy = (int)(ry);
					if ((cx >= 0) && (cx < _parent.dimx) && (cy >= 0) && (cy < _parent.dimy))
					{
						// safe to check
						if (_parent.cgrid[cy * _parent.dimx + cx] > 10000)
						{
							// space is open
						}
						else
						{
							openspace = false;
						}
					}
					else
					{
						openspace = false;
					}
				}

				// draw sand painter
				sp.Render(rx, ry, x, y);
			}
		}

		class SandPainter
		{
			private readonly _2_Substrate _parent;

			public SandPainter(_2_Substrate _parent)
			{
				this._parent = _parent;
				c = _parent.SomeColor();
				g = _parent.Random(0.01f, 0.1f);
			}

			private Color c;
			private float g;

			public void Render(float x, float y, float ox, float oy)
			{
				// modulate gain
				g += _parent.Random(-0.050f, 0.050f);
				float maxg = 1.0f;
				if (g < 0) g = 0;
				if (g > maxg) g = maxg;

				// calculate grains by distance
				//int grains = (int)(Math.Sqrt((ox-x)*(ox-x)+(oy-y)*(oy-y)));
				int grains = 64;

				// lay down grains of sand (transparent pixels)
				float w = g / (grains - 1);
				for (int i = 0; i < grains; i++)
				{
					float a = 0.1f - i / (grains * 10.0f);
					_parent.Stroke(c.R, c.G, c.B, a * 256);
					_parent.Point(ox + (x - ox) * (float)Math.Sin(Math.Sin(i * w)),
						oy + (y - oy) * (float)Math.Sin(Math.Sin(i * w)));
				}
			}
		}

		private Color SomeColor()
		{
			if (rng == null)
				rng = new Random();
			return goodcolor[rng.Next(numpal)];
		}

		public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Rectangle windowBounds)
		{
			for (int n = 0; n < num; n++)
			{
				cracks[n].Move();
			}

			texture.SetData(textureColors);

			spriteBatch.Draw(texture, windowBounds, Microsoft.Xna.Framework.Color.White);
		}

		private Texture2D texture;
		private Microsoft.Xna.Framework.Color[] textureColors;
		private int screenWidth, screenHeight;

		public void SetGraphicsDevice(GraphicsDevice graphicsDevice, Rectangle presentationParametersBounds)
		{
			screenWidth = presentationParametersBounds.Width;
			screenHeight = presentationParametersBounds.Height;

			texture = new Texture2D(graphicsDevice, presentationParametersBounds.Width,
				presentationParametersBounds.Height);
			textureColors =
				new Microsoft.Xna.Framework.Color[presentationParametersBounds.Width *
				                                  presentationParametersBounds.Height];
			texture.GetData(textureColors);
			for (int i = 0; i < textureColors.Length; i++)
			{
				textureColors[i] = Microsoft.Xna.Framework.Color.White;
			}

			goodcolor = new Color[maxpal];
			//Background(Color.FromArgb(255, 255, 255, 255));
			TakeColor();

			dimx = presentationParametersBounds.Width;
			dimy = presentationParametersBounds.Height;
			cgrid = new int[dimx * dimy];
			cracks = new Crack[maxnum];

			Begin();
		}

		public void Dispose()
		{
		}

		protected override void Point(float x, float y)
		{
			int ix = (int)x;
			int iy = (int)y;
			if (y < 0)
				return;
			if (y >= screenHeight)
				return;
			if (x < 0)
				return;
			if (x > screenWidth)
				return;

			textureColors[(iy * screenWidth) + ix].A = strokeColorObject.A;
			textureColors[(iy * screenWidth) + ix].R = strokeColorObject.R;
			textureColors[(iy * screenWidth) + ix].G = strokeColorObject.G;
			textureColors[(iy * screenWidth) + ix].B = strokeColorObject.B;
		}
	}
}
