/*
	Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
	и других спецификаций для функционирования компактных приложений на языке
	Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

	Copyright © 2016, 2019 Малик Разработчик

	Это свободная программа: вы можете перераспространять ее и/или изменять
	ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
	в каком она была опубликована Фондом свободного программного обеспечения;
	либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

	Эта программа распространяется в надежде, что она будет полезной,
	но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
	или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
	общественной лицензии GNU.

	Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
	вместе с этой программой. Если это не так, см.
	<https://www.gnu.org/licenses/>.
*/


package java.lang;

public final class Memory extends Object
{
	private static final int OFFSET_MONITOR = -0x04;
	private static final int OFFSET_OBJCLASS = +0x04;
	private static final int OFFSET_LENGTH = +0x08;
	private static final int OFFSET_DISPLACEMENT = +0x0c;
	private static final int OFFSET_CONTENT = +0x10;
	private static final int REF_TO_BLOCK = -0x04;
	private static final int BLOCK_TO_REF = -REF_TO_BLOCK;
	private static final int SIZE_HEAD_OBJECT = 0x0c;
	private static final int SIZE_HEAD_ARRAY = SIZE_HEAD_OBJECT + 0x08;

	private static boolean MULTI_THREADED;
	private static boolean RESERVE_ACTIVE;
	private static int STRING_POOL_OFFSET; /* СИСТЕМНОЕ ПОЛЕ */
	private static int DESCRIPTORS_SIZE; /* СИСТЕМНОЕ ПОЛЕ */
	private static int HEAP_LIMIT; /* СИСТЕМНОЕ ПОЛЕ */
	private static int HEAP_BEGIN;
	private static int HEAP_RESERVE;
	private static int DESCRIPTORS_COUNT;
	private static int DESCRIPTORS_RESERVE;
	private static long COLLECTED_BYTES;
	private static int[] FINALIZING_REF_STACK;
	private static long[] DESCRIPTORS;
	private static OutOfMemoryError LACK_MEMORY_ERROR;
	private static NoClassDefFoundError NOT_DEFINED_CLASS_ERROR;

	static
	{
		int len;
		int ref;
		int block = (len = STRING_POOL_OFFSET) + 0x04;
		for(int splen = MalikSystem.getIntAt(len), i = 0; i < splen; i++)
		{
			block += needMemory(MalikSystem.getIntAt(block + (BLOCK_TO_REF + OFFSET_LENGTH)));
		}
		ref = (block += -block & 0x0f) + BLOCK_TO_REF;
		MalikSystem.setIntAt(ref, 0);
		MalikSystem.setIntAt(ref + OFFSET_MONITOR, 0);
		MalikSystem.setIntAt(ref + OFFSET_OBJCLASS, 0);
		MalikSystem.setObjectAt(ref + OFFSET_OBJCLASS, MalikSystem.getClassInstance("[J"));
		MalikSystem.setIntAt(ref + OFFSET_LENGTH, len = (DESCRIPTORS_SIZE - SIZE_HEAD_ARRAY) >> 3);
		MalikSystem.setIntAt(ref + OFFSET_DISPLACEMENT, 0);
		block += (len << 3) + SIZE_HEAD_ARRAY;
		MULTI_THREADED = false;
		RESERVE_ACTIVE = false;
		HEAP_BEGIN = (block += (-block & 0x0f));
		HEAP_RESERVE = (HEAP_LIMIT - HEAP_BEGIN) / 20;
		DESCRIPTORS_COUNT = 0;
		DESCRIPTORS_RESERVE = len - len / 20;
		COLLECTED_BYTES = 0L;
		DESCRIPTORS = (long[]) MalikSystem.convertToObject(ref);
	}

	public static int getFree()
	{
		boolean thread;
		boolean status;
		int result;
		status = (thread = MULTI_THREADED) && MalikSystem.enterMonopolyAccess();
		try
		{
			result = free();
		}
		finally
		{
			if(thread) MalikSystem.leaveMonopolyAccess(status);
		}
		return result;
	}

	public static int getTotal()
	{
		return HEAP_LIMIT - HEAP_BEGIN;
	}

	public static long getCollected()
	{
		return COLLECTED_BYTES;
	}

