package eu.dnetlib.functionality.index.model.document;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import eu.dnetlib.functionality.index.model.Any.ValueType;
import eu.dnetlib.functionality.index.utils.IndexFieldUtility;

/**
 * The Class AbstractDocument Represent the field information needed to construct and index Document.
 */
public abstract class AbstractIndexDocument implements Map<String, IndexField>, Iterable<IndexField>, IndexDocument {

	/** The _fields. */
	protected final Map<String, IndexField> fields;

	private static final String DATE_SUFFIX = "\\+\\d{2}:\\d{2}";

	/**
	 * Actual document status.
	 */
	private Status status;

	/**
	 * index schema.
	 */
	protected final Map<String, ValueType> schema;

	/**
	 * If there was an error building the document, it is described here.
	 */
	private Throwable error;

	/**
	 * Instantiates a new abstract document.
	 */
	public AbstractIndexDocument(final Map<String, ValueType> schema, final String dsId) {
		this.schema = schema;
		fields = new LinkedHashMap<String, IndexField>();
		// addField(IndexFieldUtility.DS_ID, dsId);
	}

	/**
	 * Instantiates a new abstract document.
	 *
	 * @param fields
	 *            the fields
	 */
	public AbstractIndexDocument(final Map<String, ValueType> schema, final String dsId, final Map<String, IndexField> fields) {
		this.schema = schema;
		this.fields = fields;
		addField(IndexFieldUtility.DS_ID, dsId);
	}

	/**
	 * Remove all fields and boosts from the document.
	 */
	@Override
	public void clear() {
		if (fields != null) {
			fields.clear();
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#addField(java.lang.String, java.lang.Object)
	 */
	@Override
	public void addField(final String name, final Object value) {
		Object hack_value = hackField(name, value);
		IndexField field = fields.get(name);
		if ((field == null) || (field.getValue() == null)) {
			setField(name, hack_value);
		} else {
			field.addValue(hack_value);
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#getFieldValue(java.lang.String)
	 */
	@Override
	public Object getFieldValue(final String name) {
		IndexField field = getField(name);
		Object o = null;
		if (field != null) {
			o = field.getFirstValue();
		}
		return o;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#getField(java.lang.String)
	 */
	@Override
	public IndexField getField(final String field) {
		return fields.get(field);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#getFieldValues(java.lang.String)
	 */
	@Override
	public Collection<Object> getFieldValues(final String name) {
		IndexField field = getField(name);
		if (field != null) return field.getValues();
		return null;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#getFieldNames()
	 */
	@Override
	public Collection<String> getFieldNames() {
		return fields.keySet();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#setField(java.lang.String, java.lang.Object)
	 */
	@Override
	public void setField(final String name, final Object value) {
		Object hack_value = hackField(name, value);
		IndexField field = new IndexField(name);
		fields.put(name, field);
		field.setValue(hack_value);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#removeField(java.lang.String)
	 */
	@Override
	public IndexField removeField(final String name) {
		return fields.remove(name);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.lang.Iterable#iterator()
	 */
	@Override
	public Iterator<IndexField> iterator() {
		return fields.values().iterator();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "IndexDocument: " + fields.values();
	}

	// ---------------------------------------------------
	// MAP interface
	// ---------------------------------------------------

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#containsKey(java.lang.Object)
	 */
	@Override
	public boolean containsKey(final Object key) {
		return fields.containsKey(key);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#containsValue(java.lang.Object)
	 */
	@Override
	public boolean containsValue(final Object value) {
		return fields.containsValue(value);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#entrySet()
	 */
	@Override
	public Set<Entry<String, IndexField>> entrySet() {
		return fields.entrySet();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#get(java.lang.Object)
	 */
	@Override
	public IndexField get(final Object key) {
		return fields.get(key);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#isEmpty()
	 */
	@Override
	public boolean isEmpty() {
		return fields.isEmpty();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#keySet()
	 */
	@Override
	public Set<String> keySet() {
		return fields.keySet();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
	 */
	@Override
	public IndexField put(final String key, final IndexField value) {
		return fields.put(key, value);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#putAll(java.util.Map)
	 */
	@Override
	public void putAll(final Map<? extends String, ? extends IndexField> t) {
		fields.putAll(t);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#remove(java.lang.Object)
	 */
	@Override
	public IndexField remove(final Object key) {
		return fields.remove(key);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#size()
	 */
	@Override
	public int size() {
		return fields.size();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see java.util.Map#values()
	 */
	@Override
	public Collection<IndexField> values() {
		return fields.values();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#getStatus()
	 */
	@Override
	public Status getStatus() {
		return status;
	}

	/**
	 * Sets the status.
	 *
	 * @param status
	 *            the status to set
	 * @return the index document
	 */
	@Override
	public IndexDocument setStatus(final Status status) {
		this.status = status;
		return this;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#deepCopy()
	 */
	@Override
	public IndexDocument deepCopy() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#getError()
	 */
	@Override
	public Throwable getError() {

		return error;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#setOK()
	 */
	@Override
	public IndexDocument setOK() {

		return this.setStatus(Status.OK);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#setMarked()
	 */
	@Override
	public IndexDocument setMarked() {
		addField(IndexFieldUtility.DELETE_DOCUMENT, true);
		return this.setStatus(Status.MARKED);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.functionality.index.model.document.IndexDocument#setError(java.lang.Throwable)
	 */
	@Override
	public IndexDocument setError(final Throwable error) {
		this.error = error;
		return this.setStatus(Status.ERROR);
	}

	/**
	 * This method modifies a field w.r.t. his type
	 *
	 * @param name
	 * @param value
	 * @return
	 */
	private Object hackField(final String name, Object value) {
		if (schema == null) return value;

		final ValueType type = schema.get(name);

		// TODO: hack this is hardcoded for the date type
		switch (type) {
		case DATETIME:
		case DATE:
			if (value.toString().matches(DATE_SUFFIX)) return value.toString().replaceAll(DATE_SUFFIX, "Z");
			if (value.toString().endsWith("Z")) return value;

			value = value + "T00:00:00Z";
			break;
		default:
			break;
		}
		return value;
	}

}
