// 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.

// Surface.cpp: Implements the Surface class, representing a drawing surface
// such as the client area of a window, including any back buffers.

#include "Surface.h"

#include "main.h"
#include "Display.h"
#include "Image.hpp"
#include "Context.h"
#include "common/debug.h"
#include "Main/FrameBuffer.hpp"

#if defined(_WIN32)
#include <tchar.h>
#endif

#include <algorithm>

namespace gl
{

Surface::Surface(Display *display, NativeWindowType window)
	: mDisplay(display), mWindow(window)
{
	frameBuffer = 0;
	backBuffer = 0;

	mDepthStencil = nullptr;
	mTextureFormat = GL_NONE;
	mTextureTarget = GL_NONE;

	mSwapInterval = -1;
	setSwapInterval(1);
}

Surface::Surface(Display *display, GLint width, GLint height, GLenum textureFormat, GLenum textureType)
	: mDisplay(display), mWindow(nullptr), mWidth(width), mHeight(height)
{
	frameBuffer = 0;
	backBuffer = 0;

	mDepthStencil = nullptr;
	mWindowSubclassed = false;
	mTextureFormat = textureFormat;
	mTextureTarget = textureType;

	mSwapInterval = -1;
	setSwapInterval(1);
}

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

bool Surface::initialize()
{
	ASSERT(!frameBuffer && !backBuffer && !mDepthStencil);

	return reset();
}

void Surface::release()
{
	if(mDepthStencil)
	{
		mDepthStencil->release();
		mDepthStencil = nullptr;
	}

	if(backBuffer)
	{
		backBuffer->release();
		backBuffer = 0;
	}

	delete frameBuffer;
	frameBuffer = 0;
}

bool Surface::reset()
{
	if(!mWindow)
	{
		return reset(mWidth, mHeight);
	}

	// FIXME: Wrap into an abstract Window class
	#if defined(_WIN32)
		RECT windowRect;
		GetClientRect(mWindow, &windowRect);

		return reset(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);
	#else
		XWindowAttributes windowAttributes;
		XGetWindowAttributes(mDisplay->getNativeDisplay(), mWindow, &windowAttributes);

		return reset(windowAttributes.width, windowAttributes.height);
	#endif
}

bool Surface::reset(int backBufferWidth, int backBufferHeight)
{
	release();

	if(mWindow)
	{
		frameBuffer = ::createFrameBuffer(mDisplay->getNativeDisplay(), mWindow, backBufferWidth, backBufferHeight);

		if(!frameBuffer)
		{
			ERR("Could not create frame buffer");
			release();
			return error(GL_OUT_OF_MEMORY, false);
		}
	}

	backBuffer = new Image(0, backBufferWidth, backBufferHeight, GL_RGB, GL_UNSIGNED_BYTE);

	if(!backBuffer)
	{
		ERR("Could not create back buffer");
		release();
		return error(GL_OUT_OF_MEMORY, false);
	}

	if(true)   // Always provide a depth/stencil buffer
	{
		mDepthStencil = new Image(0, backBufferWidth, backBufferHeight, sw::FORMAT_D24S8, 1, false, true);

		if(!mDepthStencil)
		{
			ERR("Could not create depth/stencil buffer for surface");
			release();
			return error(GL_OUT_OF_MEMORY, false);
		}
	}

	mWidth = backBufferWidth;
	mHeight = backBufferHeight;

	return true;
}

void Surface::swap()
{
	if(backBuffer)
	{
		frameBuffer->flip(backBuffer);

		checkForResize();
	}
}

Image *Surface::getRenderTarget()
{
	if(backBuffer)
	{
		backBuffer->addRef();
	}

	return backBuffer;
}

Image *Surface::getDepthStencil()
{
	if(mDepthStencil)
	{
		mDepthStencil->addRef();
	}

	return mDepthStencil;
}

void Surface::setSwapInterval(GLint interval)
{
	if(mSwapInterval == interval)
	{
		return;
	}

	mSwapInterval = interval;
	mSwapInterval = std::max(mSwapInterval, mDisplay->getMinSwapInterval());
	mSwapInterval = std::min(mSwapInterval, mDisplay->getMaxSwapInterval());
}

GLint Surface::getWidth() const
{
	return mWidth;
}

GLint Surface::getHeight() const
{
	return mHeight;
}

GLenum Surface::getTextureFormat() const
{
	return mTextureFormat;
}

GLenum Surface::getTextureTarget() const
{
	return mTextureTarget;
}

bool Surface::checkForResize()
{
	#if defined(_WIN32)
		RECT client;
		if(!GetClientRect(mWindow, &client))
		{
			ASSERT(false);
			return false;
		}

		int clientWidth = client.right - client.left;
		int clientHeight = client.bottom - client.top;
	#else
		XWindowAttributes windowAttributes;
		XGetWindowAttributes(mDisplay->getNativeDisplay(), mWindow, &windowAttributes);

		int clientWidth = windowAttributes.width;
		int clientHeight = windowAttributes.height;
	#endif

	bool sizeDirty = clientWidth != getWidth() || clientHeight != getHeight();

	if(sizeDirty)
	{
		reset(clientWidth, clientHeight);

		if(getCurrentDrawSurface() == this)
		{
			getContext()->makeCurrent(this);
		}

		return true;
	}

	return false;
}
}