#include "stdafx.h"
#include "Visibility.h"
#include "Named.h"
#include "Type.h"
#include "Package.h"
#include "Engine.h"

namespace storm {

	/**
	 * Utilities.
	 */

	// Find the first parent of 'at' that is of type T. This includes 'at' itself.
	template <class T>
	static MAYBE(T *) firstOf(NameLookup *at) {
		T *result = as<T>(at);
		while (at && !result) {
			at = at->parent();
			result = as<T>(at);
		}
		return result;
	}

	// Fidn the first parent of 'at' that is of type T, excluding 'at'.
	template <class T>
	static MAYBE(T *) firstParent(NameLookup *at) {
		if (at)
			return firstOf<T>(at->parent());
		else
			return null;
	}


	/**
	 * Base class.
	 */

	Visibility::Visibility() {}

	Bool Visibility::visible(Named *check, NameLookup *source) {
		assert(false, L"Override Visibility::visible!");
		return false;
	}


	/**
	 * Public.
	 */

	Public::Public() {}

	Bool Public::visible(Named *check, NameLookup *source) {
		return true;
	}

	void Public::toS(StrBuf *to) const {
		*to << S("public");
	}


	/**
	 * Private within types.
	 */

	TypePrivate::TypePrivate() {}

	Bool TypePrivate::visible(Named *check, NameLookup *source) {
		Type *type = firstParent<Type>(check);
		return source->hasParent(type);
	}

	void TypePrivate::toS(StrBuf *to) const {
		*to << S("private");
	}


	/**
	 * Protected.
	 */

	TypeProtected::TypeProtected() {}

	Bool TypeProtected::visible(Named *check, NameLookup *source) {
		Type *type = firstParent<Type>(check);
		Type *src = firstOf<Type>(source);

		if (!type || !src)
			return false;

		return src->isA(type);
	}

	void TypeProtected::toS(StrBuf *to) const {
		*to << S("protected");
	}


	/**
	 * Private within a package.
	 */

	PackagePrivate::PackagePrivate() {}

	Bool PackagePrivate::visible(Named *check, NameLookup *source) {
		Package *package = firstParent<Package>(check);
		return source->hasParent(package);
	}

	void PackagePrivate::toS(StrBuf *to) const {
		*to << S("package private");
	}


	/**
	 * Private within a file.
	 */

	FilePrivate::FilePrivate() {}

	static SrcPos findPos(NameLookup *source) {
		while (source) {
			if (LookupPos *n = as<LookupPos>(source))
				return n->pos;

			source = source->parent();
		}

		return SrcPos();
	}

	Bool FilePrivate::visible(Named *check, NameLookup *source) {
		SrcPos src = findPos(source);

		// If both come from unknown files, they are probably not from the same file.
		if (!check->pos.file || !src.file)
			return false;

		// If they are the same object, we don't need the expensive equal-check.
		if (check->pos.file == src.file)
			return true;
		return *check->pos.file == *src.file;
	}

	void FilePrivate::toS(StrBuf *to) const {
		*to << S("file private");
	}


	/**
	 * Object access.
	 */

	Visibility *STORM_NAME(allPublic, public)(EnginePtr e) {
		return e.v.visibility(Engine::vPublic);
	}

	Visibility *typePrivate(EnginePtr e) {
		return e.v.visibility(Engine::vTypePrivate);
	}

	Visibility *typeProtected(EnginePtr e) {
		return e.v.visibility(Engine::vTypeProtected);
	}

	Visibility *packagePrivate(EnginePtr e) {
		return e.v.visibility(Engine::vPackagePrivate);
	}

	Visibility *filePrivate(EnginePtr e) {
		return e.v.visibility(Engine::vFilePrivate);
	}

}
