/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License 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 License for the specific language governing permissions and
 * limitations under the License.
 */
/* Generated by Together */

package ghidra.program.model.util;

import java.io.*;
import java.util.Iterator;
import java.util.NoSuchElementException;

import ghidra.program.model.address.*;
import ghidra.util.LongIterator;
import ghidra.util.datastruct.NoSuchIndexException;
import ghidra.util.map.ValueMap;

/**
 * PropertyMap is used to store values for a fixed property at
 *   address locations given as longs. The values for the property
 *   must be homogeneous, i.e. all have the same type, and are
 *   determined by which subclass of PropertyMap is instantiated.
 *   For any long the property
 *   manager can be used to tell if the property exists there and
 *   what its value is. It also maintains information that allows it
 *   to efficiently search for the next and previous occurrence of the
 *   property relative to a given address.
 *   The subclass provides the createPage() method that dictates
 *  the type of PropertyPage that will be managed.
 *  @param <T> property value type
 */
public abstract class DefaultPropertyMap<T> implements PropertyMap<T> {

	protected ValueMap<T> propertyMgr;
	protected AddressMapImpl addrMap;
	protected String description;

	/**
	 * Construct a PropertyMap
	 * @param propertyMgr property manager that manages storage of
	 * properties
	 */
	public DefaultPropertyMap(ValueMap<T> propertyMgr) {
		this.propertyMgr = propertyMgr;
		this.addrMap = new AddressMapImpl();
	}

	@Override
	public String getName() {
		return propertyMgr.getName();
	}

	/**
	 * Set the description for this property.
	 *
	 * @param description property description
	 */
	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Return the property description.
	 * 
	 * @return the property description
	 */
	public String getDescription() {
		return description;
	}

	@Override
	public boolean intersects(Address start, Address end) {
		return propertyMgr.intersects(addrMap.getKey(start), addrMap.getKey(end));
	}

