// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "Direct3DSwapChain9.hpp"

#include "Direct3DDevice9.hpp"
#include "Renderer.hpp"
#include "Timer.hpp"
#include "Resource.hpp"
#include "Configurator.hpp"
#include "Debug.hpp"

#include "FrameBufferDD.hpp"
#include "FrameBufferGDI.hpp"

namespace D3D9
{
	Direct3DSwapChain9::Direct3DSwapChain9(Direct3DDevice9 *device, D3DPRESENT_PARAMETERS *presentParameters) : device(device), presentParameters(*presentParameters)
	{
		frameBuffer = 0;

		for(int i = 0; i < 3; i++)
		{
			backBuffer[i] = 0;
		}

		reset(presentParameters);
	}

	Direct3DSwapChain9::~Direct3DSwapChain9()
	{
		release();
	}

	long Direct3DSwapChain9::QueryInterface(const IID &iid, void **object)
	{
		CriticalSection cs(device);

		TRACE("");

		if(iid == IID_IDirect3DSwapChain9 ||
		   iid == IID_IUnknown)
		{
			AddRef();
			*object = this;

			return S_OK;
		}

		*object = 0;

		return NOINTERFACE(iid);
	}

	unsigned long Direct3DSwapChain9::AddRef()
	{
		TRACE("");

		return Unknown::AddRef();
	}

	unsigned long Direct3DSwapChain9::Release()
	{
		TRACE("");

		return Unknown::Release();
	}

	long Direct3DSwapChain9::Present(const RECT *sourceRect, const RECT *destRect, HWND destWindowOverride, const RGNDATA *dirtyRegion, unsigned long flags)
	{
		CriticalSection cs(device);

		TRACE("");

		#if PERF_PROFILE
			profiler.nextFrame();
		#endif

		#if PERF_HUD
			sw::Renderer *renderer = device->renderer;

			static int64_t frame = sw::Timer::ticks();

			int64_t frameTime = sw::Timer::ticks() - frame;
			frame = sw::Timer::ticks();

			if(frameTime > 0)
			{
				unsigned int *frameBuffer = (unsigned int*)lockBackBuffer(0);   // FIXME: Don't assume A8R8G8B8 mode
				unsigned int stride = backBuffer[0]->getInternalPitchP();

				int thread;
				for(thread = 0; thread < renderer->getThreadCount(); thread++)
				{
					int64_t drawTime = renderer->getVertexTime(thread) + renderer->getSetupTime(thread) + renderer->getPixelTime(thread);

					int vertexPercentage = sw::clamp((int)(100 * renderer->getVertexTime(thread) / frameTime), 0, 100);
					int setupPercentage = sw::clamp((int)(100 * renderer->getSetupTime(thread) / frameTime), 0, 100);
					int pixelPercentage = sw::clamp((int)(100 * renderer->getPixelTime(thread) / frameTime), 0, 100);

					for(int i = 0; i < 100; i++)
					{
						frameBuffer[thread * stride + i] = 0x00000000;
					}

					unsigned int *buffer = frameBuffer;

					for(int i = 0; i < vertexPercentage; i++)
					{
						buffer[thread * stride] = 0x000000FF;
						buffer++;
					}

					for(int i = 0; i < setupPercentage; i++)
					{
						buffer[thread * stride] = 0x0000FF00;
						buffer++;
					}

					for(int i = 0; i < pixelPercentage; i++)
					{
						buffer[thread * stride] = 0x00FF0000;
						buffer++;
					}

					frameBuffer[thread * stride + 100] = 0x00FFFFFF;
				}

				for(int i = 0; i <= 100; i++)
				{
					frameBuffer[thread * stride + i] = 0x00FFFFFF;
				}

				unlockBackBuffer(0);
			}

			renderer->resetTimers();
		#endif

		HWND window = destWindowOverride ? destWindowOverride : presentParameters.hDeviceWindow;

		POINT point;
		GetCursorPos(&point);
		ScreenToClient(window, &point);

		frameBuffer->setCursorPosition(point.x, point.y);

		if(!sourceRect && !destRect)   // FIXME: More cases?
		{
			frameBuffer->flip(window, backBuffer[0]);
		}
		else   // FIXME: Check for SWAPEFFECT_COPY
		{
			sw::Rect sRect(0, 0, 0, 0);
			sw::Rect dRect(0, 0, 0, 0);

			if(sourceRect)
			{
				sRect.x0 = sourceRect->left;
				sRect.y0 = sourceRect->top;
				sRect.x1 = sourceRect->right;
				sRect.y1 = sourceRect->bottom;
			}

			if(destRect)
			{
				dRect.x0 = destRect->left;
				dRect.y0 = destRect->top;
				dRect.x1 = destRect->right;
				dRect.y1 = destRect->bottom;
			}

			frameBuffer->blit(window, backBuffer[0], sourceRect ? &sRect : nullptr, destRect ? &dRect : nullptr);
		}

		return D3D_OK;
	}

