
// Live++: exchange the "#if 0" with an "#if 1" if you want to disable optimizations for this file in a Release build.
// there are other options that don't require source code modifications though, see example "05_ToggleOptimizations".
#if 0
#include "LivePP/API/x64/LPP_API_x64_CPP.h"
LPP_DISABLE_OPTIMIZATIONS;
#endif

#include "PalettizedFrameBuffer.h"
#include "XorShift.h"
#include "Point2.h"
#include <cmath>
#include <algorithm>


PalettizedFrameBuffer::PalettizedFrameBuffer(unsigned int width, unsigned int height)
	: m_width(width)
	, m_height(height)
	, m_buffer(new unsigned char[width*height]())
	, m_tempBuffer(new unsigned char[width*height]())
{
}


PalettizedFrameBuffer::~PalettizedFrameBuffer(void)
{
	delete[] m_tempBuffer;
	delete[] m_buffer;
}


void PalettizedFrameBuffer::SpawnFire(void)
{
	// Live++: change the number of fire rows to be spawned at the bottom
	const unsigned int rows = 2u;
	for (unsigned int y = 0u; y < rows; ++y)
	{
		for (unsigned int x = 0u; x < m_width; ++x)
		{
			// Live++: alter the spawn behaviour of the fire by changing the colors of new fire pixels
			const unsigned char color = Random::XorShift(0u, 180u) & 0xFFu;

			WritePixel(x, m_height - 1u - y, color);
		}
	}
}


