#pragma once

#pragma warning(push)
#pragma warning(disable:4250)

#include "../GGE/Engine/GGEMain.h"
#include "../GGE/Resource/Image.h"
#include <cstring>
#include "../GGE/Utils/Point2D.h"

#define CANVAS_EXISTS


namespace graphics {

	enum MarkerStyle {
		None,
		Normal,
		Bold,
		Cross,
		Square,
		T

	};

	////Represents a drawable canvas. Being an image resource, objects
	/// of this type can directly be drawn as raw graphics or can be
	/// prepared to increase performance.
	template<class T_>
	class basic_Canvas : public gge::resource::Image {
	public:
		////Creates a new canvas, specifying the size of it
		basic_Canvas(int w,int h) : 
		  Image(), DrawColor(gge::graphics::RGBint(0xff000000)), PointMarkers(Normal)
		{
			ImageData::Resize(w, h, gge::graphics::ColorMode::ARGB);
			data.Resize(w*h);
			LeaveData=true;
			isLoaded=true;
		}

		////Creates a new canvas
		basic_Canvas() : 
			Image(), DrawColor(gge::graphics::RGBint(0xff000000)), PointMarkers(Normal)
		{
			Width=0;
			Height=0;
			LeaveData=true;
			isLoaded=true;
		}

		////Creates a new canvas, specifying the size of it
		basic_Canvas(Image& image) : 
			Image(), DrawColor(gge::graphics::RGBint(0xff000000)), PointMarkers(Normal)
			
		{
			Width=image.Width;
			Height=image.Height;
			LeaveData=true;
			isLoaded=true;
			ImageData::Data=image.Data;
			Mode=image.Mode;

			data.Resize(Width*Height);
		}

		virtual bool isYReversed() { return false; }

		template<class U_>
		void DrawTo(basic_Canvas<U_> &canvas, gge::utils::Point2D &point) {
			DrawTo(canvas, point.x,point.y);
		}

		template<class U_>
		void DrawTo(basic_Canvas<U_> &canvas, int x, int y) {
			int minx, maxx;
			int miny, maxy;

			if(x<0)
				minx=-x;
			else
				minx=0;

			if(x+Width > canvas.Width)
				maxx=canvas.Width-x;
			else
				maxx=Width;

			if(y<0)
				miny=-y;
			else
				miny=0;

			if(y+Height > canvas.Height)
				maxy=canvas.Height-y;
			else
				maxy=Height;

			for(int yy=miny;yy<maxy;yy++)
				for(int xx=minx;xx<maxx;xx++) {
					canvas.Set(xx+x,yy+y, Get(xx,yy));
				}
		}

		using Image::Draw;
		
		////Draws this canvas on to another, using the given size
		template<class U_>
		void Draw(basic_Canvas<U_> &canvas, gge::utils::Point point, int w, int h) {
			Draw(canvas, point.x,point.y, w,h);
		}

		////Draws this canvas on to another, using the given size
		template<class U_>
		void Draw(basic_Canvas<U_> &canvas, int x, int y,int w,int h) {
			int minx, maxx;
			int miny, maxy;

			if(w>Width)
				std::logic_error("Requested width is larger than canvas width");
			if(h>Height)
				std::logic_error("Requested height is larger than canvas height");

			if(x<0)
				minx=-x;
			else
				minx=0;

			if(x+w > canvas.Width)
				maxx=canvas.Width-x;
			else
				maxx=w;

			if(y<0)
				miny=-y;
			else
				miny=0;

			if(y+h > canvas.Height)
				maxy=canvas.Height-y;
			else
				maxy=h;

			for(int yy=miny;yy<maxy;yy++)
				for(int xx=minx;xx<maxx;xx++)
					canvas.Set(xx+x,yy+y, Get(xx,yy));
		}

		////Changes the canvas size. After this operation
		/// canvas contents will become invalid
		virtual void Resize(int w, int h) {
			ImageData::Resize(w, h, gge::graphics::ColorMode::ARGB);
			data.Resize(w*h);
		}

		////Empties the canvas, rendering it completely transparent
		void Empty() {
			memset(ImageData::Data,0,Width*Height*4);
			memset(data.GetBuffer(),0,Width*Height*sizeof(T_));
		}

