using System;
using System.Drawing;
// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
using System.Collections.Generic;
// Issue 10 - End
using System.Windows.Forms;

using TheBox.BoxServer;

namespace TheBox.Roofing
{
	/// <summary>
	/// Describes a roof generated by the user
	/// </summary>
	public class Roof
	{
		private Point m_BasePoint;
		private RoofImage m_RoofImage;
		// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
		private List<RoofRect> m_Edges;
		// Issue 10 - End
		private TileSet m_TileSet;

		/// <summary>
		/// Occurs when the image for the current roof has been changed and should be redrawn
		/// </summary>
		public event EventHandler RoofImageChanged;

		/// <summary>
		/// Gets the roof image
		/// </summary>
		public Bitmap Image
		{
			get { return m_RoofImage.Image; }
		}

		/// <summary>
		/// Gets the rectangles currently selected
		/// </summary>
		// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
		public List<RoofRect> Rectangles
		// Issue 10 - End
		{
			get { return m_Edges; }
		}

		/// <summary>
		/// Gets or sets the tileset used for this roof
		/// </summary>
		public TileSet TileSet
		{
			get { return m_TileSet; }
			set { m_TileSet = value; }
		}

		/// <summary>
		/// Creates a new roof
		/// </summary>
		public Roof()
		{
			// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
			m_Edges = new List<RoofRect>();
			// Issue 10 - End
			m_RoofImage = new RoofImage();
		}

		/// <summary>
		/// Adds a rectangle to the roof
		/// </summary>
		/// <param name="rect">The new rectangle being added</param>
		/// <returns>True if the rectangle has been succesfully added, false otherwise</returns>
		public bool AddRectangle( RoofRect rect )
		{
			if ( rect.Rectangle.Height > 0 && rect.Rectangle.Width > 0 )
			{
				if ( ( !rect.Sloped && !rect.GoesUp && ( rect.Rectangle.Height % 2 > 0 ) ) || 
					( !rect.Sloped && rect.GoesUp && ( rect.Rectangle.Width % 2 > 0 ) ) )
				{
					// This rectangle isn't odd wide and itsn't sloped
					if ( MessageBox.Show( Pandora.Localization.TextProvider[ "Roofing.NotOddWide" ], "", MessageBoxButtons.YesNo, MessageBoxIcon.Question )
						== DialogResult.No )
					{
						return false;
					}
				}
			}
			else
			{
				MessageBox.Show( Pandora.Localization.TextProvider[ "Roofing.EmptyRoof" ] );
				return false;
			}
			
			foreach( RoofRect rr in m_Edges )
			{
				if ( rr.Rectangle.Equals( rect.Rectangle ) && rect.GoesUp == rr.GoesUp && rect.Sloped && rr.Sloped )
				{
					MessageBox.Show( Pandora.Localization.TextProvider[ "Roofing.AlreadyAdded"  ] );
					return false;
				}
			}

			m_Edges.Add( rect );

			if ( ! Calculate() )
			{
				MessageBox.Show( Pandora.Localization.TextProvider[ "Roofing.CantAdd" ] );
				m_Edges.Remove( rect );
				Calculate();

				return false;
			}
			else
			{
				m_RoofImage.CreateImage();
				return true;
			}
		}

		/// <summary>
		/// Removes the last rectangle in the list
		/// </summary>
		public void RemoveLastRectangle()
		{
			if ( m_Edges.Count == 0 )
				return;

			m_Edges.RemoveAt( m_Edges.Count - 1 );

			Calculate();
			m_RoofImage.CreateImage();
		}

