using System;
using BizHawk.Common;

namespace BizHawk.Emulation.Cores.Consoles.Vectrex
{
	public class PPU
	{
		public VectrexHawk Core { get; set; }

		public bool zero_sig, ramp_sig, blank_sig, off_screen;
		public byte vec_scale, x_vel, y_vel, bright;
		public double x_pos, y_pos;

		public int skip;
		public uint bright_int_1, bright_int_2, bright_int_3;

		public static uint br = 0xFFFFFFFF;

		// lines to draw in a frame and vairables to go to new line
		public double[] draw_lines = new double[1024 * 4 * 4];
		public uint[] line_brights = new uint[1024 * 3 * 4];
		public bool[] line_vis = new bool[1024 * 4];

		public double[] draw_lines_old_screen = new double[1024 * 4 * 4];
		public uint[] line_brights_old_screen = new uint[1024 * 3 * 4];
		public bool[] line_vis_old_screen = new bool[1024 * 4];
		public int line_pointer_old_screen;

		public int line_pointer;
		public bool blank_old, zero_old;
		public byte x_vel_old, y_vel_old;
		public uint bright_int_1_old;

		public void tick()
		{
			//Console.WriteLine(ramp_sig + " " + zero_sig + " " + blank_sig + " " + Core.cpu.TotalExecutedCycles + " " + (x_vel - 128.0) + " " + x_pos 
			//+ " " + (y_vel - 128.0) + " " + y_pos + " " + Core.t1_counter + " " + vec_scale);

			if (ramp_sig && !zero_sig)
			{
				if (skip == 0)
				{
					x_pos = x_pos + (x_vel - 128.0) / 256.0 * (vec_scale + 2);
					y_pos = y_pos - (y_vel - 128.0) / 256.0 * (vec_scale + 2);
				}
				else
				{
					skip--;
				}

				off_screen = false;
				
				if (x_pos > 257) { off_screen = true; if (x_pos > (257 + 256)) { x_pos = (257 + 256); } }
				if (x_pos < 2) { off_screen = true; if (x_pos < (2 - 256)) { x_pos = (2 - 256); } }
				if (y_pos > 385) { off_screen = true; if (y_pos > (385 + 256)) { y_pos = (385 + 256); } }
				if (y_pos < 2) { off_screen = true; if (y_pos < (2 - 256)) { y_pos = (2 - 256); } }
			}
			else if (zero_sig)
			{
				x_pos = 128 + 2;
				y_pos = 192 + 2;
			}
			/*
			if (!blank_sig && !off_screen)
			{
				
				Core._vidbuffer[(int)(Math.Round(x_pos) + 260 * Math.Round(y_pos))] |= (int)(br & bright_int_1);
				
				Core._vidbuffer[(int)(Math.Round(x_pos) + 1 + 260 * Math.Round(y_pos))] |= (int)(br & bright_int_2);
				Core._vidbuffer[(int)(Math.Round(x_pos) - 1 + 260 * Math.Round(y_pos))] |= (int)(br & bright_int_2);
				Core._vidbuffer[(int)(Math.Round(x_pos) + 260 * (Math.Round(y_pos) + 1))] |= (int)(br & bright_int_2);
				Core._vidbuffer[(int)(Math.Round(x_pos) + 260 * (Math.Round(y_pos) - 1))] |= (int)(br & bright_int_2);
				
				Core._vidbuffer[(int)(Math.Round(x_pos) + 2 + 260 * Math.Round(y_pos))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) - 2 + 260 * Math.Round(y_pos))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) + 260 * (Math.Round(y_pos) + 2))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) + 260 * (Math.Round(y_pos) - 2))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) + 1 + 260 * (Math.Round(y_pos) + 1))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) + 1 + 260 * (Math.Round(y_pos) - 1))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) - 1 + 260 * (Math.Round(y_pos) + 1))] |= (int)(br & bright_int_3);
				Core._vidbuffer[(int)(Math.Round(x_pos) - 1 + 260 * (Math.Round(y_pos) - 1))] |= (int)(br & bright_int_3);
				
			}
			*/
		}

