// 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 egl::Surface class, representing a drawing surface
// such as the client area of a window, including any back buffers.
// Implements EGLSurface and related functionality. [EGL 1.4] section 2.2 page 3.

#include "Surface.hpp"

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

#if defined(USE_X11)
#include "Main/libX11.hpp"
#elif defined(_WIN32)
#include <tchar.h>
#elif defined(__APPLE__)
#include "OSXUtils.hpp"
#endif

#include <algorithm>

namespace gl
{
Surface::Surface()
{
}

Surface::~Surface()
{
}
}

namespace egl
{
Surface::Surface(const Display *display, const Config *config) : display(display), config(config)
{
}

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

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

	if(libGLESv2)
	{
		if(clientBuffer)
		{
			backBuffer = libGLESv2->createBackBufferFromClientBuffer(
				egl::ClientBuffer(width, height, getClientBufferFormat(), clientBuffer, clientBufferPlane));
		}
		else
		{
			backBuffer = libGLESv2->createBackBuffer(width, height, config->mRenderTargetFormat, config->mSamples);
		}
	}
	else if(libGLES_CM)
	{
		backBuffer = libGLES_CM->createBackBuffer(width, height, config->mRenderTargetFormat, config->mSamples);
	}

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

	if(config->mDepthStencilFormat != sw::FORMAT_NULL)
	{
		if(libGLESv2)
		{
			depthStencil = libGLESv2->createDepthStencil(width, height, config->mDepthStencilFormat, config->mSamples);
		}
		else if(libGLES_CM)
		{
			depthStencil = libGLES_CM->createDepthStencil(width, height, config->mDepthStencilFormat, config->mSamples);
		}

		if(!depthStencil)
		{
			ERR("Could not create depth/stencil buffer for surface");
			deleteResources();
			return error(EGL_BAD_ALLOC, false);
		}
	}

	return true;
}

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

	if(texture)
	{
		texture->releaseTexImage();
		texture = nullptr;
	}

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

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

	return backBuffer;
}

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

	return depthStencil;
}

void Surface::setMipmapLevel(EGLint mipmapLevel)
{
	this->mipmapLevel = mipmapLevel;
}

void Surface::setMultisampleResolve(EGLenum multisampleResolve)
{
	this->multisampleResolve = multisampleResolve;
}

void Surface::setSwapBehavior(EGLenum swapBehavior)
{
	this->swapBehavior = swapBehavior;
}

void Surface::setSwapInterval(EGLint interval)
{
	if(swapInterval == interval)
	{
		return;
	}

	swapInterval = interval;
	swapInterval = std::max(swapInterval, display->getMinSwapInterval());
	swapInterval = std::min(swapInterval, display->getMaxSwapInterval());
}

EGLint Surface::getConfigID() const
{
	return config->mConfigID;
}

EGLenum Surface::getSurfaceType() const
{
	return config->mSurfaceType;
}

EGLint Surface::getWidth() const
{
	return width;
}

EGLint Surface::getHeight() const
{
	return height;
}

EGLint Surface::getMipmapLevel() const
{
	return mipmapLevel;
}

EGLenum Surface::getMultisampleResolve() const
{
	return multisampleResolve;
}

EGLint Surface::getPixelAspectRatio() const
{
	return pixelAspectRatio;
}

EGLenum Surface::getRenderBuffer() const
{
	return renderBuffer;
}

EGLenum Surface::getSwapBehavior() const
{
	return swapBehavior;
}

EGLenum Surface::getTextureFormat() const
{
	return textureFormat;
}

EGLenum Surface::getTextureTarget() const
{
	return textureTarget;
}

EGLBoolean Surface::getLargestPBuffer() const
{
	return largestPBuffer;
}

sw::Format Surface::getClientBufferFormat() const
{
	switch(clientBufferType)
	{
	case GL_UNSIGNED_BYTE:
		switch(clientBufferFormat)
		{
		case GL_RED:
			return sw::FORMAT_R8;
		case GL_RG:
			return sw::FORMAT_G8R8;
		case GL_BGRA_EXT:
			return sw::FORMAT_A8R8G8B8;
		default:
			UNREACHABLE(clientBufferFormat);
			break;
		}
		break;
	case GL_UNSIGNED_SHORT:
		switch(clientBufferFormat)
		{
		case GL_R16UI:
			return sw::FORMAT_R16UI;
		default:
			UNREACHABLE(clientBufferFormat);
			break;
		}
		break;
	case GL_HALF_FLOAT_OES:
	case GL_HALF_FLOAT:
		switch(clientBufferFormat)
		{
		case GL_RGBA:
			return sw::FORMAT_A16B16G16R16F;
		default:
			UNREACHABLE(clientBufferFormat);
			break;
		}
	default:
		UNREACHABLE(clientBufferType);
		break;
	}

	return sw::FORMAT_NULL;
}