	public static Object[] getAllObjects()
	{
		boolean thread;
		boolean status;
		Object[] result;
		status = (thread = MULTI_THREADED) && MalikSystem.enterMonopolyAccess();
		try
		{
			int rlen;
			result = new Object[rlen = DESCRIPTORS_COUNT - MalikSystem.arrayfindf_int(FINALIZING_REF_STACK, 0, 0)];
			for(int slen = FINALIZING_REF_STACK.length, dlen = DESCRIPTORS_COUNT, i, j = i = 0; i < rlen && j < dlen; j++)
			{
				int ref = (int) DESCRIPTORS[j] + BLOCK_TO_REF;
				Object reference;
				if(MalikSystem.arrayfindf_int(FINALIZING_REF_STACK, 0, ref) >= slen && (reference = MalikSystem.convertToObject(ref)) != result)
				{
					result[i++] = reference;
				}
			}
		}
		finally
		{
			if(thread) MalikSystem.leaveMonopolyAccess(status);
		}
		return result;
	}

	static void completeInit()
	{
		FINALIZING_REF_STACK = new int[0x20];
		LACK_MEMORY_ERROR = new OutOfMemoryError("Невозможно создать новый объект: динамическая память заполнена.");
		NOT_DEFINED_CLASS_ERROR = new NoClassDefFoundError("Невозможно создать новый объект: класс создаваемого объекта не указан.");
	}

	static void setMultiThreaded()
	{
		MULTI_THREADED = true;
	}

	static void checkSize(long refSize) throws OutOfMemoryError
	{
		if(refSize > 0x000000007fffffecL)
		{
			throw LACK_MEMORY_ERROR;
		}
	}

	static boolean isHeapAddress(int beginAddress, int endAddress)
	{
		return (beginAddress > STRING_POOL_OFFSET || endAddress > STRING_POOL_OFFSET) && (beginAddress < HEAP_LIMIT || endAddress < HEAP_LIMIT);
	}

	static int getStringPoolOffset()
	{
		return STRING_POOL_OFFSET;
	}