		public void draw_screen()
		{
			// screen is 2 times the internal size of the image 
			double start_x = 0;
			double end_x = 0;
			double start_y = 0;
			double end_y = 0;

			uint c_bright = 0;

			for (int i = 0; i < line_pointer; i++)
			{
				if (line_vis[i] == true)
				{
					start_x = draw_lines[i * 4];
					start_y = draw_lines[i * 4 + 1];

					end_x = draw_lines[i * 4 + 2];
					end_y = draw_lines[i * 4 + 3];

					c_bright = line_brights[i * 3];

					double steps = 0;

					double max_x = Math.Abs(end_x - start_x);
					double max_y = Math.Abs(end_y - start_y);

					bool draw_this_line = true;

					if ((start_x < 2) && (end_x < 2)) { draw_this_line = false; }
					if ((start_y < 2) && (end_y < 2)) { draw_this_line = false; }
					if ((start_x > 257) && (end_x > 257)) { draw_this_line = false; }
					if ((start_y > 385) && (end_y > 385)) { draw_this_line = false; }

					if (draw_this_line)
					{
						// truncate lines to only be on screen
						if ((start_x >= 2) && (end_x < 2)) { max_x = start_x - 2; end_x = 2; }
						if ((end_x >= 2) && (start_x < 2)) { max_x = end_x - 2; start_x = 2; }
						if ((start_x <= 257) && (end_x > 257)) { max_x = 257 - start_x; end_x = 257; }
						if ((end_x <= 257) && (start_x >= 257)) { max_x = 257 - end_x; start_x = 257; }

						if ((start_y >= 2) && (end_y < 2)) { max_y = start_y - 2; end_y = 2; }
						if ((end_y >= 2) && (start_y < 2)) { max_y = end_y - 2; start_y = 2; }
						if ((start_y <= 385) && (end_y > 385)) { max_y = 385 - start_y; end_y = 257; }
						if ((end_y <= 385) && (start_y >= 385)) { max_y = 385 - end_y; start_y = 385; }

						// screen size is double internal size
							
						start_x *= 2;
						end_x *= 2;
						start_y *= 2;
						end_y *= 2;

						max_x *= 2;
						max_y *= 2;
							
						steps = Math.Max(max_x, max_y) + 1;

						double x_step = (end_x - start_x) / steps;
						double y_step = (end_y - start_y) / steps;

						for (int j = 0; j <= steps; j++)
						{
							Core._vidbuffer[(int)(Math.Round(start_x + x_step * j) + 260 * 2 * Math.Round(start_y + y_step * j))] |= (int)(br & c_bright);

							// at minimum need to make 3 pixels thick to be represetnative of a real vectrex, add more for glow
							Core._vidbuffer[(int)(Math.Round(start_x + x_step * j) + 1 + 260 * 2 * Math.Round(start_y + y_step * j))] |= (int)(br & c_bright);
							Core._vidbuffer[(int)(Math.Round(start_x + x_step * j) - 1 + 260 * 2 * Math.Round(start_y + y_step * j))] |= (int)(br & c_bright);
							Core._vidbuffer[(int)(Math.Round(start_x + x_step * j) + 260 * 2 * (Math.Round(start_y + y_step * j) + 1))] |= (int)(br & c_bright);
							Core._vidbuffer[(int)(Math.Round(start_x + x_step * j) + 260 * 2 * (Math.Round(start_y + y_step * j) - 1))] |= (int)(br & c_bright);
						}
					}
				}
			}

			// copy all the data to the old screen arrays to save it for loading states
			for (int i = 0; i < line_pointer; i++)
			{
				draw_lines_old_screen[i * 4] = draw_lines[i * 4];
				draw_lines_old_screen[i * 4 + 1] = draw_lines[i * 4 + 1];
				draw_lines_old_screen[i * 4 + 2] = draw_lines[i * 4 + 2];
				draw_lines_old_screen[i * 4 + 3] = draw_lines[i * 4 + 3];

				line_brights_old_screen[i * 3] = line_brights[i * 3];
				line_brights_old_screen[i * 3 + 1] = line_brights[i * 3 + 1];
				line_brights_old_screen[i * 3 + 2] = line_brights[i * 3 + 2];

				line_vis_old_screen[i] = line_vis[i];
			}

			line_pointer_old_screen = line_pointer;

			// reset pointer back to zero but keep current starting point
			draw_lines[0] = draw_lines[line_pointer * 4];
			draw_lines[1] = draw_lines[line_pointer * 4 + 1];

			line_vis[0] = line_vis[line_pointer];

			line_brights[0] = line_brights[line_pointer * 3];

			line_pointer = 0;
		}