void PalettizedFrameBuffer::AverageAndShiftUpwards(void)
{
	// average neighboring pixels into the temporary buffer
	
	// rows
	{
		// top-most row, left-most pixel
		{
			const unsigned int x = 0u;
			const unsigned int y = 0u;
			const unsigned int accumulatedColor =
				ReadPixel(x + 0u, y + 0u) +
				ReadPixel(x + 1u, y + 0u) +
				ReadPixel(x + 0u, y + 1u) +
				ReadPixel(x + 1u, y + 1u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 4u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}

		// top-most row, right-most pixel
		{
			const unsigned int x = m_width - 1u;
			const unsigned int y = 0u;
			const unsigned int accumulatedColor =
				ReadPixel(x - 1u, y + 0u) +
				ReadPixel(x + 0u, y + 0u) +
				ReadPixel(x - 1u, y + 1u) +
				ReadPixel(x + 0u, y + 1u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 4u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}

		// bottom-most row, left-most pixel
		{
			const unsigned int x = 0u;
			const unsigned int y = m_height - 1u;
			const unsigned int accumulatedColor =
				ReadPixel(x + 0u, y - 1u) +
				ReadPixel(x + 1u, y - 1u) +
				ReadPixel(x + 0u, y + 0u) +
				ReadPixel(x + 1u, y + 0u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 4u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}

		// bottom-most row, right-most pixel
		{
			const unsigned int x = m_width - 1u;
			const unsigned int y = m_height - 1u;
			const unsigned int accumulatedColor =
				ReadPixel(x - 1u, y - 1u) +
				ReadPixel(x + 0u, y - 1u) +
				ReadPixel(x - 1u, y + 0u) +
				ReadPixel(x + 0u, y + 0u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 4u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}

		for (unsigned int x = 1u; x < m_width - 1u; ++x)
		{
			// top-most row
			{
				const unsigned int y = 0u;
				const unsigned int accumulatedColor =
					ReadPixel(x - 1u, y + 0u) +
					ReadPixel(x + 0u, y + 0u) +
					ReadPixel(x + 1u, y + 0u) +
					ReadPixel(x - 1u, y + 1u) +
					ReadPixel(x + 0u, y + 1u) +
					ReadPixel(x + 1u, y + 1u);

				const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 6u);
				m_tempBuffer[y * m_width + x] = averageColor;
			}

			// bottom-most row
			{
				const unsigned int y = m_height - 1u;
				const unsigned int accumulatedColor =
					ReadPixel(x - 1u, y - 1u) +
					ReadPixel(x + 0u, y - 1u) +
					ReadPixel(x + 1u, y - 1u) +
					ReadPixel(x - 1u, y + 0u) +
					ReadPixel(x + 0u, y + 0u) +
					ReadPixel(x + 1u, y + 0u);

				const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 6u);
				m_tempBuffer[y * m_width + x] = averageColor;
			}
		}
	}

	// columns
	for (unsigned int y = 1u; y < m_height - 1u; ++y)
	{
		// left-most column
		{
			const unsigned int x = 0u;
			const unsigned int accumulatedColor =
				ReadPixel(x + 0u, y - 1u) +
				ReadPixel(x + 1u, y - 1u) +
				ReadPixel(x + 0u, y + 0u) +
				ReadPixel(x + 1u, y + 0u) +
				ReadPixel(x + 0u, y + 1u) +
				ReadPixel(x + 1u, y + 1u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 6u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}

		// right-most column
		{
			const unsigned int x = m_width - 1u;
			const unsigned int accumulatedColor =
				ReadPixel(x - 1u, y - 1u) +
				ReadPixel(x + 0u, y - 1u) +
				ReadPixel(x - 1u, y + 0u) +
				ReadPixel(x + 0u, y + 0u) +
				ReadPixel(x - 1u, y + 1u) +
				ReadPixel(x + 0u, y + 1u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 6u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}
	}

	// inner pixels
	for (unsigned int y = 1u; y < m_height - 1u; ++y)
	{
		for (unsigned int x = 1u; x < m_width - 1u; ++x)
		{
			const unsigned int accumulatedColor =
				ReadPixel(x - 1u, y - 1u) +
				ReadPixel(x + 0u, y - 1u) +
				ReadPixel(x + 1u, y - 1u) +
				ReadPixel(x - 1u, y + 0u) +
				ReadPixel(x + 0u, y + 0u) +
				ReadPixel(x + 1u, y + 0u) +
				ReadPixel(x - 1u, y + 1u) +
				ReadPixel(x + 0u, y + 1u) +
				ReadPixel(x + 1u, y + 1u);

			const unsigned char averageColor = static_cast<unsigned char>(accumulatedColor / 9u);
			m_tempBuffer[y * m_width + x] = averageColor;
		}
	}

	// shift upwards N pixels by copying from temporary buffer to frame buffer
	// Live++: change the number of rows by which the frame buffer is shifted upwards
	const unsigned int shiftRows = 2u;
	for (unsigned int y = 0u; y < m_height - shiftRows; ++y)
	{
		for (unsigned int x = 0u; x < m_width; ++x)
		{
			const unsigned char color = m_tempBuffer[(y + shiftRows) * m_width + x];
			WritePixel(x, y, color);
		}
	}
}


void PalettizedFrameBuffer::FadeOut(void)
{
	for (unsigned int y = 0u; y < m_height; ++y)
	{
		for (unsigned int x = 0u; x < m_width; ++x)
		{
			// Live++: change the fade out behaviour
			const unsigned char color = ReadPixel(x, y);
			if (color > 0u)
			{
				WritePixel(x, y, color - 1u);
			}
		}
	}
}


void PalettizedFrameBuffer::DrawLine(const Point2& p0, const Point2& p1)
{
	const int dx = p1.x - p0.x;
	const int dy = p1.y - p0.y;

	int dLong = abs(dx);
	int dShort = abs(dy);

	int offsetLong = dx > 0 ? 1 : -1;
	int offsetShort = dy > 0 ? m_width : -int(m_width);

	if (dLong < dShort)
	{
		std::swap(dShort, dLong);
		std::swap(offsetShort, offsetLong);
	}

	int error = dLong / 2;
	int index = p0.y * m_width + p0.x;
	const int offset[] = { offsetLong, offsetLong + offsetShort };
	const int abs_d[] = { dShort, dShort - dLong };

	const int lastIndex = m_width * m_height;
	for (int i = 0; i <= dLong; ++i)
	{
		// Live++: control the intensity with which the cubes burn by adjusting the min and max random values
		const unsigned char color = Random::XorShift(80u, 110u) & 0xFFu;

		// do not draw outside the buffer
		if ((index >= 0) && (index < lastIndex))
		{
			m_buffer[index] = color;
		}
		
		const int errorIsTooBig = error >= dLong;
		index += offset[errorIsTooBig];
		error += abs_d[errorIsTooBig];
	}
}


void PalettizedFrameBuffer::WritePixel(unsigned int x, unsigned int y, unsigned char value)
{
	m_buffer[y * m_width + x] = value;
}


unsigned char PalettizedFrameBuffer::ReadPixel(unsigned int x, unsigned int y) const
{
	return m_buffer[y * m_width + x];
}