void Surface::setBoundTexture(egl::Texture *texture)
{
	this->texture = texture;
}

egl::Texture *Surface::getBoundTexture() const
{
	return texture;
}

WindowSurface::WindowSurface(Display *display, const Config *config, EGLNativeWindowType window)
	: Surface(display, config), window(window)
{
	pixelAspectRatio = (EGLint)(1.0 * EGL_DISPLAY_SCALING);   // FIXME: Determine actual pixel aspect ratio
}

WindowSurface::~WindowSurface()
{
	WindowSurface::deleteResources();
}

bool WindowSurface::initialize()
{
	ASSERT(!frameBuffer && !backBuffer && !depthStencil);

	return checkForResize();
}

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

		checkForResize();
	}
}

EGLNativeWindowType WindowSurface::getWindowHandle() const
{
	return window;
}

bool WindowSurface::checkForResize()
{
	#if defined(_WIN32)
		RECT client;
		BOOL status = GetClientRect(window, &client);

		if(status == 0)
		{
			return error(EGL_BAD_NATIVE_WINDOW, false);
		}

		int windowWidth = client.right - client.left;
		int windowHeight = client.bottom - client.top;
	#elif defined(__ANDROID__)
		int windowWidth;  window->query(window, NATIVE_WINDOW_WIDTH, &windowWidth);
		int windowHeight; window->query(window, NATIVE_WINDOW_HEIGHT, &windowHeight);
	#elif defined(USE_X11)
		XWindowAttributes windowAttributes;
		Status status = libX11->XGetWindowAttributes((::Display*)display->getNativeDisplay(), window, &windowAttributes);

		if(status == 0)
		{
			return error(EGL_BAD_NATIVE_WINDOW, false);
		}

		int windowWidth = windowAttributes.width;
		int windowHeight = windowAttributes.height;
	#elif defined(__linux__)
		// Non X11 linux is headless only
		int windowWidth = 100;
		int windowHeight = 100;
	#elif defined(__APPLE__)
		int windowWidth;
		int windowHeight;
		sw::OSX::GetNativeWindowSize(window, windowWidth, windowHeight);
	#elif defined(__Fuchsia__)
		// TODO(crbug.com/800951): Integrate with Mozart.
		int windowWidth = 100;
		int windowHeight = 100;
	#else
		#error "WindowSurface::checkForResize unimplemented for this platform"
	#endif

	if((windowWidth != width) || (windowHeight != height))
	{
		bool success = reset(windowWidth, windowHeight);

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

		return success;
	}

	return true;   // Success
}

void WindowSurface::deleteResources()
{
	delete frameBuffer;
	frameBuffer = nullptr;

	Surface::deleteResources();
}

bool WindowSurface::reset(int backBufferWidth, int backBufferHeight)
{
	width = backBufferWidth;
	height = backBufferHeight;

	deleteResources();

	if(window)
	{
		if(libGLESv2)
		{
			frameBuffer = libGLESv2->createFrameBuffer(display->getNativeDisplay(), window, width, height);
		}
		else if(libGLES_CM)
		{
			frameBuffer = libGLES_CM->createFrameBuffer(display->getNativeDisplay(), window, width, height);
		}

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

	return Surface::initialize();
}

PBufferSurface::PBufferSurface(Display *display, const Config *config, EGLint width, EGLint height,
                               EGLenum textureFormat, EGLenum textureTarget, EGLenum clientBufferFormat,
                               EGLenum clientBufferType, EGLBoolean largestPBuffer, EGLClientBuffer clientBuffer,
                               EGLint clientBufferPlane)
	: Surface(display, config)
{
	this->width = width;
	this->height = height;
	this->largestPBuffer = largestPBuffer;
	this->textureFormat = textureFormat;
	this->textureTarget = textureTarget;
	this->clientBufferFormat = clientBufferFormat;
	this->clientBufferType = clientBufferType;
	this->clientBuffer = clientBuffer;
	this->clientBufferPlane = clientBufferPlane;
}

PBufferSurface::~PBufferSurface()
{
	PBufferSurface::deleteResources();
}

void PBufferSurface::swap()
{
	// No effect
}

EGLNativeWindowType PBufferSurface::getWindowHandle() const
{
	UNREACHABLE(-1);   // Should not be called. Only WindowSurface has a window handle.

	return 0;
}

void PBufferSurface::deleteResources()
{
	Surface::deleteResources();
}

}