		/// <summary>
		/// Gets the bounds of the roof defined by the current rectangles
		/// </summary>
		private Rectangle RoofBounds
		{
			get
			{
				if ( m_Edges.Count == 0 )
				{
					return Rectangle.Empty;
				}
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				RoofRect temp = m_Edges[ 0 ];
				// Issue 10 - End
				Rectangle bounds = new Rectangle( temp.Rectangle.Location, temp.Rectangle.Size );

				for ( int i = 1; i < m_Edges.Count; i++ )
				{
					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					RoofRect cfr = m_Edges[ i ];
					// Issue 10 - End

					if ( bounds.Left > cfr.Rectangle.Left )
					{
						bounds.X = cfr.Rectangle.X;
					}

					if ( bounds.Top > cfr.Rectangle.Top )
					{
						bounds.Y = cfr.Rectangle.Y;
					}

					if ( bounds.Right < cfr.Rectangle.Right )
					{
						bounds.Width = cfr.Rectangle.Right - bounds.Left;
					}

					if ( bounds.Bottom < cfr.Rectangle.Bottom )
					{
						bounds.Height = cfr.Rectangle.Bottom - bounds.Top;
					}
				}

				return bounds;
			}
		}

		/// <summary>
		/// Calculates the roof
		/// </summary>
		/// <returns>True if a valid roof can be generated from the current set of rectangles</returns>
		private bool Calculate()
		{
			// Clear the roofimage data if needed
			if ( m_RoofImage.Data.Count > 0 )
			{
				m_RoofImage.Data.Clear();
			}

			// Empty roof is a valid roof
			if ( m_Edges.Count == 0 )
			{
				return true;
			}

			Rectangle bounds = RoofBounds;

			// Maximum supported roof size is 300 x 300
			if ( bounds.Width > 300 || bounds.Height > 300 )
			{
				return false;
			}

			m_RoofImage.Width = bounds.Width + 3;
			m_RoofImage.Height = bounds.Height + 3;

			// Create the data array
			// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
			m_RoofImage.Data = new List<int>( m_RoofImage.Width * m_RoofImage.Height );
			// Issue 10 - End

			for ( int i = 0; i < m_RoofImage.Width * m_RoofImage.Height; i++ )
			{
				m_RoofImage.Data.Add( 0 );
			}

			m_BasePoint = new Point( bounds.X - 1, bounds.Y - 1 );

			bounds.X -= m_BasePoint.X;
			bounds.Y -= m_BasePoint.Y;

			foreach( RoofRect roofrect in m_Edges )
			{
				RoofRect rect = roofrect.Clone() as RoofRect;

				// Shift the rect to our relative coordinates
				Rectangle r = rect.Rectangle;
				
				r.X -= m_BasePoint.X;
				r.Y -= m_BasePoint.Y;

				rect.Rectangle = r;

				if ( rect.GoesUp )
				{
					for ( int i = 0; i < rect.Rectangle.Height + 1; i++ )
					{
						AddVertexX(	rect.Rectangle.Left,
							rect.Rectangle.Right,
							rect.Rectangle.Top + i,
							rect.Tent ? Math.Min( i + 1, rect.Rectangle.Height + 1 - i ) : 1000,
							rect.Sloped ? rect.Slope : Slope.None );
					}
				}
				else
				{
					for ( int i = 0; i < rect.Rectangle.Width + 1; i++ )
					{
						AddVertexY(
							rect.Rectangle.Top,
							rect.Rectangle.Bottom,
							rect.Rectangle.Left + i,
							rect.Tent ? Math.Min( i + 1, rect.Rectangle.Width + 1 - i ) : 1000,
							rect.Sloped ? rect.Slope : Slope.None );
					}
				}
			}

			return true;
		}

