using System;

using System.Collections;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

using Icarus.Graphics;
using Icarus.Graphics.Animation;
using Icarus.Logic;
using Icarus.Renderer.Animation;
using Icarus.Logic.Physics;

namespace Icarus.Renderer
{
	/// <summary>
	/// This is the class that actually renders stuff.
	/// </summary>
	public class ViewRenderer
	{
		private static System.Drawing.Point msOrigin = new System.Drawing.Point(0, 0);

		/// <summary>
		/// Initializes the view renderer.
		/// </summary>
		public ViewRenderer() { }

		/// <summary>
		/// Returns the point on the screen corresponding to the point p in the stage s.
		/// </summary>
		/// <param name="p"></param>
		/// <param name="s"></param>
		/// <returns></returns>
		public System.Drawing.Point WorldToScreen(Point p, Stage s)
		{
			float x = 0;
			float y = 0;

			// Compute point relative to the center of the screen in world coordinates:
			x = p.x - s.ViewCenter.x;
			y = p.y - s.ViewCenter.y;

			// Scale to screen coordinates:
			x *= s.ZoomFactor;
			y *= s.ZoomFactor;

			// Offset to screen corner:
			x += Direct3DSettings.Instance.ParentWindow.WindowSize.Width / 2.0f;// - x;
			y += Direct3DSettings.Instance.ParentWindow.WindowSize.Height / 2.0f;// - y;

			return new System.Drawing.Point((int) Math.Round(x), (int) Math.Round(y));
		}

		#region World View Positioning Stuff
		private int UpdateHorizontal(int margin, int mercX, int stageLeft, int stageRight, int screenWidth)
		{
			int result = 0;
			
			// Account for player position:
			int leftdx = margin - mercX;
			int rightdx = mercX - (screenWidth - margin);
			if (leftdx > 0) { result -= leftdx; }
			else if (rightdx > 0) { result += rightdx; }

			// Don't scroll off the stage:
			leftdx = stageLeft - result;
			rightdx = screenWidth - (stageRight - result);
			if (leftdx > 0) { result += leftdx; } 
			else if (rightdx > 0) { result -= rightdx; }

			return result;
		}

		private int UpdateVertical(int margin, int mercY, int stageTop, int stageBottom, int screenHeight)
		{
			int result = 0;
			
			// Account for player position:
			int topdx = margin - mercY;
			int bottomdx = mercY - (screenHeight - margin);
			if (topdx > 0) { result -= topdx; }
			else if (bottomdx > 0) { result += bottomdx; }

			//Don't scroll off the stage:
			topdx = stageTop - result;
			bottomdx = screenHeight - (stageBottom - result);
			if (topdx > 0) { result += topdx; }
			else if (bottomdx > 0) { result -= bottomdx; }

			return result;
		}

		private void UpdateWorldView(Stage s)
		{
			System.Drawing.Point mercOnScreen = WorldToScreen(s.PlayerActor.Location, s);
			System.Drawing.Point topLeftCorner = WorldToScreen(new Icarus.Logic.Physics.Point(0, 0, 0), s);
			System.Drawing.Point bottomRightCorner = WorldToScreen(new Icarus.Logic.Physics.Point(s.Width, s.Height, 0), s);
			int screenWidth = Direct3DSettings.Instance.ParentWindow.WindowSize.Width;
			int screenHeight = Direct3DSettings.Instance.ParentWindow.WindowSize.Height;
			int margin = Direct3DSettings.Instance.ScrollMargin;

			if (s.Width * s.ZoomFactor > screenWidth)
			{
				//Potentially need to scroll horizontally:
				s.ViewCenter.x += UpdateHorizontal(margin, mercOnScreen.X, topLeftCorner.X, bottomRightCorner.X, screenWidth) / s.ZoomFactor;
			} 
			else
			{
				//Just center on screen:
				s.ViewCenter.x = s.Width / 2;
			}

			if (s.Height * s.ZoomFactor > screenHeight)
			{
				//Potentially need to scroll vertically:
				s.ViewCenter.y += UpdateVertical(margin, mercOnScreen.Y, topLeftCorner.Y, bottomRightCorner.Y, screenHeight) / s.ZoomFactor;
			}
			else
			{
				//Just center on screen:
				s.ViewCenter.y = s.Height / 2;
			}
		}
		#endregion

		private Matrix getParallaxMatrix(float parallaxFactor, float zoomFactor, Point viewCenter, Point stageCenter, Point backgroundCenter)
		{
			// Apply parallax to zoom level:
			float zoom = (float) Math.Pow(zoomFactor, parallaxFactor);

            float offx = backgroundCenter.x - (stageCenter.x - viewCenter.x) * (backgroundCenter.x / stageCenter.x) * parallaxFactor;
            float offy = backgroundCenter.y - (stageCenter.y - viewCenter.y) * (backgroundCenter.y / stageCenter.y) * parallaxFactor;

			// Compute appropriate transformation matrix:
			return getTransformationMatrixFor(new Point(offx, offy, 0), zoom);
		}