	static long collectGarbage()
	{
		long result = 0L;
		long lastRunTime = System.currentTimeMillis() - 1000L;
		label0: for(int i = 0; i < DESCRIPTORS_COUNT; i++)
		{
			boolean found;
			boolean status;
			int esp;
			int ebp;
			int ref;
			int block;
			long deallocated;
			long elapsedTime;
			long currentTime;

			/* Примерно каждую секунду удаляем все объекты с нулевым количеством ссылок на них */
			if((elapsedTime = (currentTime = System.currentTimeMillis()) - lastRunTime) >= 1000L || elapsedTime < 0L)
			{
				for(lastRunTime = currentTime, block = ref = 0; ; )
				{
					status = MalikSystem.enterMonopolyAccess();
					try
					{
						int index;
						if(found = (index = DESCRIPTORS_COUNT - 1) >= 0 && (index = MalikSystem.findzerob(DESCRIPTORS, index, BLOCK_TO_REF)) >= 0)
						{
							FINALIZING_REF_STACK[0] = ref = (block = (int) DESCRIPTORS[index]) + BLOCK_TO_REF;
						}
					}
					finally
					{
						MalikSystem.leaveMonopolyAccess(status);
					}
					if(!found) break;
					try
					{
						MalikSystem.convertToObject(ref).$finalize$();
					}
					catch(Throwable e)
					{
					}
					status = MalikSystem.enterMonopolyAccess();
					try
					{
						deallocated = (long) deallocate(MalikSystem.blockfindb(DESCRIPTORS, DESCRIPTORS_COUNT - 1, block));
						FINALIZING_REF_STACK[0] = 0;
					}
					finally
					{
						MalikSystem.leaveMonopolyAccess(status);
					}
					result += deallocated;
					COLLECTED_BYTES += deallocated;
				}
			}

			/* В остальное время: */
			/* 1. Удаляем объект, все ссылки на который ведут только из него самого */
			for(; ; )
			{
				if((ref = (block = (int) DESCRIPTORS[i]) + BLOCK_TO_REF) == BLOCK_TO_REF) break label0;
				if(StringPool.isMyOwnedObject(MalikSystem.convertToObject(ref))) continue label0;
				status = MalikSystem.enterMonopolyAccess();
				try
				{
					if(found = MalikSystem.getIntAt(ref) == MalikSystem.convertToObject(ref).getQuantityOfReferencesTo(ref))
					{
						FINALIZING_REF_STACK[0] = ref;
					}
				}
				finally
				{
					MalikSystem.leaveMonopolyAccess(status);
				}
				if(!found) break;
				try
				{
					MalikSystem.convertToObject(ref).$finalize$();
				}
				catch(Throwable e)
				{
				}
				status = MalikSystem.enterMonopolyAccess();
				try
				{
					deallocated = (long) deallocate(MalikSystem.blockfindb(DESCRIPTORS, DESCRIPTORS_COUNT - 1, block));
					FINALIZING_REF_STACK[0] = 0;
				}
				finally
				{
					MalikSystem.leaveMonopolyAccess(status);
				}
				result += deallocated;
				COLLECTED_BYTES += deallocated;
			}

			/* 2. Удаляем «острова изоляции» (группа объектов, которые ссылаются друг на друга и не имеют других ссылок на себя) */
			if((ref = (int) DESCRIPTORS[i] + BLOCK_TO_REF) == BLOCK_TO_REF) break label0;
			if(StringPool.isMyOwnedObject(MalikSystem.convertToObject(ref))) continue label0;
			for(esp = 1, FINALIZING_REF_STACK[ebp = 0] = ref; ebp < esp; ebp++)
			{
				int refCount = 0;
				ref = FINALIZING_REF_STACK[ebp];
				for(int j = 0; j < DESCRIPTORS_COUNT; j++)
				{
					int count;
					int refAnot;
					if((j & 0x03ff) == 0x03ff && ((elapsedTime = (currentTime = System.currentTimeMillis()) - lastRunTime) >= 1000L || elapsedTime < 0L))
					{
						for(lastRunTime = currentTime, block = refAnot = 0; ; )
						{
							status = MalikSystem.enterMonopolyAccess();
							try
							{
								int index;
								found = (index = DESCRIPTORS_COUNT - 1) >= 0 && (index = MalikSystem.findzerob(DESCRIPTORS, index, BLOCK_TO_REF)) >= 0 && push(refAnot = (block = (int) DESCRIPTORS[index]) + BLOCK_TO_REF, esp);
							}
							finally
							{
								MalikSystem.leaveMonopolyAccess(status);
							}
							if(!found) break;
							try
							{
								MalikSystem.convertToObject(refAnot).$finalize$();
							}
							catch(Throwable e)
							{
							}
							status = MalikSystem.enterMonopolyAccess();
							try
							{
								deallocated = (long) deallocate(MalikSystem.blockfindb(DESCRIPTORS, DESCRIPTORS_COUNT - 1, block));
								FINALIZING_REF_STACK[esp] = 0;
							}
							finally
							{
								MalikSystem.leaveMonopolyAccess(status);
							}
							result += deallocated;
							COLLECTED_BYTES += deallocated;
						}
					}
					if((refAnot = (int) DESCRIPTORS[j] + BLOCK_TO_REF) == BLOCK_TO_REF) break;
					status = MalikSystem.enterMonopolyAccess();
					try
					{
						if(found = (count = MalikSystem.convertToObject(refAnot).getQuantityOfReferencesTo(ref)) > 0)
						{
							if(push(refAnot, esp)) esp++;
						}
					}
					finally
					{
						MalikSystem.leaveMonopolyAccess(status);
					}
					if(found) refCount += count;
				}
				if(refCount < MalikSystem.getIntAt(ref))
				{
					MalikSystem.arrayfill_int(FINALIZING_REF_STACK, 0, esp, 0);
					continue label0;
				}
			}
			for(int j = esp; j-- > 0; )
			{
				try
				{
					MalikSystem.convertToObject(FINALIZING_REF_STACK[j]).$finalize$();
				}
				catch(Throwable e)
				{
				}
			}
			for(int j = esp; j-- > 0; )
			{
				status = MalikSystem.enterMonopolyAccess();
				try
				{
					deallocated = (long) deallocate(MalikSystem.blockfindb(DESCRIPTORS, DESCRIPTORS_COUNT - 1, FINALIZING_REF_STACK[j] + REF_TO_BLOCK));
					if(j == 0) MalikSystem.arrayfill_int(FINALIZING_REF_STACK, 0, esp, 0);
				}
				finally
				{
					MalikSystem.leaveMonopolyAccess(status);
				}
				result += deallocated;
				COLLECTED_BYTES += deallocated;
			}
		}
		return result;
	}