		/// <summary>
		/// Adds a vertex to the roof data lying perpendicular to the Y axis of its roof edge
		/// </summary>
		/// <param name="x1">The left x coordinate of the section</param>
		/// <param name="x2">The right x coordinate of the section</param>
		/// <param name="y">The y coordinate of the section</param>
		/// <param name="hLimit">The limit in height for this roof</param>
		/// <param name="slope">The type of slope for this roof</param>
		/// <returns>True if the section has been correctly added</returns>
		private bool AddVertexX( int x1, int x2, int y, int hLimit, Slope slope )
		{
			bool added = false;

			int height = 1;

			while ( x2 >= x1 )
			{
				if ( slope == Slope.None || slope == Slope.Right )
				{
					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					if ( m_RoofImage.Data[ x2 + y * m_RoofImage.Width ] < height )
					// Issue 10 - End
					{
						m_RoofImage.Data[ x2 + y * m_RoofImage.Width ] = height;
						added = true;
					}

					x2--;
				}

				if ( slope == Slope.None || slope == Slope.Left )
				{
					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					if ( m_RoofImage.Data[ x1 + y * m_RoofImage.Width ] < height )
					// Issue 10 - End
					{
						m_RoofImage.Data[ x1 + y * m_RoofImage.Width ] = height;
						added = true;
					}

					x1++;
				}

				if ( height < hLimit )
				{
					height++;
				}
			}
			return added;
		}

		/// <summary>
		/// Adds a roof section perpendicular to the X axis
		/// </summary>
		/// <param name="y1">The lower y coordinate</param>
		/// <param name="y2">The upper y coordinate</param>
		/// <param name="x">The x coordinate (fixed)</param>
		/// <param name="hLimit">The height limit for this roof section</param>
		/// <param name="slope">The slope for this section</param>
		/// <returns>True if the section has been added</returns>
		private bool AddVertexY( int y1, int y2, int x, int hLimit, Slope slope )
		{
			bool added = false;
			int height = 1;

			while ( y2 >= y1 )
			{
				if ( slope == Slope.None || slope == Slope.Top )
				{
					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					if ( m_RoofImage.Data[ x + y2 * m_RoofImage.Width ] < height )
					// Issue 10 - End
					{
						m_RoofImage.Data[ x + y2 * m_RoofImage.Width ] = height;
						added = true;
					}

					y2--;
				}

				if ( slope == Slope.None || slope == Slope.Bottom )
				{
					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					if ( m_RoofImage.Data[ x + y1 * m_RoofImage.Width ] < height )
					// Issue 10 - End
					{
						m_RoofImage.Data[ x + y1 * m_RoofImage.Width ] = height;
						added = true;
					}

					y1++;
				}

				if ( height < hLimit )
				{
					height++;
				}
			}

			return added;
		}

		/// <summary>
		/// Defines possible values for the classic generation mode
		/// </summary>
		public enum TestMode
		{
			/// <summary>
			/// All the roof is generated right away
			/// </summary>
			NoTest,
			/// <summary>
			/// Only the corner pieces are generated first
			/// </summary>
			Test,
			/// <summary>
			/// After testing, the remaining pieces are generated
			/// </summary>
			Rest
		}

		/// <summary>
		/// Retrieves a portion of the roof image consisting of 3 samples
		/// </summary>
		/// <param name="index">The center sample index for the line</param>
		/// <returns>An array of 3 int values representing the line centered at index</returns>
		private int[] MakeLine( int index )
		{
			int[] line = new int[ 3 ];

			// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
			line[ 0 ] = m_RoofImage.Data[ index - 1 ];
			line[ 1 ] = m_RoofImage.Data[ index ];
			line[ 2 ] = m_RoofImage.Data[ index + 1 ];
			// Issue 10 - End

			return line;
		}