		////Clears the canvas, making it white (?)
		void Clear() {
			memset(ImageData::Data,0xff,Width*Height*4);
			memset(data.GetBuffer(),0xff,Width*Height*sizeof(T_));
		}

		////Clears the canvas, making it white (?)
		void Paint(T_ col) {
			for(int y=0;y<GetHeight();y++) {
				for(int x=0;x<GetWidth();x++) {
					DirectSet(x,y,col);
				}
			}
		}

		////Obtains data array of the canvas
		T_ *Data() {
			return (T_ *)data;
		}

		basic_Canvas &Duplicate() {
			basic_Canvas<T_> *canvas=new basic_Canvas<T_>(Width, Height);

			memcpy(canvas->GetBuffer().GetBuffer(), GetBuffer().GetBuffer(), GetBuffer().GetSize()*sizeof(T_));
			memcpy(canvas->GetImageBuffer().GetBuffer(), GetImageBuffer().GetBuffer(), GetImageBuffer().GetSize());

			return *canvas;
		}

		operator gge::utils::ManagedBuffer<T_>() {
			return GetBuffer();
		}

		/*operator ManagedBuffer<RGBint>() {
			return GetImageBuffer();
		}*/

		operator T_*() {
			return GetBuffer().GetBuffer();
		}

		////Takes a pixel from the data array to be read or changed.
		/// This method returns nearest pixel if the coordinates are
		/// out of bounds
		virtual T_ &operator()(int x, int y) {
			if(x<0) x=0;
			else if(x>=Width) x=Width-1;
			
			if(y<0) y=0;
			else if(y>=Height) y=Height-1;

			return data[x+y*Width];
		}