	static Object intern(Object instance)
	{
		boolean thread;
		boolean status;
		Object result;
		Class instanceClass;
		if((result = instance) == null) return null;
		instanceClass = instance.getClass();
		status = (thread = MULTI_THREADED) && MalikSystem.enterMonopolyAccess();
		try
		{
			for(int slen = FINALIZING_REF_STACK.length, i = 0; i < DESCRIPTORS_COUNT; i++)
			{
				int ref = (int) DESCRIPTORS[i] + BLOCK_TO_REF;
				if(MalikSystem.arrayfindf_int(FINALIZING_REF_STACK, 0, ref) >= slen && MalikSystem.getObjectAt(ref + OFFSET_OBJCLASS) == instanceClass && MalikSystem.convertToObject(ref).equals(instance))
				{
					result = MalikSystem.convertToObject(ref);
					break;
				}
			}
		}
		finally
		{
			if(thread) MalikSystem.leaveMonopolyAccess(status);
		}
		return result;
	}

	static Object internIgnore(Object instance)
	{
		boolean thread;
		boolean status;
		Object result;
		Class instanceClass;
		if((result = instance) == null) return null;
		instanceClass = instance.getClass();
		status = (thread = MULTI_THREADED) && MalikSystem.enterMonopolyAccess();
		try
		{
			for(int slen = FINALIZING_REF_STACK.length, i = 0; i < DESCRIPTORS_COUNT; i++)
			{
				int ref = (int) DESCRIPTORS[i] + BLOCK_TO_REF;
				if(MalikSystem.arrayfindf_int(FINALIZING_REF_STACK, 0, ref) >= slen && ref != MalikSystem.convertToReference(instance) && MalikSystem.getObjectAt(ref + OFFSET_OBJCLASS) == instanceClass && MalikSystem.convertToObject(ref).equals(instance))
				{
					result = MalikSystem.convertToObject(ref);
					break;
				}
			}
		}
		finally
		{
			if(thread) MalikSystem.leaveMonopolyAccess(status);
		}
		return result;
	}

	static Object allocateInstanceOf(Class instanceClass, int refSize, int arrayLength) throws OutOfMemoryError, NoClassDefFoundError
	{
		boolean thread;
		boolean status;
		int blockSize;
		Object result;
		if(instanceClass == null)
		{
			throw NOT_DEFINED_CLASS_ERROR;
		}
		if(refSize <= 0)
		{
			return null;
		}
		blockSize = refSize - REF_TO_BLOCK;
		refSize = (blockSize += -blockSize & 0x0f) - BLOCK_TO_REF;
		status = (thread = MULTI_THREADED) && MalikSystem.enterMonopolyAccess();
		try
		{
			result = makeObject(instanceClass, allocate(blockSize) + BLOCK_TO_REF, refSize, arrayLength);
		}
		finally
		{
			if(thread) MalikSystem.leaveMonopolyAccess(status);
		}
		return result;
	}

	private static void outOfMemory() throws OutOfMemoryError
	{
		if(RESERVE_ACTIVE)
		{
			/* Выходим из программы, если резерв памяти был исчерпан */
			MalikSystem.syscall((long) Thread.MAIN_THREAD_ID, 0x0001);
		}
		RESERVE_ACTIVE = true;
		throw LACK_MEMORY_ERROR;
	}

	private static boolean push(int ref, int top)
	{
		if(MalikSystem.arrayfindf_int(FINALIZING_REF_STACK, 0, ref) < top)
		{
			return false;
		}
		if(top == FINALIZING_REF_STACK.length)
		{
			MalikSystem.arraycopyf_int(FINALIZING_REF_STACK, 0, FINALIZING_REF_STACK = new int[top << 1], 0, top);
		}
		FINALIZING_REF_STACK[top] = ref;
		return true;
	}

	private static int free()
	{
		int i;
		long descriptor;
		return HEAP_LIMIT - ((i = DESCRIPTORS_COUNT - 1) >= 0 ? (int) (descriptor = DESCRIPTORS[i]) + (int) (descriptor >> 32) : HEAP_BEGIN);
	}