		private Matrix getTransformationMatrixFor(Point offset, float zoom)
		{
			Matrix screenTranslation = Matrix.Translation(Direct3DSettings.Instance.ParentWindow.WindowSize.Width / 2.0f, Direct3DSettings.Instance.ParentWindow.WindowSize.Height / 2.0f, 0);
			Matrix translation = Matrix.Translation(-offset.x, -offset.y, 0);
			Matrix scaling = Matrix.Scaling(zoom, zoom, 1.0f);

			return Matrix.Multiply(Matrix.Multiply(translation, scaling), screenTranslation);
		}

		/// <summary>
		/// Renders the animation.
		/// </summary>
		/// <param name="coordinates"></param>
		/// <param name="parameters"></param>
		/// <param name="anim"></param>
		private void renderAnimation(System.Drawing.Point coordinates, IRenderParameters parameters, FramesetAnimation anim)
		{
			anim.update(1);

			//Render current frame:
			anim.CurrentFrame.Render(coordinates, parameters);
		}

		private System.Drawing.Bitmap makeRectangle(int width, int height, string text)
		{
			System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(width, height);
			System.Drawing.Graphics gfx = System.Drawing.Graphics.FromImage(bmp);

			gfx.DrawRectangle(new System.Drawing.Pen(System.Drawing.Color.White, 1), 0, 0, width - 1, height - 1);
			gfx.DrawString(text, new System.Drawing.Font(System.Drawing.FontFamily.GenericSerif.Name, 8), new System.Drawing.SolidBrush(System.Drawing.Color.White), 5, 5);

			return bmp;
		}

		private void RenderWorldObjectChildren(WorldObject obj, IRenderParameters parameters)
		{
			for (int i = 0; i < obj.GetChildCount(); i++)
			{
				WorldObject c = obj.GetChildAt(i);

				renderAnimation(new System.Drawing.Point((int) c.Location.x, (int) c.Location.y), parameters, c.Animation);

				if (Direct3DSettings.Instance.RenderCollisionBoxes)
				{
					System.Drawing.Bitmap bmp = makeRectangle(((PhysicsObject) c).CollisionBox.BoundingBoxWidth, ((PhysicsObject) c).CollisionBox.BoundingBoxHeight, "child" + i.ToString());
					D3DFrame frm = new D3DFrame();
					frm.initialize(ViewRenderer.msOrigin, bmp);

					frm.Render(new System.Drawing.Point((int) c.CollisionBox.BoundingBox.X, (int) c.CollisionBox.BoundingBox.Y), parameters);
				}

				RenderWorldObjectChildren(c, parameters);
			}
		}

		private void renderViewableList(IViewable[] viewables, IRenderParameters parameters) 
		{
			foreach (IViewable v in viewables)
			{
				if (v == null) break;

				renderAnimation(new System.Drawing.Point((int) v.Location.x, (int) v.Location.y), parameters, v.Animation);

				if (v is PhysicsObject)
                {
                    PhysicsObject po = v as PhysicsObject;

                    if (Direct3DSettings.Instance.RenderCollisionBoxes)
                    {
                        System.Drawing.Bitmap bmp = makeRectangle(po.CollisionBox.BoundingBoxWidth, po.CollisionBox.BoundingBoxHeight, "parent");
                        D3DFrame frm = new D3DFrame();
                        frm.initialize(ViewRenderer.msOrigin, bmp);

                        frm.Render(new System.Drawing.Point((int) po.CollisionBox.BoundingBox.X, (int) po.CollisionBox.BoundingBox.Y), parameters);
                    }

					RenderWorldObjectChildren(v as WorldObject, parameters);
				}
			}
		}

		/// <summary>
		/// Renders a level.
		/// </summary>
		public void Render(Level lvl, IRenderParameters parameters) 
		{
			D3DRenderParameters p = (D3DRenderParameters) parameters;

			//First, render the backgrounds:
			for (int i = 0; i < lvl.CurrStage.GetBackgroundCount(); i++)
			{
				// Get current background:
				LevelBackground background = lvl.CurrStage.GetBackgroundAt(i);

				// Compute appropriate transformation matrix:
				p.TargetSprite.Transform = getParallaxMatrix((float) background.ParallaxFactor, lvl.CurrStage.ZoomFactor, lvl.CurrStage.ViewCenter, lvl.CurrStage.LevelCenter, background.BackgroundCenter);

				background.UpdateAnimation(1);
				background.BackgroundRenderable.Render(ViewRenderer.msOrigin, parameters);
			}

			//lvl.CurrStage.ViewCenter = lvl.CurrStage.PlayerActor.Location;
			UpdateWorldView(lvl.CurrStage);

			// Apply the regular transformation matrix:
			p.TargetSprite.Transform = getParallaxMatrix(1, lvl.CurrStage.ZoomFactor, lvl.CurrStage.ViewCenter, lvl.CurrStage.LevelCenter, lvl.CurrStage.LevelCenter);

			

			//And particles:
			renderViewableList(lvl.CurrStage.particleList, parameters);

			//And actors:
			renderViewableList(lvl.CurrStage.actorList, parameters);
            
            //Next render items: (Rendering after actors to render Merc again. He has been placed here to draw over other actors)
			renderViewableList(lvl.CurrStage.itemList, parameters);

			p.TargetSprite.Transform = Matrix.Identity;

			//Console.WriteLine("Merc is at coordinates {0} on screen.", WorldToScreen(lvl.CurrStage.PlayerActor.Location, lvl.CurrStage));
		}
	}
}
