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

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

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

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

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


package javax.microedition.rms;

final class Enumerator extends Object
		implements RecordListener, RecordEnumeration
{
	private boolean destroyed;
	private boolean nowCreated;
	private boolean autoRebuild;
	private int current;
	private int count;
	private Record[] records;
	private RecordStore store;
	private RecordFilter filter;
	private RecordComparator comparator;
	private Object lock;

	Enumerator(RecordStore store, RecordFilter filter, RecordComparator comparator,
			boolean autoRebuild)
	{
		this.destroyed = false;
		this.nowCreated = true;
		this.autoRebuild = autoRebuild;
		this.store = store;
		this.filter = filter;
		this.comparator = comparator;
		this.lock = new Object();
		rebuild();
		if(!autoRebuild)
		{
			return;
		}
		store.addRecordListener(this);
	}

	public void recordAdded(RecordStore store, int recordId)
	{
		if(store != this.store)
		{
			return;
		}
		rebuild();
	}

	public void recordDeleted(RecordStore store, int recordId)
	{
		if(store != this.store)
		{
			return;
		}
		rebuild();
	}

	public void recordChanged(RecordStore store, int recordId)
	{
		if(store != this.store)
		{
			return;
		}
		rebuild();
	}

	public void destroy()
	{
		synchronized(lock)
		{
			checkDestroyed();
			store.removeRecordListener(this);
			destroyed = true;
			nowCreated = false;
			autoRebuild = false;
			current = 0;
			count = 0;
			records = null;
			store = null;
			filter = null;
			comparator = null;
		}
	}

	public void rebuild()
	{
		boolean opened;
		int i;
		int j;
		int sourceCount;
		int rebuildedCount;
		Record[] source;
		Record[] rebuilded;
		Record r1;
		Record r2;
		RecordStore store;
		RecordFilter filter;
		RecordComparator comparator;
		Object lock;
		synchronized(lock = this.lock)
		{
			checkDestroyed();
			store = this.store;
			filter = this.filter;
			comparator = this.comparator;
		}
		synchronized(RecordStore.getMonitor())
		{
			if(opened = store.isRecordStoreOpened())
			{
				sourceCount = store.getRecordsCount();
				source = store.getRecords();
			} else
			{
				sourceCount = 0;
				source = null;
			}
		}
		if(source == null || !opened)
		{
			return;
		}
		rebuildedCount = 0;
		rebuilded = new Record[sourceCount];
		try
		{
			for(i = 0; i < sourceCount; i++)
			{
				if((r1 = source[i]) != null && filter.matches(r1.getData()))
				{
					rebuilded[rebuildedCount++] = r1;
				}
			}
			for(i = 0; i < rebuildedCount - 1; i++)
			{
				r1 = rebuilded[i];
				for(j = i + 1; j < rebuildedCount; j++)
				{
					r2 = rebuilded[j];
					if(comparator.compare(r2.getData(), r1.getData()) < 0)
					{
						rebuilded[i] = r2;
						rebuilded[j] = r1;
						r1 = r2;
					}
				}
			}
		}
		catch(RuntimeException e)
		{
			e.printStackTrace();
			return;
		}
		synchronized(lock)
		{
			checkDestroyed();
			nowCreated = true;
			count = rebuildedCount;
			records = rebuilded;
		}
	}

	public void reset()
	{
		synchronized(lock)
		{
			checkDestroyed();
			nowCreated = true;
		}
	}

	public void keepUpdated(boolean autoRebuild)
	{
		synchronized(lock)
		{
			checkDestroyed();
			if(this.autoRebuild != autoRebuild)
			{
				if(this.autoRebuild = autoRebuild)
				{
					store.addRecordListener(this);
				} else
				{
					store.removeRecordListener(this);
				}
			}
		}
		if(autoRebuild)
		{
			rebuild();
		}
	}

	public boolean isKeptUpdated()
	{
		checkDestroyed();
		return autoRebuild;
	}

	public boolean hasNextElement()
	{
		boolean result;
		synchronized(lock)
		{
			checkDestroyed();
			result = nowCreated ? count > 0 : current < count - 1;
		}
		return result;
	}

	public boolean hasPreviousElement()
	{
		boolean result;
		synchronized(lock)
		{
			checkDestroyed();
			result = nowCreated ? count > 0 : current > 0;
		}
		return result;
	}

	public int numRecords()
	{
		checkDestroyed();
		return count;
	}

	public int nextRecordId()
			throws InvalidRecordIDException
	{
		int result;
		synchronized(lock)
		{
			checkDestroyed();
			result = records[checkAvailableRecord(true)].getRecordID();
		}
		return result;
	}

	public int previousRecordId()
			throws InvalidRecordIDException
	{
		int result;
		synchronized(lock)
		{
			checkDestroyed();
			result = records[checkAvailableRecord(false)].getRecordID();
		}
		return result;
	}

	public byte[] nextRecord()
			throws RecordStoreException, RecordStoreNotOpenException, InvalidRecordIDException
	{
		int length;
		byte[] data;
		byte[] result;
		synchronized(lock)
		{
			checkDestroyed();
			checkOpenedRecordStore();
			Array.copy(data = records[checkAvailableRecord(true)].getData(), 0,
					result = new byte[length = data.length], 0, length);
		}
		return result;
	}

	public byte[] previousRecord()
			throws RecordStoreException, RecordStoreNotOpenException, InvalidRecordIDException
	{
		int length;
		byte[] data;
		byte[] result;
		synchronized(lock)
		{
			checkDestroyed();
			checkOpenedRecordStore();
			Array.copy(data = records[checkAvailableRecord(false)].getData(), 0,
					result = new byte[length = data.length], 0, length);
		}
		return result;
	}

	private void checkDestroyed()
	{
		if(destroyed)
		{
			throw new IllegalStateException("RecordEnumeration: " +
					"использование после вызова метода destroy().");
		}
	}

	private void checkOpenedRecordStore()
			throws RecordStoreNotOpenException
	{
		RecordStore store;
		if(!(store = this.store).isRecordStoreOpened())
		{
			throw new RecordStoreNotOpenException("RecordEnumeration: " +
					"хранилище записей " + store.getRecordStoreName() + " не открыто.");
		}
	}

	private int checkAvailableRecord(boolean moveToNext)
			throws InvalidRecordIDException
	{
		int cnt = count;
		int curr;
		int result;
		if(nowCreated)
		{
			if(cnt == 0)
			{
				throw new InvalidRecordIDException("RecordEnumeration: " +
						"в перечислении больше не осталось записей.");
			}
			nowCreated = false;
			result = current = moveToNext ? 0 : cnt - 1;
		} else
		{
			if((curr = current) == (moveToNext ? cnt - 1 : 0))
			{
				throw new InvalidRecordIDException("RecordEnumeration: " +
						"в перечислении больше не осталось записей.");
			}
			result = current = moveToNext ? curr + 1 : curr - 1;
		}
		return result;
	}
}
