#include <cassert>
#include <cstring>
#include "maybe_unused.h"
#include "GsCachedArea.h"
#include "GsPixelFormats.h"

static bool DoMemoryRangesOverlap(uint32 start1, uint32 size1, uint32 start2, uint32 size2)
{
	uint32 min1 = start1;
	uint32 max1 = start1 + size1;

	uint32 min2 = start2;
	uint32 max2 = start2 + size2;

	if(max1 <= min2) return false;
	if(min1 >= max2) return false;

	return true;
}

CGsCachedArea::CGsCachedArea()
{
	ClearDirtyPages();
}

void CGsCachedArea::SetArea(uint32 psm, uint32 bufPtr, uint32 bufWidth, uint32 height)
{
	m_psm = psm;
	m_bufPtr = bufPtr;
	m_bufWidth = bufWidth;
	m_height = height;

	//Check that we have enough bits to represent page dirtyness in m_dirtyPages
	{
		FRAMEWORK_MAYBE_UNUSED auto pageRect = GetAreaPageRect();
		assert((pageRect.width * pageRect.height) <= MAX_DIRTYPAGES);
	}
}

CGsCachedArea::PageRect CGsCachedArea::GetAreaPageRect() const
{
	auto texturePageSize = CGsPixelFormats::GetPsmPageSize(m_psm);
	uint32 pageCountX = (m_bufWidth + texturePageSize.first - 1) / texturePageSize.first;
	uint32 pageCountY = (m_height + texturePageSize.second - 1) / texturePageSize.second;
	return PageRect{0, 0, pageCountX, pageCountY};
}

CGsCachedArea::PageRect CGsCachedArea::GetDirtyPageRect() const
{
	auto areaRect = GetAreaPageRect();

	//Find starting point
	uint32 startX = 0, startY = 0;
	for(startY = 0; startY < areaRect.height; startY++)
	{
		bool done = false;
		for(startX = 0; startX < areaRect.width; startX++)
		{
			uint32 pageIndex = startX + (startY * areaRect.width);
			if(IsPageDirty(pageIndex))
			{
				done = true;
				break;
			}
		}
		if(done) break;
	}

	if((startX == areaRect.width) || (startY == areaRect.height))
	{
		return PageRect{0, 0, 0, 0};
	}

	const auto getHorzSpan =
	    [&](uint32 bx, uint32 by) {
		    uint32 span = 0;
		    for(uint32 x = bx; x < areaRect.width; x++)
		    {
			    uint32 pageIndex = x + (by * areaRect.width);
			    if(!IsPageDirty(pageIndex)) break;
			    span++;
		    }
		    return span;
	    };

	//Check how high is the dirty rect
	uint32 spanX = getHorzSpan(startX, startY);
	uint32 spanY = 1;
	for(uint32 y = startY + 1; y < areaRect.height; y++)
	{
		uint32 lineSpanX = getHorzSpan(startX, y);
		if(lineSpanX < spanX) break;
		spanY++;
	}

	return PageRect{startX, startY, spanX, spanY};
}

uint32 CGsCachedArea::GetPageCount() const
{
	auto areaRect = GetAreaPageRect();
	return areaRect.width * areaRect.height;
}

uint32 CGsCachedArea::GetSize() const
{
	return GetPageCount() * CGsPixelFormats::PAGESIZE;
}

void CGsCachedArea::Invalidate(uint32 memoryStart, uint32 memorySize)
{
	uint32 areaSize = GetSize();

	if(DoMemoryRangesOverlap(memoryStart, memorySize, m_bufPtr, areaSize))
	{
		//Find the pages that are touched by this transfer
		uint32 pageStart = (memoryStart < m_bufPtr) ? 0 : ((memoryStart - m_bufPtr) / CGsPixelFormats::PAGESIZE);
		uint32 pageCount = (memorySize + CGsPixelFormats::PAGESIZE - 1) / CGsPixelFormats::PAGESIZE;
		uint32 areaPageCount = GetPageCount();

		for(unsigned int i = 0; i < pageCount; i++)
		{
			uint32 pageIndex = pageStart + i;
			if(pageIndex >= areaPageCount) break;
			SetPageDirty(pageIndex);
		}

		//Wouldn't make sense to go through here and not have at least a dirty page
		assert(HasDirtyPages());
	}
}

bool CGsCachedArea::IsPageDirty(uint32 pageIndex) const
{
	if(pageIndex >= MAX_DIRTYPAGES)
	{
		assert(false);
		return false;
	}
	unsigned int dirtyPageSection = pageIndex / (sizeof(m_dirtyPages[0]) * 8);
	unsigned int dirtyPageIndex = pageIndex % (sizeof(m_dirtyPages[0]) * 8);
	return (m_dirtyPages[dirtyPageSection] & (1ULL << dirtyPageIndex)) != 0;
}

void CGsCachedArea::SetPageDirty(uint32 pageIndex)
{
	if(pageIndex >= MAX_DIRTYPAGES)
	{
		assert(false);
		return;
	}
	unsigned int dirtyPageSection = pageIndex / (sizeof(m_dirtyPages[0]) * 8);
	unsigned int dirtyPageIndex = pageIndex % (sizeof(m_dirtyPages[0]) * 8);
	m_dirtyPages[dirtyPageSection] |= (1ULL << dirtyPageIndex);
}

bool CGsCachedArea::HasDirtyPages() const
{
	DirtyPageHolder dirtyStatus = 0;
	for(unsigned int i = 0; i < MAX_DIRTYPAGES_SECTIONS; i++)
	{
		dirtyStatus |= m_dirtyPages[i];
	}
	return (dirtyStatus != 0);
}

void CGsCachedArea::ClearDirtyPages()
{
	memset(m_dirtyPages, 0, sizeof(m_dirtyPages));
}

void CGsCachedArea::ClearDirtyPages(const PageRect& rect)
{
	auto areaRect = GetAreaPageRect();
	uint32 endX = rect.x + rect.width;
	uint32 endY = rect.y + rect.height;

	for(uint32 y = rect.y; y < endY; y++)
	{
		for(uint32 x = rect.x; x < endX; x++)
		{
			uint32 pageIndex = x + (y * areaRect.width);
			assert(pageIndex < sizeof(m_dirtyPages) * 8);
			assert(pageIndex < GetPageCount());
			unsigned int dirtyPageSection = pageIndex / (sizeof(m_dirtyPages[0]) * 8);
			unsigned int dirtyPageIndex = pageIndex % (sizeof(m_dirtyPages[0]) * 8);
			m_dirtyPages[dirtyPageSection] &= ~(1ULL << dirtyPageIndex);
		}
	}
}
