#include "cRenderer.hpp"

/*** Custom Header Files ***/
#include "cVideo.hpp"
#include "../UtilityEngine/cUtility.hpp"

using VideoEngine::cRenderer;
using VideoEngine::cVideo;
using UtilityEngine::cUtility;

/*static*/ cRenderer* cRenderer::sp_inst = nullptr;

/// private:
cRenderer::cRenderer()
	: mp_renderer(nullptr), mp_rendererSoftware(nullptr)
{
	Setup();
}

cRenderer::~cRenderer()
{
	CleanUp();
}

/// public:
/// Functions
/*static*/ cRenderer& cRenderer::Inst()
{
	if (sp_inst == nullptr)
		sp_inst = new cRenderer();
	return *sp_inst;
}

/*static*/ void cRenderer::Delete()
{
	delete sp_inst;
	sp_inst = nullptr;
}

const bool cRenderer::Setup()
{
	bool rtn = false;
	if (cVideo::Inst().IsInit() == false)
		cUtility::Inst().Message("Unable to setup until SDL_Video is initialized.");
	else {
		CleanUp();
		mp_renderer = cVideo::Inst().CreateRender();
		SDL_Surface* surface = SDL_GetWindowSurface(cVideo::Inst().getWindow());
		mp_rendererSoftware = SDL_CreateSoftwareRenderer(surface);
		if (mp_renderer != nullptr)
			rtn = true;
	}
	return rtn;
}

void cRenderer::CleanUp()
{
	if (mp_renderer != nullptr)
		SDL_DestroyRenderer(mp_renderer);
	if (mp_rendererSoftware != nullptr)
		SDL_DestroyRenderer(mp_rendererSoftware);

	mp_renderer = nullptr;
	mp_rendererSoftware = nullptr;
}

void cRenderer::Render( SDL_Texture* texture, SDL_Rect* srcrect, SDL_Rect* dstrect )
{
	cRenderer::Render(texture, srcrect, mp_renderer, dstrect);
}

void cRenderer::Render( SDL_Surface* surface, SDL_Rect* srcrect, SDL_Rect* dstrect )
{
	SDL_Texture* texture = SurfaceToTexture(surface);
	Render( texture, srcrect, dstrect );
	SDL_DestroyTexture(texture);
}

void cRenderer::Render( SDL_Surface* src, const SDL_Rect* srcrect, SDL_Surface* dst, SDL_Rect* dstrect )
{
	if (SDL_BlitSurface(src, srcrect, dst, dstrect) < 0)
		cUtility::Inst().Message("Unable to BlitSurface. SDL_BlitSurface():", __AT__, cUtility::eTypeSDL::SDL);
}

void cRenderer::Render( SDL_Texture* src, const SDL_Rect* srcrect, SDL_Surface* dst, SDL_Rect* dstrect )
{
	src;
	srcrect;
	dst;
	dstrect;
	//???????
}

void cRenderer::Render( SDL_Texture* src, const SDL_Rect* srcrect, SDL_Renderer* dst, SDL_Rect* dstrect )
{
	if (SDL_RenderCopy(dst, src, srcrect, dstrect) < 0)
		cUtility::Inst().Message("Unable to copy Texture to Renderer. SDL_RenderCopy():", __AT__, cUtility::eTypeSDL::SDL);
}

void cRenderer::RenderToTexture( SDL_Texture* src, SDL_Rect* srcrect, SDL_Texture* dst, SDL_Rect* dstrect )
{
	SetRenderTarget(dst);
	Render(	src, srcrect, dstrect );
	ResetRenderTarget();
}

void cRenderer::RenderDrawColor( const SDL_Colour& colour )
{
	if (SDL_SetRenderDrawColor(mp_renderer, colour.r, colour.g, colour.b, colour.a) < 0)
		cUtility::Inst().Message("Unable to set render draw colour. SDL_SetRenderDrawColor():", __AT__, cUtility::eTypeSDL::SDL);
}

void cRenderer::RenderDrawRect( const SDL_Rect& rect, SDL_Texture* dst /*= nullptr */ )
{
	SetRenderTarget(dst);
	if (SDL_RenderFillRect(mp_renderer, &rect) < 0)
		cUtility::Inst().Message("Unable to draw rectangle. SDL_RenderFillRect():", __AT__, cUtility::eTypeSDL::SDL);
	ResetRenderTarget();
}

void cRenderer::ScreenShot( const cString& filename, const cString& dir /*= ""*/ )
{
	SDL_Surface *sshot = NewSurface();
 	SDL_RenderReadPixels(getRenderer(), nullptr, SDL_PIXELFORMAT_RGBA32, sshot->pixels, sshot->pitch);
	SaveSurface(sshot, filename, dir);
	SDL_FreeSurface(sshot);
}

void cRenderer::SaveSurface( SDL_Surface* surface, const cString& fileName, const cString& dir /*= ""*/ )
{
	cString temp = dir + fileName;
	if (SDL_SaveBMP(surface, temp.c_str()) < 0)
		cUtility::Inst().Message("Unable to save Surface. SDL_SaveBMP()", __AT__, cUtility::eTypeSDL::SDL);
}

void cRenderer::SaveTexture( SDL_Texture* texture, const cString& fileName, const cString& dir /*= ""*/ )
{
	SDL_Surface* surface = TextureToSurface(texture);

	if (surface != nullptr)
		SaveSurface(surface, fileName, dir);
	else
		cUtility::Inst().Message("Unable to create Surface from Texture. TextureToSurface():", __AT__);

	SDL_FreeSurface(surface);
}

SDL_Texture* cRenderer::SurfaceToTexture( SDL_Surface* surface )
{
	SDL_Texture* rtn = SDL_CreateTextureFromSurface( mp_renderer, surface );
	if ( rtn == nullptr)
		cUtility::Inst().Message("Unable to create Texture. SDL_CreateTextureFromSurface():", __AT__, cUtility::eTypeSDL::SDL);

	return rtn;
}