	@Override
	public boolean intersects(AddressSetView set) {
		AddressRangeIterator ranges = set.getAddressRanges();
		while (ranges.hasNext()) {
			AddressRange range = ranges.next();
			if (intersects(range.getMinAddress(), range.getMaxAddress())) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean removeRange(Address start, Address end) {
		return propertyMgr.removeRange(addrMap.getKey(start), addrMap.getKey(end));
	}

	@Override
	public boolean remove(Address addr) {
		return propertyMgr.remove(addrMap.getKey(addr));
	}

	@Override
	public boolean hasProperty(Address addr) {
		return propertyMgr.hasProperty(addrMap.getKey(addr));

	}

	@Override
	public Address getNextPropertyAddress(Address addr) {

		try {
			long index = propertyMgr.getNextPropertyIndex(addrMap.getKey(addr));
			return addrMap.decodeAddress(index);
		}
		catch (NoSuchIndexException e) {
		}
		return null;

	}

	@Override
	public Address getPreviousPropertyAddress(Address addr) {
		try {
			long index = propertyMgr.getPreviousPropertyIndex(addrMap.getKey(addr));
			return addrMap.decodeAddress(index);
		}
		catch (NoSuchIndexException e) {
		}
		return null;
	}

	@Override
	public Address getFirstPropertyAddress() {
		try {
			long index = propertyMgr.getFirstPropertyIndex();
			return addrMap.decodeAddress(index);
		}
		catch (NoSuchIndexException e) {
		}
		return null;

	}

	@Override
	public Address getLastPropertyAddress() {
		try {
			long index = propertyMgr.getLastPropertyIndex();
			return addrMap.decodeAddress(index);
		}
		catch (NoSuchIndexException e) {
		}
		return null;
	}

	@Override
	public int getSize() {
		return propertyMgr.getSize();
	}

	@Override
	public AddressIterator getPropertyIterator(Address start, Address end) {
		return new AddressPropertyIterator(start, end);
	}

	@Override
	public AddressIterator getPropertyIterator(Address start, Address end, boolean forward) {
		return new AddressPropertyIterator(start, end, forward);
	}

	@Override
	public AddressIterator getPropertyIterator() {
		return new AddressPropertyIterator();
	}

	@Override
	public AddressIterator getPropertyIterator(AddressSetView asv) {
		return new AddressSetPropertyIterator(asv, true);
	}

	@Override
	public AddressIterator getPropertyIterator(AddressSetView asv, boolean forward) {
		return new AddressSetPropertyIterator(asv, forward);
	}

	@Override
	public AddressIterator getPropertyIterator(Address start, boolean forward) {
		return new AddressPropertyIterator(start, forward);
	}

	@Override
	public void moveRange(Address start, Address end, Address newStart) {
		propertyMgr.moveRange(addrMap.getKey(start), addrMap.getKey(end), addrMap.getKey(newStart));
	}

	/**
	 * Save the properties in the given range to output stream.
	 * @param oos output stream to write to
	 * @param start start address in the range
	 * @param end end address in the range 
	 * @throws IOException if there a problem doing the write
	 */
	public void saveProperties(ObjectOutputStream oos, Address start, Address end)
			throws IOException {
		propertyMgr.saveProperties(oos, addrMap.getKey(start), addrMap.getKey(end));
	}

	/**
	 * Restore properties from the given input stream.
	 * @param ois input stream
	 * @throws IOException if there is a problem reading from the stream
	 * @throws ClassNotFoundException if the class for the object being
	 * read is not in the class path
	 */
	public void restoreProperties(ObjectInputStream ois)
			throws IOException, ClassNotFoundException {
		propertyMgr.restoreProperties(ois);
	}

	private class AddressPropertyIterator implements AddressIterator {

		private LongIterator iter;
		private boolean forward = true;

		AddressPropertyIterator() {
			iter = propertyMgr.getPropertyIterator();
		}

		AddressPropertyIterator(Address start, boolean forward) {
			iter = propertyMgr.getPropertyIterator(addrMap.getKey(start), forward);
			this.forward = forward;
		}

		AddressPropertyIterator(Address start, Address end) {
			iter = propertyMgr.getPropertyIterator(addrMap.getKey(start), addrMap.getKey(end));

		}

		AddressPropertyIterator(Address start, Address end, boolean forward) {
			iter =
				propertyMgr.getPropertyIterator(addrMap.getKey(start), addrMap.getKey(end),
					forward);
			this.forward = forward;

		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean hasNext() {
			if (forward) {
				return iter.hasNext();
			}
			return iter.hasPrevious();
		}

		@Override
		public Address next() {
			try {
				if (forward) {
					return addrMap.decodeAddress(iter.next());
				}
				return addrMap.decodeAddress(iter.previous());
			}
			catch (NoSuchElementException e) {
			}
			return null;
		}

		@Override
		public Iterator<Address> iterator() {
			return this;
		}
	}

	private class AddressSetPropertyIterator implements AddressIterator {

		private AddressPropertyIterator iter;
		private AddressRangeIterator ranges;
		private Address nextAddr;
		private AddressRange curRange;
		private boolean atStart;

		AddressSetPropertyIterator(AddressSetView addrSet, boolean atStart) {
			ranges = addrSet.getAddressRanges(atStart);
			this.atStart = atStart;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean hasNext() {
			if (nextAddr == null) {
				nextAddr = findNext();
			}
			return nextAddr != null;
		}

		@Override
		public Address next() {
			if (hasNext()) {
				Address addr = nextAddr;
				nextAddr = null;
				return addr;
			}
			return null;
		}

		private Address findNext() {
			if ((iter != null) && iter.hasNext()) {
				return iter.next();
			}
			while (ranges.hasNext()) {
				AddressRange range = ranges.next();
				if (range == curRange) {
					continue;
				}
				curRange = range;
				iter =
					new AddressPropertyIterator(range.getMinAddress(), range.getMaxAddress(),
						atStart);
				if (iter.hasNext()) {
					return iter.next();
				}
			}
			return null;
		}

		@Override
		public Iterator<Address> iterator() {
			return this;
		}
	}

}