	private static int allocate(int blockSize) throws OutOfMemoryError
	{
		int i;
		int result;
		long descriptor;
		if(RESERVE_ACTIVE && (DESCRIPTORS_COUNT < DESCRIPTORS_RESERVE || free() > HEAP_RESERVE)) RESERVE_ACTIVE = false;
		if(DESCRIPTORS_COUNT >= (RESERVE_ACTIVE ? DESCRIPTORS.length : DESCRIPTORS_RESERVE)) outOfMemory();
		if(DESCRIPTORS_COUNT == 0)
		{
			if(blockSize > HEAP_LIMIT - HEAP_BEGIN) outOfMemory();
			DESCRIPTORS[DESCRIPTORS_COUNT++] = makeDescriptor(HEAP_BEGIN, blockSize);
			return HEAP_BEGIN;
		}
		if((i = MalikSystem.findfreef(DESCRIPTORS, 0, blockSize)) > 0 && i < DESCRIPTORS_COUNT)
		{
			MalikSystem.arraycopyb_long(DESCRIPTORS, DESCRIPTORS_COUNT, DESCRIPTORS, DESCRIPTORS_COUNT + 1, DESCRIPTORS_COUNT++ - i);
			DESCRIPTORS[i] = makeDescriptor(result = (int) DESCRIPTORS[i] - blockSize, blockSize);
			return result;
		}
		i = DESCRIPTORS_COUNT - 1;
		if(blockSize > HEAP_LIMIT - (result = (int) (descriptor = DESCRIPTORS[i]) + (int) (descriptor >> 32))) outOfMemory();
		DESCRIPTORS[DESCRIPTORS_COUNT++] = makeDescriptor(result, blockSize);
		return result;
	}

	private static int deallocate(int blockIndex)
	{
		if(blockIndex >= 0)
		{
			int result = (int) (DESCRIPTORS[blockIndex] >> 32);
			MalikSystem.arraycopyf_long(DESCRIPTORS, blockIndex + 1, DESCRIPTORS, blockIndex, --DESCRIPTORS_COUNT - blockIndex);
			DESCRIPTORS[DESCRIPTORS_COUNT] = 0L;
			return result;
		}
		return 0;
	}

	private static int needMemory(int length)
	{
		return length + (-length & 3) + SIZE_HEAD_ARRAY;
	}

	private static long makeDescriptor(int block, int blockSize)
	{
		return (long) blockSize << 32 | (long) block & 0x00000000ffffffffL;
	}

	private static Object makeObject(Class instanceClass, int ref, int refSize, int arrayLength)
	{
		Object result;
		MalikSystem.setIntAt(ref, 0);
		MalikSystem.setIntAt(ref + OFFSET_MONITOR, 0);
		MalikSystem.setIntAt(ref + OFFSET_OBJCLASS, 0);
		MalikSystem.setObjectAt(ref + OFFSET_OBJCLASS, instanceClass);
		result = MalikSystem.convertToObject(ref);
		if(refSize > OFFSET_CONTENT)
		{
			int len;
			MalikSystem.setIntAt(ref + OFFSET_LENGTH, len = (refSize - 0x10) >> 2);
			MalikSystem.setIntAt(ref + OFFSET_DISPLACEMENT, 0);
			MalikSystem.arrayfill_int(result, 0, len, 0);
		}
		MalikSystem.setIntAt(ref + OFFSET_LENGTH, arrayLength >= 0 ? arrayLength : 0);
		return result;
	}


	private Memory()
	{
	}
}

final class StringPool extends Object
{
	private static final int LENGTH;
	private static final byte[][] CODES;
	private static final String[] STRINGS;

	static
	{
		int address;
		int length = MalikSystem.getIntAt(address = Memory.getStringPoolOffset());
		byte[][] codes;
		address += 0x04;
		LENGTH = length;
		CODES = codes = new byte[length][];
		STRINGS = new String[length];
		for(int i = 0; i < length; i++)
		{
			int size = (codes[i] = readArray(address)).length;
			address += size + (-size & 0x03) + 0x14;
		}
		Memory.completeInit();
		Thread.completeInit();
	}

	public static int getLength()
	{
		return LENGTH;
	}

	public static String getString(int index)
	{
		boolean flag;
		String[] strings;
		String result;
		if(index >= LENGTH || index < 0) return null;
		if((result = (strings = STRINGS)[index]) != null) return result;
		flag = MalikSystem.enterMonopolyAccess();
		try
		{
			if(strings[index] == null)
			{
				strings[index] = result = (String) Memory.internIgnore(new String(CODES[index]));
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(flag);
		}
		return result;
	}

	static boolean isMyOwnedObject(Object obj)
	{
		return obj == CODES || obj == STRINGS || Array.findf(STRINGS, 0, obj) < LENGTH;
	}

	static String intern(String string)
	{
		String[] strings = STRINGS;
		String result = string;
		for(int index = LENGTH; index-- > 0; )
		{
			String current;
			if((current = strings[index]) != null && current.equals(string))
			{
				result = current;
				break;
			}
		}
		return result;
	}

	private static byte[] readArray(int address)
	{
		return (byte[]) MalikSystem.convertToObject(address + 0x04);
	}


	private StringPool()
	{
	}
}
