/*
 *  MicroEmulator
 *  Copyright (C) 2001-2005 Bartek Teodorczyk <[email protected]>
 *  Copyright (C) 2018 Nikita Shakarun
 *
 *  It is licensed under the following two licenses as alternatives:
 *    1. GNU Lesser General Public License (the "LGPL") version 2.1 or any newer version
 *    2. Apache License (the "AL") Version 2.0
 *
 *  You may not use this file except in compliance with at least one of
 *  the above two licenses.
 *
 *  You may obtain a copy of the LGPL at
 *      http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
 *
 *  You may obtain a copy of the AL at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the LGPL or the AL for the specific language governing permissions and
 *  limitations.
 */

package javax.microedition.rms.impl;

import java.util.Collections;
import java.util.Vector;

import javax.microedition.rms.InvalidRecordIDException;
import javax.microedition.rms.RecordComparator;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordFilter;
import javax.microedition.rms.RecordListener;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;

public class RecordEnumerationImpl implements RecordEnumeration {
	private RecordStoreImpl recordStoreImpl;
	private RecordFilter filter;
	private RecordComparator comparator;
	private boolean keepUpdated;

	private Vector<EnumerationRecord> enumerationRecords = new Vector<>();
	private int currentRecord;

	private RecordListener recordListener = new RecordListener() {

		@Override
		public void recordAdded(RecordStore recordStore, int recordId) {
			rebuild();
		}

		@Override
		public void recordChanged(RecordStore recordStore, int recordId) {
			rebuild();
		}

		@Override
		public void recordDeleted(RecordStore recordStore, int recordId) {
			rebuild();
		}

	};

	public RecordEnumerationImpl(RecordStoreImpl recordStoreImpl, RecordFilter filter, RecordComparator comparator, boolean keepUpdated) {
		this.recordStoreImpl = recordStoreImpl;
		this.filter = filter;
		this.comparator = comparator;
		this.keepUpdated = keepUpdated;

		rebuild();

		if (keepUpdated) {
			recordStoreImpl.addRecordListener(recordListener);
		}
	}

	@Override
	public int numRecords() {
		return enumerationRecords.size();
	}

	@Override
	public byte[] nextRecord()
			throws InvalidRecordIDException, RecordStoreNotOpenException, RecordStoreException {
		if (!recordStoreImpl.isOpen()) {
			throw new RecordStoreNotOpenException();
		}

		if (currentRecord >= numRecords()) {
			throw new InvalidRecordIDException();
		}

		byte[] result = enumerationRecords.elementAt(currentRecord).value;
		currentRecord++;

		return result;
	}

	@Override
	public int nextRecordId()
			throws InvalidRecordIDException {
		if (currentRecord >= numRecords()) {
			throw new InvalidRecordIDException();
		}

		int result = enumerationRecords.elementAt(currentRecord).recordId;
		currentRecord++;

		return result;
	}

	@Override
	public byte[] previousRecord()
			throws InvalidRecordIDException, RecordStoreNotOpenException, RecordStoreException {
		if (!recordStoreImpl.isOpen()) {
			throw new RecordStoreNotOpenException();
		}
		if (currentRecord < 0) {
			throw new InvalidRecordIDException();
		}

		currentRecord--;

		return enumerationRecords.elementAt(currentRecord).value;
	}

	@Override
	public int previousRecordId()
			throws InvalidRecordIDException {
		if (currentRecord < 0) {
			throw new InvalidRecordIDException();
		}

		currentRecord--;

		return enumerationRecords.elementAt(currentRecord).recordId;
	}

	@Override
	public boolean hasNextElement() {
		if (currentRecord == numRecords()) {
			return false;
		} else {
			return true;
		}
	}

	@Override
	public boolean hasPreviousElement() {
		return currentRecord != 0;
	}

	@Override
	public void reset() {
		currentRecord = 0;
	}

	@Override
	public void rebuild() {
		enumerationRecords.removeAllElements();
		//
		// filter
		//
		synchronized (recordStoreImpl) {
			try {
				int recordId = 1;
				int i = 0;
				while (i < recordStoreImpl.getNumRecords()) {
					try {
						byte[] data = recordStoreImpl.getRecord(recordId);
						i++;
						if (filter != null && !filter.matches(data)) {
							recordId++;
							continue;
						}
						enumerationRecords.add(new EnumerationRecord(recordId, data));
					} catch (InvalidRecordIDException e) {
						e.printStackTrace();
					}
					recordId++;
				}
			} catch (RecordStoreException e) {
				e.printStackTrace();
			}
		}

		//
		// sort
		//
		if (comparator != null) {
			Collections.sort(enumerationRecords, (lhs, rhs) -> {
				int compare = comparator.compare(lhs.value, rhs.value);
				if (compare == RecordComparator.EQUIVALENT)
					return 0;
				else if (compare == RecordComparator.FOLLOWS)
					return 1;
				else
					return -1;
			});
		}
	}

	@Override
	public void keepUpdated(boolean keepUpdated) {
		if (keepUpdated) {
			if (!this.keepUpdated) {
				rebuild();
				recordStoreImpl.addRecordListener(recordListener);
			}
		} else {
			recordStoreImpl.removeRecordListener(recordListener);
		}

		this.keepUpdated = keepUpdated;
	}

	@Override
	public boolean isKeptUpdated() {
		return keepUpdated;
	}

	@Override
	public void destroy() {
	}

	class EnumerationRecord {
		int recordId;
		byte[] value;

		EnumerationRecord(int recordId, byte[] value) {
			this.recordId = recordId;
			this.value = value;
		}
	}
}