		/// <summary>
		/// Does classic generation of the roof
		/// </summary>
		/// <param name="mode">The mode for this generation</param>
		/// <param name="height">The height at which the tiling occurs</param>
		/// <param name="hue">The hue for the roof</param>
		/// <returns>True if generation is succesful</returns>
		public bool GenerateClassic( TestMode mode, int height, int hue )
		{
			int[] roofIDs = new int[ m_RoofImage.Width * m_RoofImage.Height ];
			bool fail = false;

			// Revert any sign changes due to image processing
			for ( int i = 0; i < m_RoofImage.Width * m_RoofImage.Height; i++ )
			{
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				if ( m_RoofImage.Data[ i ] < 0 )
					m_RoofImage.Data[ i ] = - m_RoofImage.Data[ i ];
				// Issue 10 - End
			}

			// Calculate the roof ids
			for ( int i = 0; i < m_RoofImage.Width * m_RoofImage.Height; i++ )
			{
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				if ( m_RoofImage.Data[ i ] == 0 )
				// Issue 10 - End
				{
					roofIDs[ i ] = 0;
				}
				else
				{
					uint flags = RoofingHelper.GetFlags( MakeLine( i - m_RoofImage.Width ), MakeLine( i ), MakeLine( i + m_RoofImage.Width ) );
					roofIDs[ i ] = m_TileSet.FindID( flags );

					if ( roofIDs[ i ] == 0 )
					{
						// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
						m_RoofImage.Data[ i ] = - m_RoofImage.Data[ i ];
						// Issue 10 - End
						fail = true;
					}

					if ( mode != TestMode.NoTest )
					{
						bool corner = !((flags & ~0x88878778) != 0) ||
							!((flags & ~0x88887877) != 0) ||
							!((flags & ~0x77878888) != 0) ||
							!((flags & ~0x87787888) != 0) ||
							!((flags & ~0x87777777) != 0) ||
							!((flags & ~0x77877777) != 0) ||
							!((flags & ~0x77777877) != 0) ||
							!((flags & ~0x77777778) != 0);

						if ( mode == TestMode.Test && !corner )
						{
							roofIDs[ i ] = 0;
						}

						if ( mode == TestMode.Rest && corner )
						{
							roofIDs[ i ] = 0;
						}
					}
				}
			}

			if ( fail )
			{
				m_RoofImage.CreateImage();

				// Request redraw image
				if ( RoofImageChanged != null )
				{
					RoofImageChanged( this, new EventArgs() );
				}

				if ( MessageBox.Show(
					Pandora.Localization.TextProvider[ "Roofing.MissTiles" ],
					"",
					MessageBoxButtons.YesNo,
					MessageBoxIcon.Question ) == DialogResult.No )
				{
					return false;
				}
			}

			string idFormat = hue > 0 ? "static {0} set hue " + hue.ToString() : "static {0}";

			int dx = 0;
			int dy = 0;
			int p = 0;

			int tilex = 0;
			int tiley = 0;
			int tilew = 0;
			int tileh = 0;

			int tilez = 0;
			int tileid = 0;

			for ( int y = 0; y < m_RoofImage.Height; y++ )
			{
				for ( int x = 0; x < m_RoofImage.Width; x++, p++ )
				{
					if ( roofIDs[ p ] == 0 )
					{
						continue;
					}

					for ( dx = 1; dx + x < m_RoofImage.Width; dx++ )
					{
						// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
						if ( ( roofIDs[ p + dx ] != roofIDs[ p ] ) || ( m_RoofImage.Data[ p ] != m_RoofImage.Data[ p + dx ] ) )
						// Issue 10 - End
						{
							break;
						}
					}

					for ( dy = 1; dy + y < m_RoofImage.Height; dy++ )
					{
						if ( ( (int) roofIDs[ p + m_RoofImage.Width * dy ] != roofIDs[ p ] ) ||
							// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
							( m_RoofImage.Data[ p ] != m_RoofImage.Data[ p + m_RoofImage.Width * dy ] ) )
						// Issue 10 - End
						{
							break;
						}
					}

					dx--;
					dy--;

					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					tilez = height + ( 3 * m_RoofImage.Data[ p ] ) - 3;
					// Issue 10 - End
					tileid = roofIDs[ p ];

					if ( dx > 0 || dy > 0 )
					{
						tilex = m_BasePoint.X + x;
						tiley = m_BasePoint.Y + y;

						if ( dy > dx )
						{
							tilew = 1;
							tileh = dy + 1;

							while ( dy >= 0 )
							{
								roofIDs[ p + m_RoofImage.Width * dy ] = 0;
								dy--;
							}
						}
						else
						{
							tilew = dx + 1;
							tileh = 1;

							while ( dx >= 0 )
							{
								roofIDs[ p + dx ] = 0;
								dx--;
							}
						}

						x += dx;
						p += dx;
					}
					else
					{
						tilex = m_BasePoint.X + x;
						tiley = m_BasePoint.Y + y;
						tilew = 1;
						tileh = 1;
					}

					// Build command
					string item = string.Format( idFormat, tileid );
					string cmd = string.Format( "TileXYZ {0} {1} {2} {3} {4} {5}",
																					 tilex, tiley, tilew, tileh, tilez, item );
					Pandora.SendToUO( cmd, true );
				}
			}

			return true;
		}

