#region Copyright & License Information
/*
 * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation. For more information,
 * see COPYING.
 */
#endregion

using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using OpenTK.Graphics.OpenGL;

namespace OpenRA.Renderer.Sdl2
{
	public sealed class FrameBuffer : IFrameBuffer
	{
		Texture texture;
		Size size;
		int framebuffer, depth;
		bool disposed;

		public FrameBuffer(Size size)
		{
			this.size = size;
			if (!Exts.IsPowerOf2(size.Width) || !Exts.IsPowerOf2(size.Height))
				throw new InvalidDataException("Frame buffer size ({0}x{1}) must be a power of two".F(size.Width, size.Height));

			GL.Ext.GenFramebuffers(1, out framebuffer);
			ErrorHandler.CheckGlError();
			GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, framebuffer);
			ErrorHandler.CheckGlError();

			// Color
			texture = new Texture();
			texture.SetEmpty(size.Width, size.Height);
			GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, texture.ID, 0);
			ErrorHandler.CheckGlError();

			// Depth
			GL.Ext.GenRenderbuffers(1, out depth);
			ErrorHandler.CheckGlError();

			GL.Ext.BindRenderbuffer(RenderbufferTarget.RenderbufferExt, depth);
			ErrorHandler.CheckGlError();

			GL.Ext.RenderbufferStorage(RenderbufferTarget.RenderbufferExt, (RenderbufferStorage)All.DepthComponent, size.Width, size.Height);
			ErrorHandler.CheckGlError();

			GL.Ext.FramebufferRenderbuffer(FramebufferTarget.FramebufferExt, FramebufferAttachment.DepthAttachmentExt, RenderbufferTarget.RenderbufferExt, depth);
			ErrorHandler.CheckGlError();

			// Test for completeness
			var status = GL.Ext.CheckFramebufferStatus(FramebufferTarget.FramebufferExt);
			if (status != FramebufferErrorCode.FramebufferCompleteExt)
			{
				var error = "Error creating framebuffer: {0}\n{1}".F(status, new StackTrace());
				ErrorHandler.WriteGraphicsLog(error);
				throw new InvalidOperationException("OpenGL Error: See graphics.log for details.");
			}

			// Restore default buffer
			GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
			ErrorHandler.CheckGlError();
		}

		static int[] ViewportRectangle()
		{
			var v = new int[4];
			unsafe
			{
				fixed (int* ptr = &v[0])
				{
					GL.GetInteger(GetPName.Viewport, ptr);
				}
			}

			ErrorHandler.CheckGlError();
			return v;
		}

		int[] cv = new int[4];
		public void Bind()
		{
			// Cache viewport rect to restore when unbinding
			cv = ViewportRectangle();

			GL.Flush();
			ErrorHandler.CheckGlError();
			GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, framebuffer);
			ErrorHandler.CheckGlError();
			GL.Viewport(0, 0, size.Width, size.Height);
			ErrorHandler.CheckGlError();
			GL.ClearColor(0, 0, 0, 0);
			ErrorHandler.CheckGlError();
			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
			ErrorHandler.CheckGlError();
		}

		public void Unbind()
		{
			GL.Flush();
			ErrorHandler.CheckGlError();
			GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
			ErrorHandler.CheckGlError();
			GL.Viewport(cv[0], cv[1], cv[2], cv[3]);
			ErrorHandler.CheckGlError();
		}

		public ITexture Texture { get { return texture; } }

		~FrameBuffer()
		{
			Game.RunAfterTick(() => Dispose(false));
		}

		public void Dispose()
		{
			Game.RunAfterTick(() => Dispose(true));
			GC.SuppressFinalize(this);
		}

		void Dispose(bool disposing)
		{
			if (disposed)
				return;
			disposed = true;
			if (disposing)
				texture.Dispose();
			GL.Ext.DeleteFramebuffers(1, ref framebuffer);
			ErrorHandler.CheckGlError();
			GL.Ext.DeleteRenderbuffers(1, ref depth);
			ErrorHandler.CheckGlError();
		}
	}
}
