// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.core.domain.v0_6;

import java.util.Collection;
import java.util.Date;

import org.openstreetmap.osmosis.core.domain.common.SimpleTimestampContainer;
import org.openstreetmap.osmosis.core.domain.common.TimestampContainer;
import org.openstreetmap.osmosis.core.store.StoreClassRegister;
import org.openstreetmap.osmosis.core.store.StoreReader;
import org.openstreetmap.osmosis.core.store.StoreWriter;
import org.openstreetmap.osmosis.core.util.FixedPrecisionCoordinateConvertor;


/**
 * A data class representing a single OSM node.
 * 
 * @author Brett Henderson
 */
public class Node extends Entity implements Comparable<Node> {

	private double latitude;
	private double longitude;


	/**
	 * Creates a new instance.
	 * 
	 * @param id
	 *            The unique identifier.
	 * @param version
	 *            The version of the entity.
	 * @param timestamp
	 *            The last updated timestamp.
	 * @param user
	 *            The user that last modified this entity.
	 * @param changesetId
	 *            The id of the changeset that this version of the entity was created by.
	 * @param latitude
	 *            The geographic latitude.
	 * @param longitude
	 *            The geographic longitude.
	 * @deprecated As of 0.40, replaced by Node(entityData, latitude, longitude).
	 */
	@Deprecated
	public Node(
			long id, int version, Date timestamp, OsmUser user, long changesetId, double latitude, double longitude) {
		// Chain to the more-specific constructor
		this(id, version, new SimpleTimestampContainer(timestamp), user, changesetId, latitude, longitude);
	}


	/**
	 * Creates a new instance.
	 * 
	 * @param id
	 *            The unique identifier.
	 * @param version
	 *            The version of the entity.
	 * @param timestampContainer
	 *            The container holding the timestamp in an alternative timestamp representation.
	 * @param user
	 *            The name of the user that last modified this entity.
	 * @param changesetId
	 *            The id of the changeset that this version of the entity was created by.
	 * @param latitude
	 *            The geographic latitude.
	 * @param longitude
	 *            The geographic longitude.
	 * @deprecated As of 0.40, replaced by Node(entityData, latitude, longitude).
	 */
	@Deprecated
	public Node(long id, int version, TimestampContainer timestampContainer, OsmUser user, long changesetId,
			double latitude, double longitude) {
		super(id, version, timestampContainer, user, changesetId);

		init(latitude, longitude);
	}


	/**
	 * Creates a new instance.
	 * 
	 * @param entityData
	 *            The common entity data.
	 * @param latitude
	 *            The geographic latitude.
	 * @param longitude
	 *            The geographic longitude.
	 */
	public Node(CommonEntityData entityData, double latitude, double longitude) {
		super(entityData);

		init(latitude, longitude);
	}


	/**
	 * Creates a new instance.
	 * 
	 * @param id
	 *            The unique identifier.
	 * @param version
	 *            The version of the entity.
	 * @param timestamp
	 *            The last updated timestamp.
	 * @param user
	 *            The user that last modified this entity.
	 * @param changesetId
	 *            The id of the changeset that this version of the entity was created by.
	 * @param tags
	 *            The tags to apply to the object.
	 * @param latitude
	 *            The geographic latitude.
	 * @param longitude
	 *            The geographic longitude.
	 * @deprecated As of 0.40, replaced by Node(entityData, latitude, longitude).
	 */
	@Deprecated
	public Node(long id, int version, Date timestamp, OsmUser user, long changesetId, Collection<Tag> tags,
			double latitude, double longitude) {
		// Chain to the more-specific constructor
		this(id, version, new SimpleTimestampContainer(timestamp), user, changesetId, tags, latitude, longitude);
	}


	/**
	 * Creates a new instance.
	 * 
	 * @param id
	 *            The unique identifier.
	 * @param version
	 *            The version of the entity.
	 * @param timestampContainer
	 *            The container holding the timestamp in an alternative timestamp representation.
	 * @param user
	 *            The name of the user that last modified this entity.
	 * @param changesetId
	 *            The id of the changeset that this version of the entity was created by.
	 * @param tags
	 *            The tags to apply to the object.
	 * @param latitude
	 *            The geographic latitude.
	 * @param longitude
	 *            The geographic longitude.
	 * @deprecated As of 0.40, replaced by Node(entityData, latitude, longitude).
	 */
	@Deprecated
	public Node(long id, int version, TimestampContainer timestampContainer, OsmUser user, long changesetId,
			Collection<Tag> tags, double latitude, double longitude) {
		super(id, version, timestampContainer, user, changesetId, tags);

		init(latitude, longitude);
	}
	
	
	/**
	 * Creates a new instance.
	 * 
	 * @param originalNode
	 *            The node to clone from.
	 */
	private Node(Node originalNode) {
		super(originalNode);
		
		init(originalNode.latitude, originalNode.longitude);
	}