	long Direct3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *destSurface)
	{
		CriticalSection cs(device);

		TRACE("");

		if(!destSurface)
		{
			return INVALIDCALL();
		}

		sw::Surface *dest = static_cast<Direct3DSurface9*>(destSurface);
		void *buffer = dest->lockExternal(0, 0, 0, sw::LOCK_WRITEONLY, sw::PRIVATE);

		frameBuffer->screenshot(buffer);

		dest->unlockExternal();

		return D3D_OK;
	}

	long Direct3DSwapChain9::GetBackBuffer(unsigned int index, D3DBACKBUFFER_TYPE type, IDirect3DSurface9 **backBuffer)
	{
		CriticalSection cs(device);

		TRACE("");

		if(!backBuffer/* || type != D3DBACKBUFFER_TYPE_MONO*/)
		{
			return INVALIDCALL();
		}

		*backBuffer = 0;

		if(index >= 3 || this->backBuffer[index] == 0)
		{
			return INVALIDCALL();
		}

		*backBuffer = this->backBuffer[index];
		this->backBuffer[index]->AddRef();

		return D3D_OK;
	}

	long Direct3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *rasterStatus)
	{
		CriticalSection cs(device);

		TRACE("");

		if(!rasterStatus)
		{
			return INVALIDCALL();
		}

		bool inVerticalBlank;
		unsigned int scanline;
		bool supported = frameBuffer->getScanline(inVerticalBlank, scanline);

		if(supported)
		{
			rasterStatus->InVBlank = inVerticalBlank;
			rasterStatus->ScanLine = scanline;
		}
		else
		{
			return INVALIDCALL();
		}

		return D3D_OK;
	}

	long Direct3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *displayMode)
	{
		CriticalSection cs(device);

		TRACE("");

		if(!displayMode)
		{
			return INVALIDCALL();
		}

		device->getAdapterDisplayMode(D3DADAPTER_DEFAULT, displayMode);

		return D3D_OK;
	}

	long Direct3DSwapChain9::GetDevice(IDirect3DDevice9 **device)
	{
		CriticalSection cs(this->device);

		TRACE("");

		if(!device)
		{
			return INVALIDCALL();
		}

		this->device->AddRef();
		*device = this->device;

		return D3D_OK;
	}

	long Direct3DSwapChain9::GetPresentParameters(D3DPRESENT_PARAMETERS *presentParameters)
	{
		CriticalSection cs(device);

		TRACE("");

		if(!presentParameters)
		{
			return INVALIDCALL();
		}

		*presentParameters = this->presentParameters;

		return D3D_OK;
	}

	void Direct3DSwapChain9::reset(D3DPRESENT_PARAMETERS *presentParameters)
	{
		release();

		ASSERT(presentParameters->BackBufferCount <= 3);   // Maximum of three back buffers

		if(presentParameters->BackBufferCount == 0)
		{
			presentParameters->BackBufferCount = 1;
		}

		if(presentParameters->BackBufferFormat == D3DFMT_UNKNOWN)
		{
			D3DDISPLAYMODE displayMode;
			GetDisplayMode(&displayMode);

			presentParameters->BackBufferFormat = displayMode.Format;
		}

		D3DDEVICE_CREATION_PARAMETERS creationParameters;
		device->GetCreationParameters(&creationParameters);

		HWND windowHandle = presentParameters->hDeviceWindow ? presentParameters->hDeviceWindow : creationParameters.hFocusWindow;

		if(presentParameters->Windowed && (presentParameters->BackBufferHeight == 0 || presentParameters->BackBufferWidth == 0))
		{
			RECT rectangle;
			GetClientRect(windowHandle, &rectangle);

			presentParameters->BackBufferWidth = rectangle.right - rectangle.left;
			presentParameters->BackBufferHeight = rectangle.bottom - rectangle.top;
		}

		frameBuffer = createFrameBufferWin(windowHandle, presentParameters->BackBufferWidth, presentParameters->BackBufferHeight, presentParameters->Windowed == FALSE, true);

		lockable = presentParameters->Flags & D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;

		backBuffer[0] = 0;
		backBuffer[1] = 0;
		backBuffer[2] = 0;

		for(int i = 0; i < (int)presentParameters->BackBufferCount; i++)
		{
			backBuffer[i] = new Direct3DSurface9(device, this, presentParameters->BackBufferWidth, presentParameters->BackBufferHeight, presentParameters->BackBufferFormat, D3DPOOL_DEFAULT, presentParameters->MultiSampleType, presentParameters->MultiSampleQuality, lockable, D3DUSAGE_RENDERTARGET);
			backBuffer[i]->bind();
		}

		this->presentParameters = *presentParameters;
	}

	void Direct3DSwapChain9::release()
	{
		delete frameBuffer;
		frameBuffer = 0;

		for(int i = 0; i < 3; i++)
		{
			if(backBuffer[i])
			{
				backBuffer[i]->unbind();
				backBuffer[i] = 0;
			}
		}
	}

	void Direct3DSwapChain9::setGammaRamp(sw::GammaRamp *gammaRamp, bool calibrate)
	{
		frameBuffer->setGammaRamp(gammaRamp, calibrate);
	}

	void Direct3DSwapChain9::getGammaRamp(sw::GammaRamp *gammaRamp)
	{
		frameBuffer->getGammaRamp(gammaRamp);
	}

	void *Direct3DSwapChain9::lockBackBuffer(int index)
	{
		return backBuffer[index]->lockInternal(0, 0, 0, sw::LOCK_READWRITE, sw::PUBLIC);   // FIXME: External
	}

	void Direct3DSwapChain9::unlockBackBuffer(int index)
	{
		backBuffer[index]->unlockInternal();   // FIXME: External
	}
}