#include "Draw.h"

namespace Upp {

#define LTIMING(x) // RTIMING(x)

int ImageBuffer::ScanKind() const
{
	const RGBA *s = pixels;
	const RGBA *ee = pixels + (GetLength() & ~3);
	while(s < ee) {
		if((s[0].a & s[1].a & s[2].a & s[3].a) != 255)
			return IMAGE_ALPHA;
		s += 4;
	}
	const RGBA *e = pixels + GetLength();
	while(s < e) {
		if(s->a != 255)
			return IMAGE_ALPHA;
		s++;
	}
	return IMAGE_OPAQUE;
}

void ImageBuffer::SetHotSpots(const Image& src)
{
	SetHotSpot(src.GetHotSpot());
	Set2ndSpot(src.Get2ndSpot());
}

void ImageBuffer::Create(int cx, int cy)
{
	ASSERT(cx >= 0 && cy >= 0);
	size.cx = cx;
	size.cy = cy;
	pixels.Alloc(GetLength(), RGBAZero());
#ifdef _DEBUG
	RGBA *s = pixels;
	RGBA *e = pixels + GetLength();
	byte  a = 0;
	while(s < e) {
		s->a = a;
		a = ~a;
		s->r = a ? 255 : 0;
		s->g = s->b = 0;
		s++;
	}
#endif
	kind = IMAGE_UNKNOWN;
	InitAttrs();
}

void ImageBuffer::InitAttrs()
{
	spot2 = hotspot = Point(0, 0);
	dots = Size(0, 0);
	paintonce = false;
}

void ImageBuffer::CopyAttrs(const ImageBuffer& img)
{
	SetHotSpot(img.GetHotSpot());
	Set2ndSpot(img.Get2ndSpot());
	SetDots(img.GetDots());
	PaintOnceHint(img.IsPaintOnceHint());
}

void ImageBuffer::CopyAttrs(const Image& img)
{
	if(img.data)
		CopyAttrs(img.data->buffer);
	else
		InitAttrs();
}

void ImageBuffer::DeepCopy(const ImageBuffer& img)
{
	Create(img.GetSize());
	CopyAttrs(img);
	memcpy_t(~pixels, ~img.pixels, GetLength());
}

void ImageBuffer::Set(Image& img)
{
	if(img.data)
		if(img.data->refcount == 1) {
			size = img.GetSize();
			kind = IMAGE_UNKNOWN;
			CopyAttrs(img);
			pixels = pick(img.data->buffer.pixels);
			img.Clear();
		}
		else {
			DeepCopy(img.data->buffer);
			kind = IMAGE_UNKNOWN;
			img.Clear();
		}
	else
		Create(0, 0);
}


void ImageBuffer::operator=(Image& img)
{
	Clear();
	Set(img);
}

void ImageBuffer::operator=(ImageBuffer& img)
{
	Clear();
	Image m = img;
	Set(m);
}

ImageBuffer::ImageBuffer(Image& img)
{
	Set(img);
}

ImageBuffer::ImageBuffer(ImageBuffer& b)
{
	kind.store(b.kind);
	size = b.size;
	pixels = pick(b.pixels);
	CopyAttrs(b);
}

void ImageBuffer::SetDPI(Size dpi) 
{
	dots.cx = int(600.*size.cx/dpi.cx);
	dots.cy = int(600.*size.cy/dpi.cy);
}

Size ImageBuffer::GetDPI() 
{
	return Size(dots.cx ? int(600.*size.cx/dots.cx) : 0, dots.cy ? int(600.*size.cy/dots.cy) : 0);
}

void Image::Set(ImageBuffer& b)
{
	if(b.GetWidth() == 0 || b.GetHeight() == 0)
		data = NULL;
	else
		data = new Data(b);
}

void Image::Clear()
{
	if(data)
		data->Release();
	data = NULL;
}

Image& Image::operator=(ImageBuffer& img)
{
	if(data)
		data->Release();
	Set(img);
	return *this;
}

Image& Image::operator=(const Image& img)
{
	Data *d = data;
	data = img.data;
	if(data)
		data->Retain();
	if(d)
		d->Release();
	return *this;
}

Point Image::GetHotSpot() const
{
	return data ? data->buffer.GetHotSpot() : Point(0, 0);
}

Point Image::Get2ndSpot() const
{
	return data ? data->buffer.Get2ndSpot() : Point(0, 0);
}

Size Image::GetDots() const
{
	return data ? data->buffer.GetDots() : Size(0, 0);
}

Size Image::GetDPI() const
{
	Size size = GetSize();
	Size dots = GetDots();
	return data ?  Size(int(600.*size.cx/dots.cx), int(600.*size.cy/dots.cy)): Size(0, 0);
}

int Image::GetKindNoScan() const
{
	return data ? data->buffer.GetKind() : IMAGE_ALPHA;
}

int Image::Data::GetKind()
{
	int k = buffer.GetKind();
	if(k != IMAGE_UNKNOWN)
		return k;
	k = buffer.ScanKind();
	buffer.SetKind(k);
	return k;
}

int Image::GetKind() const
{
	return data ? data->GetKind() : IMAGE_EMPTY;
}

void Image::Serialize(Stream& s)
{
	Point spot2 = Get2ndSpot();
	int version = spot2.x || spot2.y; // version 1 only if we need 2nd spot, to improve BW compatibility
	s / version;
	Size sz = GetSize();
	Point p = GetHotSpot();
	Size dots = GetDots();
	s % sz % p % dots;
	Point p2(0, 0);
	if(version >= 1) {
		p2 = spot2;
		s % p2;
	}
	if(sz.cx < 0 || sz.cy < 0)
		s.LoadError();
	int64 len = (int64)sz.cx * (int64)sz.cy * (int64)sizeof(RGBA);
	if(s.IsLoading()) {
		if(len) {
			ImageBuffer b;
			if(len < 6 * 1024 * 1024) {
				b.Create(sz);
				if(!s.GetAll(~b, (int)len)) {
					Clear();
					s.LoadError();
					return;
				}
			}
			else {
				Huge h;
				if(!s.GetAll(h, (size_t)len)) {
					Clear();
					s.LoadError();
					return;
				}
				b.Create(sz);
				h.Get(~b);
			}

			b.SetDots(dots);
			b.SetHotSpot(p);
			b.Set2ndSpot(p2);
			*this = b;
		}
		else
			Clear();
	}
	else
		s.Put64(~*this, len);
}
INITBLOCK {
	Value::Register<Image>("Image");
}

bool Image::operator==(const Image& img) const
{
	static_assert(sizeof(RGBA) == 4, "sizeof(RGBA)");
	return IsSame(img) ||
	   GetSize() == img.GetSize() &&
	   GetHotSpot() == img.GetHotSpot() &&
	   Get2ndSpot() == img.Get2ndSpot() &&
	   GetDots() == img.GetDots() &&
	   memeq_t(~*this, ~img, GetLength());
}

bool Image::operator!=(const Image& img) const
{
	return !operator==(img);
}

hash_t Image::GetHashValue() const
{
	return memhash(~*this, GetLength() * sizeof(RGBA));
}

Image::Image(const Image& img)
{
	data = img.data;
	if(data)
		data->Retain();
}

Image::Image(Image (*fn)())
{
	data = NULL;
	*this = (*fn)();
}

Image::Image(const Value& src)
{
	data = NULL;
	*this = src.Get<Image>();
}

Image::Image(ImageBuffer& b)
{
	Set(b);
}

Image::~Image()
{
	if(data)
		data->Release();
}

String Image::ToString() const
{
	return String("Image ").Cat() << GetSize();
}

void Image::Data::NewSerial()
{
	INTERLOCKED {
		static int64 gserial;
		serial = ++gserial;
	}
}

Image::Data::Data(ImageBuffer& b)
:	buffer(b)
{
	paintcount = 0;
	paintonly = false;
	refcount = 1;
	aux_data = 0;
	NewSerial();
}

void Image::SetAuxData(uint64 adata)
{
	if(data)
		data->aux_data = adata;
}

uint64 Image::GetAuxData() const
{
	return data ? data->aux_data : 0;
}

static void sMultiply(ImageBuffer& b, int (*op)(RGBA *t, const RGBA *s, size_t len))
{
	if(b.GetKind() != IMAGE_OPAQUE && b.GetKind() != IMAGE_EMPTY)
		(*op)(~b, ~b, b.GetLength());
}

void Premultiply(ImageBuffer& b)
{
	sMultiply(b, Premultiply);
}

void Unmultiply(ImageBuffer& b)
{
	sMultiply(b, Unmultiply);
}

static Image sMultiply(const Image& img, int (*op)(RGBA *t, const RGBA *s, size_t len))
{
	int k = img.GetKind();
	if(k == IMAGE_OPAQUE || k == IMAGE_EMPTY)
		return img;
	ImageBuffer ib(img.GetSize());
	ib.CopyAttrs(img);
	ib.SetKind((*op)(~ib, ~img, ib.GetLength()));
	return Image(ib);
}

Image Premultiply(const Image& img)
{
	return sMultiply(img, Premultiply);
}

Image Unmultiply(const Image& img)
{
	return sMultiply(img, Unmultiply);
}

String StoreImageAsString(const Image& img)
{
	if(img.GetKind() == IMAGE_EMPTY)
		return Null;
	int type = img.GetKind() == IMAGE_OPAQUE ? 3 : 4;
	StringStream ss;
	ss.Put(type);
	Size sz = img.GetSize();
	ss.Put16le(sz.cx);
	ss.Put16le(sz.cy);
	Point p = img.GetHotSpot();
	ss.Put16le(p.x);
	ss.Put16le(p.y);
	Size dots = img.GetDots();
	ss.Put16le(dots.cx);
	ss.Put16le(dots.cy);
	const RGBA *s = img;
	const RGBA *e = s + img.GetLength();
	Buffer<byte> b(type * img.GetLength());
	byte *t = b;
	if(type == 3)
		while(s < e) {
			*t++ = s->r;
			*t++ = s->g;
			*t++ = s->b;
			s++;
		}
	else
		while(s < e) {
			*t++ = s->r;
			*t++ = s->g;
			*t++ = s->b;
			*t++ = s->a;
			s++;
		}
	MemReadStream m(b, type * img.GetLength());
	ZCompress(ss, m);
	return ss;
}

Image  LoadImageFromString(const String& src)
{
	if(src.GetLength() < 13)
		return Null;
	StringStream ss(src);
	int type = ss.Get();
	type &= 0x3f;
	Size sz;
	sz.cx = ss.Get16le();
	sz.cy = ss.Get16le();
	if(sz.cx < 0 || sz.cy < 0)
		return Null;
	Point p;
	p.x = ss.Get16le();
	p.y = ss.Get16le();
	if(p.x < 0 || p.y < 0)
		return Null;
	Size dots;
	dots.cx = ss.Get16le();
	dots.cy = ss.Get16le();
	if(dots.cx < 0 || dots.cy < 0)
		return Null;
	StringStream out;
	ZDecompress(out, ss);
	String data = out;
	if(data.GetLength() != type * sz.cx * sz.cy)
		return Image();
	ImageBuffer ib(sz);
	ib.SetHotSpot(p);
	ib.SetDots(dots);
	RGBA *t = ib;
	const RGBA *e = t + ib.GetLength();
	const byte *s = data;
	if(type == 3)
		while(t < e) {
			t->r = *s++;
			t->g = *s++;
			t->b = *s++;
			t->a = 255;
			t++;
		}
	else
	if(type == 4)
		while(t < e) {
			t->r = *s++;
			t->g = *s++;
			t->b = *s++;
			t->a = *s++;
			t++;
		}
	else
		return Image();
	return Image(ib);
}

Size GetImageStringSize(const String& src)
{
	if(src.GetLength() < 13)
		return Size(0, 0);
	StringStream ss(src);
	ss.Get();
	Size sz;
	sz.cx = ss.Get16le();
	sz.cy = ss.Get16le();
	return sz;
}

Size GetImageStringDots(const String& src)
{
	if(src.GetLength() < 13)
		return Size(0, 0);
	StringStream ss(src);
	ss.SeekCur(9);
	Size sz;
	sz.cx = ss.Get16le();
	sz.cy = ss.Get16le();
	return sz;
}

}