		public void draw_old_screen()
		{
			// screen is 2 times the internal size of the image 
			double start_x = 0;
			double end_x = 0;
			double start_y = 0;
			double end_y = 0;

			uint c_bright = 0;

			for (int i = 0; i < line_pointer_old_screen; i++)
			{
				if (line_vis_old_screen[i] == true)
				{
					start_x = draw_lines_old_screen[i * 4];
					start_y = draw_lines_old_screen[i * 4 + 1];

					end_x = draw_lines_old_screen[i * 4 + 2];
					end_y = draw_lines_old_screen[i * 4 + 3];

					c_bright = line_brights_old_screen[i * 3];

					double steps = 0;

					double max_x = Math.Abs(end_x - start_x);
					double max_y = Math.Abs(end_y - start_y);

					bool draw_this_line = true;

					if ((start_x < 2) && (end_x < 2)) { draw_this_line = false; }
					if ((start_y < 2) && (end_y < 2)) { draw_this_line = false; }
					if ((start_x > 257) && (end_x > 257)) { draw_this_line = false; }
					if ((start_y > 385) && (end_y > 385)) { draw_this_line = false; }

					if (draw_this_line)
					{
						// truncate lines to only be on screen
						if ((start_x >= 2) && (end_x < 2)) { max_x = start_x - 2; end_x = 2; }
						if ((end_x >= 2) && (start_x < 2)) { max_x = end_x - 2; start_x = 2; }
						if ((start_x <= 257) && (end_x > 257)) { max_x = 257 - start_x; end_x = 257; }
						if ((end_x <= 257) && (start_x >= 257)) { max_x = 257 - end_x; start_x = 257; }

						if ((start_y >= 2) && (end_y < 2)) { max_y = start_y - 2; end_y = 2; }
						if ((end_y >= 2) && (start_y < 2)) { max_y = end_y - 2; start_y = 2; }
						if ((start_y <= 385) && (end_y > 385)) { max_y = 385 - start_y; end_y = 257; }
						if ((end_y <= 385) && (start_y >= 385)) { max_y = 385 - end_y; start_y = 385; }

						// screen size is double internal size

						start_x *= 2;
						end_x *= 2;
						start_y *= 2;
						end_y *= 2;

						max_x *= 2;
						max_y *= 2;

						steps = Math.Max(max_x, max_y) + 1;

						double x_step = (end_x - start_x) / steps;
						double y_step = (end_y - start_y) / steps;

						for (int j = 0; j <= steps; j++)
						{
							Core._framebuffer[(int)(Math.Round(start_x + x_step * j) + 260 * 2 * Math.Round(start_y + y_step * j))] |= (int)(br & c_bright);

							// at minimum need to make 3 pixels thick to be represetnative of a real vectrex, add more for glow
							Core._framebuffer[(int)(Math.Round(start_x + x_step * j) + 1 + 260 * 2 * Math.Round(start_y + y_step * j))] |= (int)(br & c_bright);
							Core._framebuffer[(int)(Math.Round(start_x + x_step * j) - 1 + 260 * 2 * Math.Round(start_y + y_step * j))] |= (int)(br & c_bright);
							Core._framebuffer[(int)(Math.Round(start_x + x_step * j) + 260 * 2 * (Math.Round(start_y + y_step * j) + 1))] |= (int)(br & c_bright);
							Core._framebuffer[(int)(Math.Round(start_x + x_step * j) + 260 * 2 * (Math.Round(start_y + y_step * j) - 1))] |= (int)(br & c_bright);
						}
					}
				}
			}
		}

