
#include <cstring>
#include <Spectre/System/Log.h>
#include <Spectre/Graphics/Image.h>
#include "ImageLoader.h"

namespace sp {

static ImageLoader _loader;

Image::Image() :
m_size		(0, 0),
m_channels  (RGBA)
{
}

void Image::create(unsigned width, unsigned height, const Color& color, enum Channels comp)
{
	m_size.x = width;
	m_size.y = height;
	m_channels = comp;

	m_pixels.resize(m_size.x * m_size.y * getNumChannels());

	for(size_t i = 0; i < m_pixels.size(); i += 4) {

		m_pixels[i+0] = color.r;
		m_pixels[i+1] = color.g;
		m_pixels[i+2] = color.b;
		m_pixels[i+3] = color.a;
	}
}

void Image::create(unsigned width, unsigned height, const void* pixels, PixelFormat format)
{
	m_size.x = width;
	m_size.y = height;

	setPixels(pixels, format);
}

const Vector2u& Image::getSize() const
{
	return m_size;
}

unsigned int Image::getWidth() const
{
	return m_size.x;
}

unsigned int Image::getHeight() const
{
	return m_size.y;
}

unsigned int Image::getNumChannels() const
{
	switch(m_channels) {
	case Channels::RGBA	: return 4;
	case Channels::RGB	: return 3;
	case Channels::Alpha :
	default : break;
	}
	return 1;
}

unsigned int Image::getStride() const
{
	return m_size.x * getNumChannels();
}

PixelFormat Image::getFormat() const
{
	switch(m_channels) {
	case Channels::RGBA	: return PixelFormat::PF_RGBA;
	case Channels::RGB	: return PixelFormat::PF_RGB;
	case Channels::Alpha :
	default : break;
	}
	return PixelFormat::PF_Alpha;
}

bool Image::hasAlpha() const
{
	return m_channels != Channels::RGB;
}

bool Image::loadFromFile(const std::string& filename)
{
	m_pixels.clear();

	return _loader.loadFromFile(filename.c_str(), *this);
}

bool Image::loadFromMemory(const void *data, unsigned int size)
{
	m_pixels.clear();

	return _loader.loadFromMemory(data, size, *this);
}

void Image::saveToFile(const std::string& filename) const
{
	_loader.saveToFile(*this, filename.c_str());
}

void Image::setPixel(unsigned x, unsigned y, const Color& c)
{
	unsigned char *base = m_pixels.data() + (getNumChannels() * ((y * m_size.x) + x));

	if (m_channels == RGB || m_channels == RGBA) {
		base[0] = c.r;
		base[1] = c.g;
		base[2] = c.b;
		if (m_channels == RGBA) {
			base[3] = c.a;
		}
	}
	else if (m_channels == Alpha) {
		base[0] = c.a;
	}
}

Color Image::getPixel(unsigned x, unsigned y) const
{
	Color c;
	const unsigned char *base = m_pixels.data() + (getNumChannels() * ((y * m_size.x) + x));

	// RGB / RGBA formats.
	if (m_channels == RGB || m_channels == RGBA) {
		c.r = base[0];
		c.g = base[1];
		c.b = base[2];
		if (m_channels == RGBA) {
			c.a = base[3];
		} else {
			c.a = 255;
		}
	}
	// Alpha, single channel format.
	else if (m_channels == Alpha) {
		c = Color(0, 0, 0, base[0]);
	}
	return c;
}

void Image::flipY()
{
	unsigned int stride = getStride();

	// The quick and dirty way :)
	for(size_t y = 0; y < m_size.y / 2; y++) {

		int row0 = y * stride;
		int row1 = (m_size.y - y - 1) * stride;

		for(size_t x = 0; x < stride; x++) {
			unsigned char tmp = m_pixels[row0 + x];
			m_pixels[row0 + x] = m_pixels[row1 + x];
			m_pixels[row1 + x] = tmp;
		}
	}
}

void Image::setPixels(const void *pixels, PixelFormat format)
{
	switch(PF_getNumChannels(format)) {
	case 4:
		m_channels = RGBA;
		break;
	case 3:
		m_channels = RGB;
		break;
	default :
		m_channels = Alpha;
	}

	m_pixels.resize(m_size.y * getStride());

	// TODO: Move pixel format convertion to it's own function(s)
	if (format == PixelFormat::PF_BGR || format == PixelFormat::PF_BGRA) {
		const uint8_t *ptr = (const uint8_t *) pixels;

		for(int i = 0; i < m_pixels.size(); i += 4) {
			m_pixels[i+0] = ptr[2];
			m_pixels[i+1] = ptr[1];
			m_pixels[i+2] = ptr[0];
			if (format == PixelFormat::PF_BGRA) {
				m_pixels[i+3] = ptr[3];
			}
			ptr += 4;
		}
	} else {
		memcpy(&m_pixels[0], pixels, m_pixels.size());
	}
}

const unsigned char* Image::getPixels() const
{
	return m_pixels.size() ? &m_pixels[0] : NULL;
}

} // namespace sp
