#include "stdafx.h"
#include "ImageSet.h"
#include "Exception.h"

namespace storm {

	ImageSet::ImageSet() : images(null) {}

	ImageSet::ImageSet(Image *image) : images(null) {
		push(image);
	}

	ImageSet::ImageSet(const ImageSet &o) : images(null) {
		if (o.images) {
			images = runtime::allocArray<Image *>(engine(), &pointerArrayType, o.images->filled);
			for (size_t i = 0; i < o.images->filled; i++)
				images->v[i] = o.images->v[i];
			images->filled = o.images->filled;
		}
	}

	void ImageSet::deepCopy(CloneEnv *env) {
		if (empty())
			return;

		for (size_t i = 0; i < images->filled; i++) {
			images->v[i] = clone(images->v[i], env);
		}
	}

	Image *ImageSet::at(Nat i) const {
		if (i >= count())
			throw new (this) ArrayError(i, count());
		return images->v[i];
	}

	static Bool lessEq(Image *a, Image *b) {
		return a->width() != b->width()
			? a->width() <= b->width()
			: a->height() <= b->height();
	}

	void ImageSet::push(Image *image) {
		if (!images || images->filled >= images->count())
			grow();

		size_t pos = images->filled;
		for (; pos > 0; pos--) {
			Image *here = images->v[pos - 1];
			if (lessEq(here, image))
				break;
			images->v[pos] = here;
		}

		images->v[pos] = image;
		images->filled++;
	}

	void ImageSet::grow() {
		if (images) {
			GcArray<Image *> *n = runtime::allocArray<Image *>(engine(), &pointerArrayType, images->count() * 2);
			for (size_t i = 0; i < images->count(); i++)
				n->v[i] = images->v[i];
			n->filled = images->filled;
			images = n;
		} else {
			images = runtime::allocArray<Image *>(engine(), &pointerArrayType, 8);
		}
	}

	static bool compareImage(geometry::Size size, Image *image) {
		return size.w != Float(image->width())
			? size.w < Float(image->width())
			: size.h < Float(image->height());
	}

	Image *ImageSet::closest(geometry::Size size) {
		if (empty())
			throw new (this) ArrayError(1, 0);

		Image **begin = images->v;
		Image **end = images->v + images->filled;

		Image **found = std::upper_bound(begin, end, size, compareImage);
		// 'found' is the first element "larger than" size.
		if (found == begin) {
			// No previous element to look at, pick the smallest.
			return *begin;
		}

		// If 'found' is the end element, then there are no elements larger:
		if (found == end) {
			return end[-1];
		}

		// Two options, pick the closest one.
		Image *a = found[-1];
		Image *b = found[0];

		if (a->width() == b->width()) {
			if (abs(Float(a->height()) - size.h) < abs(Float(b->height()) - size.h))
				return a;
			else
				return b;
		} else {
			if (abs(Float(a->width()) - size.w) < abs(Float(b->width()) - size.w))
				return a;
			else
				return b;
		}
	}

	Image *ImageSet::closest(Nat width, Nat height) {
		return closest(geometry::Size(Float(width), Float(height)));
	}

	Image *ImageSet::atLeast(geometry::Size size) {
		if (empty())
			throw new (this) ArrayError(1, 0);

		Image **begin = images->v;
		Image **end = images->v + images->filled;

		Image **found = std::upper_bound(begin, end, size, compareImage);
		// 'found' is the first element "larger than" size.
		if (found == begin) {
			// No previous element to look at, pick the smallest.
			return *begin;
		}

		// If 'found' is the end element, then there are no elements larger:
		if (found == end) {
			return end[-1];
		}

		// Two options, pick "a" if it is exactly the requested size.
		Image *a = found[-1];
		Image *b = found[0];

		if (Float(a->width()) == size.w && Float(a->height()) == size.h)
			return a;
		else
			return b;
	}

	Image *ImageSet::atLeast(Nat width, Nat height) {
		return atLeast(geometry::Size(Float(width), Float(height)));
	}

	void ImageSet::toS(StrBuf *to) const {
		*to << S("(dimensions: ");
		for (Nat i = 0; i < count(); i++) {
			if (i > 0)
				*to << S(", ");

			Image *x = at(i);
			*to << x->width() << S("x") << x->height();
		}
		*to << S(")");
	}

}