		public void new_draw_line()
		{
			if (((ramp_sig && !zero_sig) && ((x_vel != x_vel_old) || (y_vel != y_vel_old))) ||
				(blank_sig != blank_old) ||
				(bright_int_1 != bright_int_1_old) ||
				(zero_sig != zero_old))
			{
				draw_lines[line_pointer * 4 + 2] = x_pos;
				draw_lines[line_pointer * 4 + 3] = y_pos;

				line_pointer++;

				draw_lines[line_pointer * 4] = x_pos;
				draw_lines[line_pointer * 4 + 1] = y_pos;

				line_brights[line_pointer * 3] = bright_int_1;
				line_brights[line_pointer * 3 + 1] = bright_int_2;
				line_brights[line_pointer * 3 + 2] = bright_int_3;

				line_vis[line_pointer] = !blank_sig;
			}

			if (ramp_sig && !zero_sig)
			{
				x_vel_old = x_vel;
				y_vel_old = y_vel;
			}

			zero_old = zero_sig;
			blank_old = blank_sig;
			bright_int_1_old = bright_int_1;			
		}

		public void Reset()
		{
			zero_sig = true;
			blank_sig = true;
			ramp_sig = false;

			vec_scale = x_vel = y_vel = bright = 0;
			x_pos = 128 + 2;
			y_pos = 192 + 2;

			line_pointer = 0;
			blank_old = zero_old = true;
			x_vel_old = y_vel_old = 0;
			bright_int_1_old = 0;

			// initial line array values
			draw_lines[line_pointer * 5] = x_pos;
			draw_lines[line_pointer * 5 + 1] = y_pos;

			line_brights[line_pointer * 3] = 0;
			line_brights[line_pointer * 3 + 1] = 0;
			line_brights[line_pointer * 3 + 2] = 0;

			line_vis[line_pointer] = !blank_sig;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync(nameof(zero_sig), ref zero_sig);
			ser.Sync(nameof(blank_sig), ref blank_sig);
			ser.Sync(nameof(ramp_sig), ref ramp_sig);
			ser.Sync(nameof(off_screen), ref off_screen);

			ser.Sync(nameof(vec_scale), ref vec_scale);
			ser.Sync(nameof(x_vel), ref x_vel);
			ser.Sync(nameof(y_vel), ref y_vel);
			ser.Sync(nameof(bright), ref bright);

			ser.Sync(nameof(x_pos), ref x_pos);
			ser.Sync(nameof(y_pos), ref y_pos);

			ser.Sync(nameof(skip), ref skip);
			ser.Sync(nameof(bright_int_1), ref bright_int_1);
			ser.Sync(nameof(bright_int_2), ref bright_int_2);
			ser.Sync(nameof(bright_int_3), ref bright_int_3);

			ser.Sync(nameof(draw_lines), ref draw_lines, false);
			ser.Sync(nameof(line_brights), ref line_brights, false);
			ser.Sync(nameof(line_vis), ref line_vis, false);
			ser.Sync(nameof(line_pointer), ref line_pointer);
			ser.Sync(nameof(blank_old), ref blank_old);
			ser.Sync(nameof(zero_old), ref zero_old);
			ser.Sync(nameof(x_vel_old), ref x_vel_old);
			ser.Sync(nameof(y_vel_old), ref y_vel_old);
			ser.Sync(nameof(bright_int_1_old), ref bright_int_1_old);

			ser.Sync(nameof(draw_lines_old_screen), ref draw_lines_old_screen, false);
			ser.Sync(nameof(line_brights_old_screen), ref line_brights_old_screen, false);
			ser.Sync(nameof(line_vis_old_screen), ref line_vis_old_screen, false);
			ser.Sync(nameof(line_pointer_old_screen), ref line_pointer_old_screen);
		}
	}
}
