package eu.dnetlib.oai.mongo;

import java.util.Iterator;
import java.util.function.Function;

import com.google.common.collect.Lists;
import com.mongodb.DBObject;
import com.mongodb.client.MongoCursor;

import eu.dnetlib.oai.Cursor;
import eu.dnetlib.oai.info.RecordInfo;

public class DNetOAIMongoCursor implements Cursor {

	/**
	 * Underlying mongo cursor.
	 */
	private MongoCursor<DBObject> dbCursor;
	private int size = 0;
	/**
	 * Function to apply to records before delivering.
	 */
	private Function<String, String> function;

	/**
	 * true if the RecordInfo returned by this Cursor must include the record body, false otherwise.
	 */
	private boolean bodyIncluded;

	private RecordInfoGenerator recordInfoGenerator;
	private MetadataExtractor metadataExtractor;
	private ProvenanceExtractor provenanceExtractor;

	public DNetOAIMongoCursor() {
		super();
	}

	public DNetOAIMongoCursor(final MongoCursor<DBObject> dbCursor, final boolean bodyIncluded, final RecordInfoGenerator recordInfoGenerator,
			final MetadataExtractor metadataExtractor) {
		this(dbCursor, null, bodyIncluded, recordInfoGenerator, metadataExtractor);
	}

	public DNetOAIMongoCursor(final MongoCursor<DBObject> dbCursor, final Function<String, String> function, final boolean bodyIncluded,
			final RecordInfoGenerator recordInfoGenerator, final MetadataExtractor metadataExtractor) {
		super();
		this.dbCursor = dbCursor;
		this.size = 0;
		this.function = function;
		this.bodyIncluded = bodyIncluded;
		this.recordInfoGenerator = recordInfoGenerator;
		this.metadataExtractor = metadataExtractor;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int count() {
		// I can do it because MongoCursor are always created from queries with "limit", so I do not expect the creation of the list to
		// explode
		// to not exagerate, I'll get the size only if the current size is 0
		if (this.size == 0) {
			this.size = Lists.newArrayList(this.dbCursor).size();
		}
		return this.size;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see Iterable#iterator()
	 */
	@Override
	public Iterator<RecordInfo> iterator() {

		return new Iterator<RecordInfo>() {

			@Override
			public boolean hasNext() {
				return DNetOAIMongoCursor.this.dbCursor.hasNext();
			}

			@Override
			public RecordInfo next() {
				final DBObject res = DNetOAIMongoCursor.this.dbCursor.next();
				final RecordInfo info = DNetOAIMongoCursor.this.recordInfoGenerator.transformDBObject(res, DNetOAIMongoCursor.this.bodyIncluded);
				if ((DNetOAIMongoCursor.this.function != null) && DNetOAIMongoCursor.this.bodyIncluded && (info != null)) {
					info.setMetadata(DNetOAIMongoCursor.this.function.apply(info.getMetadata()));
				}
				return info;
			}

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

		};
	}

	public Function<String, String> getFunction() {
		return this.function;
	}

	public void setFunction(final Function<String, String> function) {
		this.function = function;
	}

	public MongoCursor<DBObject> getDbCursor() {
		return this.dbCursor;
	}

	public void setDbCursor(final MongoCursor<DBObject> dbCursor) {
		this.dbCursor = dbCursor;
	}

	@Override
	public boolean isBodyIncluded() {
		return this.bodyIncluded;
	}

	@Override
	public void setBodyIncluded(final boolean bodyIncluded) {
		this.bodyIncluded = bodyIncluded;
	}

	public RecordInfoGenerator getRecordInfoGenerator() {
		return this.recordInfoGenerator;
	}

	public void setRecordInfoGenerator(final RecordInfoGenerator recordInfoGenerator) {
		this.recordInfoGenerator = recordInfoGenerator;
	}

	public MetadataExtractor getMetadataExtractor() {
		return this.metadataExtractor;
	}

	public void setMetadataExtractor(final MetadataExtractor metadataExtractor) {
		this.metadataExtractor = metadataExtractor;
	}

	public ProvenanceExtractor getProvenanceExtractor() {
		return this.provenanceExtractor;
	}

	public void setProvenanceExtractor(final ProvenanceExtractor provenanceExtractor) {
		this.provenanceExtractor = provenanceExtractor;
	}

}
