#include "stdafx.h"
#include "OverlayProtocol.h"
#include "Url.h"
#include "Core/Set.h"
#include "MemStream.h"
#include "Utf8Text.h"
#include "Serialization.h"

namespace storm {

	OverlayProtocol::OverlayProtocol(Url *dir) : dir(dir) {
		files = new (this) Map<Str *, Buffer>();
	}

	Bool OverlayProtocol::partEq(Str *a, Str *b) {
		return dir->protocol()->partEq(a, b);
	}

	Nat OverlayProtocol::partHash(Str *a) {
		return dir->protocol()->partHash(a);
	}

	Bool OverlayProtocol::isEqualTo(const Protocol *other) const {
		if (const OverlayProtocol *overlay = as<const OverlayProtocol>(other))
			other = overlay->dir->protocol();

		return *dir->protocol() == *other;
	}

	MAYBE(Str *) OverlayProtocol::isBelow(Url *url) const {
		if (url->isBelow(dir) && url->count() == dir->count() + 1)
			return url->name();
		else
			return null;
	}

	Array<Url *> *OverlayProtocol::children(Url *url) {
		Array<Url *> *result = dir->protocol()->children(url);

		// Update the protocol of all elements:
		for (Nat i = 0; i < result->count(); i++) {
			if (result->at(i)->protocol() != this)
				result->at(i) = result->at(i)->withProtocol(this);
		}

		// Is it our directory? If so, merge files!
		if (url->isBelow(dir) && url->count() == dir->count()) {
			// See which ones exist:
			Set<Str *> *existing = new (this) Set<Str *>();
			for (Nat i = 0; i < result->count(); i++)
				existing->put(result->at(i)->name());

			// And add remaining ones:
			for (FileMap::Iter i = files->begin(); i != files->end(); ++i) {
				if (!existing->has(i.k()))
					result->push(url->push(i.k()));
			}
		}

		return result;
	}

	IStream *OverlayProtocol::read(Url *url) {
		if (Str *here = isBelow(url)) {
			if (files->has(here)) {
				return new (this) MemIStream(files->get(here));
			}
		}
		return dir->protocol()->read(url);
	}

	OStream *OverlayProtocol::write(Url *url) {
		// We could support it, but then we need a more custom OStream implementation.
		throw new (this) ProtocolNotSupported(S("write"), toS());
	}

	StatType OverlayProtocol::stat(Url *url) {
		if (Str *here = isBelow(url)) {
			if (files->has(here)) {
				return sFile;
			}
		}

		return dir->protocol()->stat(url);
	}

	void OverlayProtocol::toS(StrBuf *to) const {
		// *to << S("<overlay>:");
		dir->protocol()->toS(to);
	}

	Url *OverlayProtocol::put(Str *name, Buffer content) {
		files->put(name, content);
		Array<Str *> *parts = dir->getParts();
		parts->push(name);
		return new (this) Url(this, parts);
	}

	Url *OverlayProtocol::put(Str *name, Str *content) {
		MemOStream *out = new (this) MemOStream();
		Utf8Output *utf = new (this) Utf8Output(out);
		utf->write(content);
		utf->flush();
		return put(name, out->buffer());
	}


	SerializedType *OverlayProtocol::serializedType(EnginePtr e) {
		SerializedStdType *type = serializedStdType<OverlayProtocol, Protocol>(e.v);
		type->add(S("dir"), StormInfo<Url>::type(e.v));
		return type;
	}

	OverlayProtocol *OverlayProtocol::read(ObjIStream *from) {
		return (OverlayProtocol *)from->readClass(StormInfo<OverlayProtocol>::type(from->engine()));
	}

	void OverlayProtocol::write(ObjOStream *to) {
		if (to->startClass(StormInfo<OverlayProtocol>::type(engine()), this)) {
			Protocol::write(to);
			dir->write(to);
			to->end();
		}
	}

	OverlayProtocol::OverlayProtocol(ObjIStream *from) {
		dir = new (this) Url(from);
		// Note: Serialization does not save files in the memory protocol, just like paths are not
		// transferred between different systems.
		files = new (this) Map<Str *, Buffer>();
		from->end();
	}

}