	/**
	 * Initializes non-collection attributes.
	 * 
	 * @param newLatitude
	 *            The geographic latitude.
	 * @param newLongitude
	 *            The geographic longitude.
	 */
	private void init(double newLatitude, double newLongitude) {
		this.latitude = newLatitude;
		this.longitude = newLongitude;
	}


	/**
	 * Creates a new instance.
	 * 
	 * @param sr
	 *            The store to read state from.
	 * @param scr
	 *            Maintains the mapping between classes and their identifiers within the store.
	 */
	public Node(StoreReader sr, StoreClassRegister scr) {
		super(sr, scr);

		this.latitude = FixedPrecisionCoordinateConvertor.convertToDouble(sr.readInteger());
		this.longitude = FixedPrecisionCoordinateConvertor.convertToDouble(sr.readInteger());
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void store(StoreWriter sw, StoreClassRegister scr) {
		super.store(sw, scr);

		sw.writeInteger(FixedPrecisionCoordinateConvertor.convertToFixed(latitude));
		sw.writeInteger(FixedPrecisionCoordinateConvertor.convertToFixed(longitude));
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public EntityType getType() {
		return EntityType.Node;
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object o) {
		if (o instanceof Node) {
			return compareTo((Node) o) == 0;
		} else {
			return false;
		}
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		/*
		 * As per the hashCode definition, this doesn't have to be unique it just has to return the
		 * same value for any two objects that compare equal. Using both id and version will provide
		 * a good distribution of values but is simple to calculate.
		 */
		return (int) getId() + getVersion();
	}


	/**
	 * Compares this node to the specified node. The node comparison is based on
	 * a comparison of id, version, latitude, longitude, timestamp and tags in
	 * that order.
	 * 
	 * @param comparisonNode
	 *            The node to compare to.
	 * @return 0 if equal, &lt; 0 if considered "smaller", and &gt; 0 if
	 *         considered "bigger".
	 */
	public int compareTo(Node comparisonNode) {
		if (this.getId() < comparisonNode.getId()) {
			return -1;
		}

		if (this.getId() > comparisonNode.getId()) {
			return 1;
		}

		if (this.getVersion() < comparisonNode.getVersion()) {
			return -1;
		}

		if (this.getVersion() > comparisonNode.getVersion()) {
			return 1;
		}

		if (this.latitude < comparisonNode.latitude) {
			return -1;
		}

		if (this.latitude > comparisonNode.latitude) {
			return 1;
		}

		if (this.longitude < comparisonNode.longitude) {
			return -1;
		}

		if (this.longitude > comparisonNode.longitude) {
			return 1;
		}

		if (this.getTimestamp() == null && comparisonNode.getTimestamp() != null) {
			return -1;
		}
		if (this.getTimestamp() != null && comparisonNode.getTimestamp() == null) {
			return 1;
		}
		if (this.getTimestamp() != null && comparisonNode.getTimestamp() != null) {
			int result;

			result = this.getTimestamp().compareTo(comparisonNode.getTimestamp());

			if (result != 0) {
				return result;
			}
		}

		return compareTags(comparisonNode.getTags());
	}


	/**
	 * Gets the latitude.
	 * 
	 * @return The latitude.
	 */
	public double getLatitude() {
		return latitude;
	}


	/**
	 * Sets the latitude.
	 * 
	 * @param latitude
	 *            The latitude.
	 */
	public void setLatitude(double latitude) {
		assertWriteable();

		this.latitude = latitude;
	}


	/**
	 * Gets the longitude.
	 * 
	 * @return The longitude.
	 */
	public double getLongitude() {
		return longitude;
	}


	/**
	 * Sets the longitude.
	 * 
	 * @param longitude
	 *            The longitude.
	 */
	public void setLongitude(double longitude) {
		assertWriteable();

		this.longitude = longitude;
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public Node getWriteableInstance() {
		if (isReadOnly()) {
			return new Node(this);
		} else {
			return this;
		}
	}


	/**
	 * ${@inheritDoc}.
	 */
	@Override
	public String toString() {
		String name = null;
		Collection<Tag> tags = getTags();
		for (Tag tag : tags) {
			if (tag.getKey() != null && tag.getKey().equalsIgnoreCase("name")) {
				name = tag.getValue();
				break;
			}
		}
		if (name != null) {
			return "Node(id=" + getId() + ", #tags=" + getTags().size() + ", name='" + name + "')";
		}
		return "Node(id=" + getId() + ", #tags=" + getTags().size() + ")";
	}
}
