#pragma once
#include <climits>
#include <limits>
#include "GcType.h"
#include "Utils/InlineAtomics.h"

namespace storm {

	// Mask for the count of an array.
	static inline size_t arrayCountMask() {
		return std::numeric_limits<size_t>::max() >> 1;
	}

	/**
	 * Helper struct to access gc:d arrays. The preprocessor recognices this class, so it is
	 * properly marked for the gc.
	 */
	template <class T>
	struct GcArray {
		// Number of elements. MSB is a tag to see if the array has a bitmap of live elements to be
		// used during destruction (designed to maximize compatibility with existing code).
		const size_t countI;

		// Number of elements. Set on allocation and then not allowed to change.
		inline size_t count() const {
			// return countI & arrayCountMask();
			// This seems to be a tiny bit faster:
			return (countI << 1) >> 1;
		}

		// Number of used elements. Ignored by the GC, but is used if the type in the array has a
		// finalizer. If 'hasBitmap' returns false, then elements 0..filled are finalized.
		// Otherwise, the elements for which 'bitmapGet' returns true are finalized. Both the bitmap
		// and 'filled' are cleared once the object is finalized.
		size_t filled;

		// Data.
		T v[1];

		// Check if a bitmap exists.
		inline bool hasBitmap() const {
			return (countI & ~arrayCountMask()) != 0;
		}

		// For arrays that have a bitmap (1 bit/element) for finalization, access the elements using
		// these functions.
		inline void bitmapSet(size_t id, bool value) {
			void *start = &v[count()];
			byte &element = ((byte *)start)[id / CHAR_BIT];
			byte mask = 1 << (id % CHAR_BIT);
			if (value)
				element |= mask;
			else
				element &= ~mask;
		}

		inline bool bitmapGet(size_t id) const {
			const void *start = &v[count()];
			byte element = ((const byte *)start)[id / CHAR_BIT];
			return ((element >> (id % CHAR_BIT)) & 0x1) != 0x0;
		}

	};

	// Functions to access the bitmap inside an array without having a proper template instantiation.
	static inline void arrayBitmapSet(void *array, const GcType *type, size_t id, bool value) {
		GcArray<byte> *byteArray = (GcArray<byte> *)array;
		byte &element = byteArray->v[byteArray->count() * type->stride + (id / CHAR_BIT)];
		byte mask = 1 << (id % CHAR_BIT);
		if (value)
			element |= mask;
		else
			element &= ~mask;
	}

	static inline void arrayBitmapSet(void *array, const GcType *type, size_t start, size_t end, bool value) {
		if (end <= start)
			return;

		GcArray<byte> *byteArray = (GcArray<byte> *)array;
		byte *bitmap = &byteArray->v[byteArray->count() * type->stride];

		size_t startByte = start / CHAR_BIT;
		size_t startBit = start % CHAR_BIT;
		size_t endByte = end / CHAR_BIT;
		size_t endBit = end % CHAR_BIT;

		if (startByte == endByte) {
			// Special case, only modify things inside a single byte.
			Nat mask = (Nat(1) << (1 + endBit - startBit)) - 1;
			mask <<= startBit;

			if (value) {
				bitmap[startByte] |= mask;
			} else {
				bitmap[startByte] &= ~mask;
			}
		} else {
			// Normal case, fill first and last bytes individually, do the rest using memset.
			Nat startMask = Nat(0xFF) << startBit;
			Nat endMask = (Nat(1) << (endBit + 1)) - 1;

			if (value) {
				bitmap[startByte] |= startMask;
				bitmap[endByte] |= endMask;
			} else {
				bitmap[startByte] &= ~startMask;
				bitmap[endByte] &= ~endMask;
			}

			if (startByte + 1 < endByte) {
				memset(&bitmap[startByte + 1], 0xFF * value, endByte - startByte - 1);
			}
		}
	}

	static inline bool arrayBitmapGet(const void *array, const GcType *type, size_t id) {
		const GcArray<byte> *byteArray = (const GcArray<byte> *)array;
		byte element = byteArray->v[byteArray->count() * type->stride + (id / CHAR_BIT)];
		return ((element >> (id % CHAR_BIT)) & 0x1) != 0x0;
	}

	static inline void arrayBitmapClear(void *array, const GcType *type) {
		GcArray<byte> *byteArray = (GcArray<byte> *)array;
		size_t memSize = (byteArray->count() + CHAR_BIT - 1) / CHAR_BIT;
		memset(&byteArray->v[byteArray->count() * type->stride], 0, memSize);
	}

	static inline void arrayBitmapCopy(void *to, const void *from, const GcType *type) {
		GcArray<byte> *toByte = (GcArray<byte> *)to;
		const GcArray<byte> *fromByte = (const GcArray<byte> *)from;
		size_t memSize = (min(toByte->count(), fromByte->count()) + CHAR_BIT - 1) / CHAR_BIT;
		memcpy(toByte->v, fromByte->v, memSize);
	}

	/**
	 * Variant used to pre-allocate a GcArray of a certain size somewhere.
	 */
	template <class T, size_t size>
	struct GcPreArray {
		const size_t countI;
		size_t filled;
		T v[size];

		size_t count() const {
			// return countI & GcArray<T>::arrayCountMask();
			return (countI << 1) >> 1;
		}

		GcPreArray() : countI(size & arrayCountMask()), filled(0) {}
		// Specify a size that is possibly smaller thant the desired size.
		explicit GcPreArray(size_t count) : countI(min(size, count)), filled(0) {}
		operator GcArray<T> *() const { return (GcArray<T> *)this; }
	};

	/**
	 * A GcArray of weak pointers.
	 */
	template <class T>
	struct GcWeakArray {
		// Number of elements. Tagged so the GC does not think it is a pointer.
		const size_t countI;

		// Number of splatted elements since reset. Tagged.
		size_t splattedI;

		// Data. Note: it is a good idea to *read* this using atomics/readOnce. Otherwise the
		// compiler may choose to read the memory more than once, resulting in a possible splat
		// between checking for null and reading the value a second time.
		T *v[1];

		// Number of elements.
		inline size_t count() const { return countI >> 1; }

		// Splatted elements.
		inline size_t splatted() const { return readOnce(splattedI) >> 1; }
		inline void splatted(size_t v) { writeOnce(splattedI, (v << 1) | 0x1); }

		// Read elements safely.
		inline T *read(size_t id) const { return readOnce(v[id]); }

		// Write elements.
		inline void write(size_t id, T *value) { return writeOnce(v[id], value); }
	};


	// Some common GcTypes for arrays, so we don't need to have multiple definitions of them hanging around.
	extern const GcType pointerArrayType;
	extern const GcType sizeArrayType; // size_t
	extern const GcType wordArrayType;
	extern const GcType natArrayType;
	extern const GcType wcharArrayType;
	extern const GcType byteArrayType;
}