SDL_Surface* cRenderer::TextureToSurface( SDL_Texture* texture )
{
	SDL_Surface* rtn = nullptr;

	int w = 0;
	int h = 0;

	if (SDL_QueryTexture(texture, nullptr, nullptr, &w, &h) < 0)
		cUtility::Inst().Message("Unable to Query Texture. SDL_QueryTexture():", __AT__, cUtility::eTypeSDL::SDL);

	rtn = NewSurface(w, h);

	SDL_Rect rec = { 0, 0, w, h };

	SetRenderTarget(texture);

	if (SDL_RenderReadPixels(mp_renderer, nullptr, SDL_PIXELFORMAT_RGBA32, rtn->pixels, rtn->pitch) < 0)
		cUtility::Inst().Message("Unable to Read Texture's Pixels. SDL_RenderReadPixels():", __AT__, cUtility::eTypeSDL::SDL);

	ResetRenderTarget();

	return rtn;
}

void cRenderer::CopyTexture( SDL_Texture* src, SDL_Texture* dst, bool software /*= false*/ )
{
	SDL_Renderer* render = mp_renderer;
	if (software == true)
		render = mp_rendererSoftware;
	if (SDL_SetRenderTarget(render, dst) < 0)
		cUtility::Inst().Message("Unable to set render target to dst. SDL_SetRenderTarget():", __AT__, cUtility::eTypeSDL::SDL);
	Render(src, nullptr, render, nullptr);
	ResetRenderTarget();
}

SDL_Surface* cRenderer::CopySurface( SDL_Surface* src )
{
	return SDL_ConvertSurface( src, src->format, src->flags );
}

SDL_Surface* cRenderer::NewSurface( long int width /*= -1*/, long int height /*= -1*/ ) const
{
	if (width < 0)
		width = cVideo::Inst().getWidth();
	if (height < 0)
		height = cVideo::Inst().getHeight();

	SDL_Surface* rtn = SDL_CreateRGBSurface(cVideo::Inst().getVideoSettings(), width, height, cVideo::Inst().getColourDepth(), cVideo::Inst().getRmask(), cVideo::Inst().getGmask(), cVideo::Inst().getBmask(), cVideo::Inst().getAmask());

	if (rtn == nullptr)
		cUtility::Inst().Message("Unable to create surface. SDL_CreateRGBSurface():", __AT__, cUtility::eTypeSDL::SDL);

	return rtn;
}

SDL_Texture* cRenderer::NewTexture( long int width, long int height, const bool copyRenderer /*= false*/, const unsigned long int access /*= SDL_TEXTUREACCESS_TARGET*/ ) const
{	
	if (width < 0)
		width = cVideo::Inst().getWidth();
	if (height < 0)
		height = cVideo::Inst().getHeight();

	SDL_Renderer* renderer = nullptr;
	if (copyRenderer == false)
		renderer = mp_renderer;
	else
		renderer = mp_rendererSoftware;
	
	SDL_Texture* rtn = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, access, width, height);
	if (rtn == nullptr)
		cUtility::Inst().Message("Unable to create texture. SDL_CreateTexture:", __AT__, cUtility::eTypeSDL::SDL);
	return rtn;
}

const bool cRenderer::AreEqual( SDL_Surface& one, SDL_Surface& two )
{
	bool rtn = false;

	int x = 0;
	int y = 0;

	if (SDL_MUSTLOCK(&one) > 0)
		SDL_LockSurface(&one);

	if (SDL_MUSTLOCK(&two) > 0)
		SDL_LockSurface(&two);

	if ((one.pitch == two.pitch) && (one.h == two.h)) {
		unsigned char *one_pixels = (unsigned char *)one.pixels;
		unsigned char *two_pixels = (unsigned char *)two.pixels;

		rtn = true;	// we are equal so far....
		
		for (x = 0; x < one.pitch; x++) {
			for (y = 0; y < one.h; y++) {
				if (one_pixels[y + x] != two_pixels[y + x]) {
					rtn = false;
					x = one.pitch + two.pitch + 1; // break out of the top for loop
					y = one.h + two.h + 1;
					break;	// we found a pixel that is not equal no need to continue
				}
			}
		}
	}

	if (SDL_MUSTLOCK(&one) > 0)
		SDL_UnlockSurface(&one);

	if (SDL_MUSTLOCK(&two) > 0)
		SDL_UnlockSurface(&two);

	return rtn;
}

void cRenderer::SetRenderTarget( SDL_Renderer* renderer, SDL_Texture* target /*= nullptr*/ )
{
	if (SDL_SetRenderTarget(renderer, target) < 0)
		cUtility::Inst().Message("Unable to set render target to dst. SDL_SetRenderTarget():", __AT__, cUtility::eTypeSDL::SDL);
}

void cRenderer::SetRenderTarget( SDL_Texture* target /*= nullptr*/ )
{
	if (SDL_SetRenderTarget(mp_renderer, target) < 0)
		cUtility::Inst().Message("Unable to set render target to dst. SDL_SetRenderTarget():", __AT__, cUtility::eTypeSDL::SDL);
}

void cRenderer::ResetRenderTarget()
{
	if (SDL_SetRenderTarget(mp_renderer, nullptr) < 0)
		cUtility::Inst().Message("Unable to set render target to mp_rend. SDL_SetRenderTarget():", __AT__, cUtility::eTypeSDL::SDL);
}

SDL_Renderer* cRenderer::getRenderer()
{
	return mp_renderer;
}

SDL_Renderer* cRenderer::getRendererCopy()
{
	return mp_rendererSoftware;
}