		/// <summary>
		/// Generates the roof through the boxserver
		/// </summary>
		/// <param name="height">The height at which generation should happen</param>
		/// <param name="hue">The hue for the items</param>
		public void GenerateThroughServer( int height, int hue )
		{
			int[] roofIDs = new int[ m_RoofImage.Width * m_RoofImage.Height ];
			bool fail = false;

			// Revert any sign changes due to image processing
			for ( int i = 0; i < m_RoofImage.Width * m_RoofImage.Height; i++ )
			{
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				if ( m_RoofImage.Data[ i ] < 0 )
					m_RoofImage.Data[ i ] = - m_RoofImage.Data[ i ];
				// Issue 10 - End
			}

			// Calculate the roof ids
			for ( int i = 0; i < m_RoofImage.Width * m_RoofImage.Height; i++ )
			{
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				if ( m_RoofImage.Data[ i ] == 0 )
				// Issue 10 - End
				{
					roofIDs[ i ] = 0;
				}
				else
				{
					uint flags = RoofingHelper.GetFlags( MakeLine( i - m_RoofImage.Width ), MakeLine( i ), MakeLine( i + m_RoofImage.Width ) );
					roofIDs[ i ] = m_TileSet.FindID( flags );

					if ( roofIDs[ i ] == 0 )
					{
						// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
						m_RoofImage.Data[ i ] = - m_RoofImage.Data[ i ];
						// Issue 10 - End
						fail = true;
					}
				}
			}

			if ( fail )
			{
				m_RoofImage.CreateImage();

				// Request redraw image
				if ( RoofImageChanged != null )
				{
					RoofImageChanged( this, new EventArgs() );
				}

				if ( MessageBox.Show(
					Pandora.Localization.TextProvider[ "Roofing.MissTiles" ],
					"",
					MessageBoxButtons.YesNo,
					MessageBoxIcon.Question ) == DialogResult.No )
				{
					return;
				}
			}

			string idFormat = hue > 0 ? "static {0} set hue " + hue.ToString() : "static {0}";

			int p = 0;

			// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
			List<BuildItem> items = new List<BuildItem>();
			// Issue 10 - End

			for ( int y = 0; y < m_RoofImage.Height; y++ )
			{
				for ( int x = 0; x < m_RoofImage.Width; x++, p++ )
				{
					if ( roofIDs[ p ] == 0 )
					{
						continue;
					}

					// Build item
					BuildItem item = new BuildItem();
					
					item.Hue = hue;
					item.ID = roofIDs[ p ];

					item.X = m_BasePoint.X + x;
					item.Y = m_BasePoint.Y + y;
					// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
					item.Z = height + ( 3 * m_RoofImage.Data[ p ] ) - 3;
					// Issue 10 - End

					items.Add( item );
				}
			}

			TheBox.BoxServer.BuildMessage msg = new BuildMessage();
			msg.Items = items;

            Pandora.BoxConnection.SendToServer(msg);
		}
	}
}