		////Takes a pixel from the data array to be read or changed.
		/// This method returns nearest pixel if the coordinates are
		/// out of bounds
		virtual T_ &operator()(gge::utils::Point &p) {
			if(p.x<0) p.x=0;
			else if(p.x>=Width) p.x=Width-1;
			
			if(p.y<0) p.y=0;
			else if(p.y>=Height) p.y=Height-1;

			return data[p.x+p.y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual T_ Get(int x, int y) {
			if(x<0) return 0;
			if(x>=Width) return 0;
			
			if(y<0) return 0;
			if(y>=Height) return 0;

			return data[x+y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// it may cause a crash
		virtual T_ DirectGet(int x, int y) {
			return data[x+y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual T_ Get(gge::utils::Point p) {
			if(p.x<0) return 0;
			if(p.x>=Width) return 0;
			
			if(p.y<0) return 0;
			if(p.y>=Height) return 0;

			return data[p.x+p.y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual T_ GetNearest(int x, int y) {
			if(x<0) x=0;
			else if(x>=Width) x=Width-1;
			
			if(y<0) y=0;
			else if(y>=Height) y=Height-1;

			return data[x+y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual T_ GetNearest(gge::utils::Point p) {
			if(p.x<0) p.x=0;
			else if(p.x>=Width) p.x=Width-1;
			
			if(p.y<0) p.y=0;
			else if(p.y>=Height) p.y=Height-1;

			return data[p.x+p.y*Width];
		}


		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Set(int x, int y, T_ col) {
			if(x<0) return;
			if(x>=Width) return;
			
			if(y<0) return;
			if(y>=Height) return;

			data[x+y*Width]=col;
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void DirectSet(int x, int y, T_ col) {
			data[x+y*Width]=col;
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Set(gge::utils::Point p, T_ col) {
			if(p.x<0) return;
			if(p.x>=Width) return;
			
			if(p.y<0) return;
			if(p.y>=Height) return;

			data[p.x+p.y*Width]=col;
		}



		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(int x, int y, T_ col) {
			if(x<0) return;
			if(x>=Width) return;

			if(y<0) return;
			if(y>=Height) return;

			data[x+y*Width]=col;
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(gge::utils::Point p, T_ col) {
			Blend(p.x,p.y,col);
		}


		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(int x, int y, T_ col, float alpha) {
			if(x<0) return;
			if(x>=Width) return;

			if(y<0) return;
			if(y>=Height) return;

			data[x+y*Width]=col*alpha+data[x+y*Width]*(1-alpha);
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(gge::utils::Point p, T_ col, float alpha) {
			Blend(p.x,p.y,col,alpha);
		}

		////Copies another canvas to this one. After copy operation,
		/// canvas data moves together, however, if one of the canvas
		/// is resized other become corrupt
		basic_Canvas &operator = (basic_Canvas &canvas) {
			Width=canvas.Width;
			Height=canvas.Height;
			ImageData::Data=canvas.GetImageBuffer();
			Mode=canvas.Mode;
			data=canvas.data;
			data++;

			return *this;
		}

		////Copies another canvas to this one. After copy operation,
		/// canvas data moves together, however, if one of the canvas
		/// is resized other become corrupt
		basic_Canvas &operator = (Image &image) {
			Width=image.Width;
			Height=image.Height;
			ImageData::Data=image.Data;
			Mode=image.Mode;

			return *this;
		}

		////Returns the underlying data storage buffer
		gge::utils::CastableManagedBuffer<T_> &GetBuffer() {
			return data;
		}

		////Returns the underlying data storage buffer
		gge::utils::CastableManagedBuffer<gge::Byte> &GetImageBuffer() {
			return ImageData::Data;
		}

		void Commit() {
			//this should work since underlying image is always
			//RGBA color mode which fits to RGBint
			gge::graphics::RGBint *imagedata=(gge::graphics::RGBint*)(gge::Byte*)GetImageBuffer();

			for(int i=0;i<Width*Height;i++)
				imagedata[i]=data[i];				
		}

		void Retrieve() {
			//this should work since underlying image is always
			//RGBA color mode which fits to RGBint
			gge::graphics::RGBint *imagedata=(gge::graphics::RGBint*)(gge::Byte*)GetImageBuffer();

			for(int i=0;i<Width*Height;i++)
				data[i]=imagedata[i];				
		}

		void Prepare() {
			Image::Prepare(gge::Main, dummy);
		}

		T_ DrawColor;
		MarkerStyle PointMarkers;

	protected:
		gge::utils::CastableManagedBuffer<T_> data;
		gge::resource::File dummy;
	};

	
	template<>
	class basic_Canvas<gge::graphics::RGBint> : public gge::resource::Image {
	public:
		////Creates a new canvas, specifying the size of it
		basic_Canvas(int w,int h) : 
		  Image(), DrawColor(gge::graphics::RGBint(0xff000000)), PointMarkers(Normal)
		{
			ImageData::Resize(w, h, gge::graphics::ColorMode::ARGB);
			LeaveData=true;
			isLoaded=true;
			data=ImageData::Data.Cast<gge::graphics::RGBint>();
		}

		////Creates a new canvas
		basic_Canvas() : 
			Image(), DrawColor(gge::graphics::RGBint(0xff000000)), PointMarkers(Normal)
		{
			Width=0;
			Height=0;
			LeaveData=true;
			isLoaded=true;
			data=ImageData::Data.Cast<gge::graphics::RGBint>();
		}

		////Creates a new canvas, specifying the size of it
		basic_Canvas(Image& image) : 
			Image(), DrawColor(gge::graphics::RGBint(0xff000000)), PointMarkers(Normal)
			
		{
			Width=image.GetWidth();
			Height=image.GetHeight();
			LeaveData=true;
			isLoaded=true;
			ImageData::Data=image.getdata();
			Mode=image.GetMode();
			data=ImageData::Data.Cast<gge::graphics::RGBint>();
		}

		virtual bool isYReversed() { return false; }

		template<class U_>
		void DrawTo(basic_Canvas<U_> &canvas, gge::utils::Point2D &point) {
			DrawTo(canvas, point.x,point.y);
		}

		template<class U_>
		void DrawTo(basic_Canvas<U_> &canvas, int x, int y) {
			int minx, maxx;
			int miny, maxy;

			if(x<0)
				minx=-x;
			else
				minx=0;

			if(x+Width > canvas.Width)
				maxx=canvas.Width-x;
			else
				maxx=Width;

			if(y<0)
				miny=-y;
			else
				miny=0;

			if(y+Height > canvas.Height)
				maxy=canvas.Height-y;
			else
				maxy=Height;

			for(int yy=miny;yy<maxy;yy++)
				for(int xx=minx;xx<maxx;xx++) {
					canvas.Set(xx+x,yy+y, Get(xx,yy));
				}
		}
		
		////Draws this canvas on to another, using the given size
		template<class U_>
		void DrawTo(basic_Canvas<U_> &canvas, gge::utils::Point point, int w, int h) {
			DrawTo(canvas, point.x,point.y, w,h);
		}

		////Draws this canvas on to another, using the given size
		template<class U_>
		void DrawTo(basic_Canvas<U_> &canvas, int x, int y,int w,int h) {
			int minx, maxx;
			int miny, maxy;

			if(w>Width)
				std::logic_error("Requested width is larger than canvas width");
			if(h>Height)
				std::logic_error("Requested height is larger than canvas height");

			if(x<0)
				minx=-x;
			else
				minx=0;

			if(x+w > canvas.Width)
				maxx=canvas.Width-x;
			else
				maxx=w;

			if(y<0)
				miny=-y;
			else
				miny=0;

			if(y+h > canvas.Height)
				maxy=canvas.Height-y;
			else
				maxy=h;

			for(int yy=miny;yy<maxy;yy++)
				for(int xx=minx;xx<maxx;xx++)
					canvas.Set(xx+x,yy+y, Get(xx,yy));
		}

		////Changes the canvas size. After this operation
		/// canvas contents will become invalid
		virtual void Resize(int w, int h) {
			ImageData::Resize(w, h, gge::graphics::ColorMode::ARGB);
		}

		////Empties the canvas, rendering it completely transparent
		void Empty() {
			memset(ImageData::Data,0,Width*Height*4);
		}

		////Clears the canvas, making it white (?)
		void Clear() {
			memset(ImageData::Data,0xff,Width*Height*4);
		}

		////Clears the canvas, making it white (?)
		void Paint(gge::graphics::RGBint col) {
			for(int i=0;i<ImageData::Data.GetSize();i+=4) {
				ImageData::Data[i+0]=col.r;
				ImageData::Data[i+1]=col.g;
				ImageData::Data[i+2]=col.b;
				ImageData::Data[i+3]=col.a;
			}
		}

		////Obtains data array of the canvas
		gge::graphics::RGBint *Data() {
			return (gge::graphics::RGBint *)(gge::Byte*)ImageData::Data;
		}

		basic_Canvas &Duplicate() {
			basic_Canvas<gge::graphics::RGBint> *canvas=new basic_Canvas<gge::graphics::RGBint>(Width, Height);

			memcpy(canvas->GetImageBuffer().GetBuffer(), GetImageBuffer().GetBuffer(), GetImageBuffer().GetSize());

			return *canvas;
		}

		/*operator ManagedBuffer<RGBint>() {
			return GetBuffer();
		}*/

		/*operator ManagedBuffer<RGBint>() {
			return GetImageBuffer();
		}*/

		operator gge::graphics::RGBint*() {
			return (gge::graphics::RGBint*)(gge::Byte*)ImageData::Data;
		}

		////Takes a pixel from the data array to be read or changed.
		/// This method returns nearest pixel if the coordinates are
		/// out of bounds
		virtual gge::graphics::RGBint &operator()(int x, int y) {
			if(x<0) x=0;
			else if(x>=Width) x=Width-1;
			
			if(y<0) y=0;
			else if(y>=Height) y=Height-1;

			return data[x+y*Width];
		}

		////Takes a pixel from the data array to be read or changed.
		/// This method returns nearest pixel if the coordinates are
		/// out of bounds
		virtual gge::graphics::RGBint &operator()(gge::utils::Point &p) {
			if(p.x<0) p.x=0;
			else if(p.x>=Width) p.x=Width-1;
			
			if(p.y<0) p.y=0;
			else if(p.y>=Height) p.y=Height-1;

			return data[p.x+p.y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual gge::graphics::RGBint Get(int x, int y) {
			if(x<0) return 0;
			if(x>=Width) return 0;
			
			if(y<0) return 0;
			if(y>=Height) return 0;

			return data[x+y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual gge::graphics::RGBint DirectGet(int x, int y) {
			return data[x+y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual gge::graphics::RGBint Get(gge::utils::Point p) {
			if(p.x<0) return 0;
			if(p.x>=Width) return 0;
			
			if(p.y<0) return 0;
			if(p.y>=Height) return 0;

			return data[p.x+p.y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual gge::graphics::RGBint GetNearest(int x, int y) {
			if(x<0) x=0;
			else if(x>=Width) x=Width-1;
			
			if(y<0) y=0;
			else if(y>=Height) y=Height-1;

			return data[x+y*Width];
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// transparent black is returned.
		virtual gge::graphics::RGBint GetNearest(gge::utils::Point p) {
			if(p.x<0) p.x=0;
			else if(p.x>=Width) p.x=Width-1;
			
			if(p.y<0) p.y=0;
			else if(p.y>=Height) p.y=Height-1;

			return data[p.x+p.y*Width];
		}


		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Set(int x, int y, gge::graphics::RGBint col) {
			if(x<0) return;
			if(x>=Width) return;
			
			if(y<0) return;
			if(y>=Height) return;

			data[x+y*Width]=col;
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void DirectSet(int x, int y, gge::graphics::RGBint col) {
			data[x+y*Width]=col;
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Set(gge::utils::Point p, gge::graphics::RGBint col) {
			if(p.x<0) return;
			if(p.x>=Width) return;
			
			if(p.y<0) return;
			if(p.y>=Height) return;

			data[p.x+p.y*Width]=col;
		}



		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(int x, int y, gge::graphics::RGBint col) {
			if(x<0) return;
			if(x>=Width) return;
			
			if(y<0) return;
			if(y>=Height) return;

			data[x+y*Width].Blend(col);
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(gge::utils::Point p, gge::graphics::RGBint col) {
			Blend(p.x,p.y,col);
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(int x, int y, gge::graphics::RGBint col, float alpha) {
			if(x<0) return;
			if(x>=Width) return;

			if(y<0) return;
			if(y>=Height) return;

			col.a=(unsigned char)(col.a*alpha);

			data[x+y*Width].Blend(col);
		}

		////Returns the value of a pixel. If that pixel does not exists
		/// nothing is done
		virtual void Blend(gge::utils::Point p, gge::graphics::RGBint col, float alpha) {
			Blend(p.x,p.y,col,alpha);
		}

		////Copies another canvas to this one. After copy operation,
		/// canvas data moves together, however, if one of the canvas
		/// is resized other become corrupt
		basic_Canvas &operator = (basic_Canvas &canvas) {
			Width=canvas.Width;
			Height=canvas.Height;
			ImageData::Data=canvas.GetImageBuffer();
			Mode=canvas.Mode;

			return *this;
		}

		////Copies another canvas to this one. After copy operation,
		/// canvas data moves together, however, if one of the canvas
		/// is resized other become corrupt
		basic_Canvas &operator = (Image &image) {
			Width=image.GetWidth();
			Height=image.GetHeight();
			ImageData::Data=image.getdata();
			Mode=image.GetMode();

			return *this;
		}

		////Returns the underlying data storage buffer
		gge::utils::CastableManagedBuffer<gge::graphics::RGBint> GetBuffer() {
			return ImageData::Data.Cast<gge::graphics::RGBint>();
		}

		////Returns the underlying data storage buffer
		gge::utils::CastableManagedBuffer<gge::Byte> &GetImageBuffer() {
			return ImageData::Data;
		}

		void Commit() {
		}

		void Retrieve() {
		}

		void Prepare() {
			Image::Prepare(gge::Main, dummy);
		}

		gge::graphics::RGBint DrawColor;
		MarkerStyle PointMarkers;

	protected:
		gge::utils::CastableManagedBuffer<gge::graphics::RGBint> data;
		gge::resource::File dummy;

	};

	template <class T_, class U_>
	inline basic_Canvas<T_> &operator << (basic_Canvas<T_> &target, basic_Canvas<U_> &source) {
		source.Draw(target, 0,0);

		return target;
	}

	template <class T_, class U_>
	inline basic_Canvas<T_> &operator >> (basic_Canvas<T_> &source, basic_Canvas<U_> &target) {
		source.Draw(target, 0,0);

		return source;
	}

	typedef basic_Canvas<gge::graphics::RGBint> Canvas;
}

#pragma warning(pop)
