package eu.dnetlib.dlms.jdbc.server;

import java.net.URL;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.common.collect.Lists;

import eu.dnetlib.dlms.jdbc.InformationObject;
import eu.dnetlib.dlms.jdbc.serialization.ItemType;
import eu.dnetlib.dlms.jdbc.serialization.ItemWrapper;
import eu.dnetlib.dlms.jdbc.serialization.RSMetadataWrapper;
import eu.dnetlib.dlms.lowlevel.objects.DorotyObjectEnum;
import eu.dnetlib.dlms.lowlevel.objects.structures.BooleanBasicValue;
import eu.dnetlib.dlms.lowlevel.objects.structures.DateBasicValue;
import eu.dnetlib.dlms.lowlevel.objects.structures.DescriptionValueCollection;
import eu.dnetlib.dlms.lowlevel.objects.structures.DoubleBasicValue;
import eu.dnetlib.dlms.lowlevel.objects.structures.IntBasicValue;
import eu.dnetlib.dlms.lowlevel.objects.structures.StringBasicValue;
import eu.dnetlib.enabling.resultset.ResultSetListener;
import eu.dnetlib.miscutils.jaxb.JaxbFactory;

/**
 * Wraps a jdbc resultset to a dnet resultset.
 * 
 * <p>
 * Currently it only fetches one element at a time. TODO: implement bulk fetch.
 * </p>
 * 
 * @author marko
 * 
 */
public class ResultSetWrapper implements ResultSetListener {

	private static final Log log = LogFactory.getLog(ResultSetWrapper.class); // NOPMD by marko on 11/24/08 5:02 PM

	private ResultSet resultSet;

	/**
	 * position index of the <b>next</b> element to be fetched.
	 */
	private int position = 1;
	/** Keeps the first row if we're asked for metadata before any next. */
	private String firstRow;

	private JaxbFactory<ItemWrapper> itemFactory;
	private JaxbFactory<RSMetadataWrapper> metadataFactory;

	/**
	 * Gets metadata about the wrapped results set.
	 * 
	 * @return a ResultSetMetadata instance
	 * @throws SQLException
	 *             if metadata could not be retrieved from the wrapped resultset
	 */
	public ResultSetMetaData getMetadata() throws SQLException {
		return this.resultSet.getMetaData();
	}

	public ResultSetWrapper(final ResultSet resultSet, final JaxbFactory<ItemWrapper> itemFactory, final JaxbFactory<RSMetadataWrapper> metadataFactory) {
		this.resultSet = resultSet;
		this.itemFactory = itemFactory;
		this.metadataFactory = metadataFactory;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.resultset.ResultSetListener#getResult(int, int)
	 */
	public List<String> getResult(final int fromPosition, final int toPosition) {
		//TODO: non sono ancora gestite le getresult che chiedono piu' di una row alla volta.
		log.info("getting result from " + fromPosition + " to " + toPosition);
		if (fromPosition != 0 && fromPosition < this.position)
			throw new IllegalStateException("cannot fetch already fetched results");
		final List<String> result = Lists.newArrayList();
		try {
			/*
			 * La prima riga e' stata gia' presa a causa di una richiesta di metadata prima di una next?
			 */
			if (fromPosition == 1 && this.firstRow != null) {
				log.info("adding already generated firstRow");
				result.add(this.firstRow);
				this.position++;
				this.firstRow = null;
			} else {
				//fromPosition != 1 oppure la prima riga non e' gia' stata serializzata in firstRow.
				//In ogni caso dobbiamo prendere la prossima riga.
				log.debug("Calling resultSet.next()");
				final boolean hasNext = this.resultSet.next();
				log.debug("Called resultSet.next()");
				//fromPosition == 0 quando si vuole prendere i metadati.
				if (fromPosition == 0) {
					log.info("Getting metadata serialization...");
					//result[0] must be the serialization of metadata.
					final String metadataSer = this.serializeMetadata(this.resultSet.getMetaData());
					log.debug("Got serialization as:");
					log.debug(metadataSer);
					result.add(metadataSer);
				}
				log.info("Is there a row now? " + hasNext);
				//no row in resultset: returns a list with metadata (if asked for, or an empty list)
				if (!hasNext)
					return result;
				//there's the row in result set, to be serialized and added to the result or to store if they only asked me for metadata
				final int columnCount = this.resultSet.getMetaData().getColumnCount();
				log.info("column count == " + columnCount);
				final Object[] obj = new Object[columnCount];
				for (int i = 1; i <= columnCount; i++) {
					log.info("getting column " + i + " for result at " + (this.position - 1) + " position");
					obj[i - 1] = this.resultSet.getObject(i);
					log.info(obj[i - 1]);
				}
				final String rowSerialized = this.serialize(obj);
				if (toPosition == 0) {
					log.info("They only asked me for metadata...saving the first row");
					//mi hanno chiesto solo i metadati, quindi salvo la prima riga
					this.firstRow = rowSerialized;
				} else {
					log.info("new position will be " + (this.position + 1) + ". Current row put in result.");
					this.position++;
					//mi hanno chiesto anche un elemento (almeno)
					result.add(rowSerialized);
				}
			}
			return result;
		} catch (final SQLException e) {
			throw new IllegalStateException("Cannot get string: " + e.getMessage(), e);
		} catch (final JAXBException e) {
			throw new IllegalStateException("Cannot serialize: " + e.getMessage(), e);
		}
	}

	/**
	 * Serializes one result. A single result may be composed by more than one column, thus the object to serialize is
	 * an array of Object.
	 * 
	 * @param objA
	 *            Object[] result
	 * @return objA serialization
	 * @throws JAXBException
	 *             during serialization
	 */
	public String serialize(final Object[] objA) throws JAXBException {
		final ItemWrapper item = new ItemWrapper();
		for (int i = 0; i < objA.length; i++) {
			final Object obj = objA[i];
			String value = " ";
			ItemType it = null;
			if (obj != null) {
				value = obj.toString();
				if (obj instanceof IntBasicValue)
					it = ItemType.INT;
				if (obj instanceof Long)
					it = ItemType.LONG;
				if (obj instanceof StringBasicValue)
					it = ItemType.STRING;
				if (obj instanceof BooleanBasicValue)
					it = ItemType.BOOLEAN;
				if (obj instanceof DateBasicValue)
					it = ItemType.DATE;
				if (obj instanceof DoubleBasicValue)
					it = ItemType.DOUBLE;
				if (obj instanceof URL)
					it = ItemType.URL;
				if (obj instanceof InformationObject) {
					it = ItemType.INFORMATION_OBJECT;
					final InformationObject iob = (InformationObject) obj;
					value = Long.toString(iob.getId()) + "," + iob.getType() + "," + iob.getSet();
				}
				if (obj instanceof DescriptionValueCollection) {
					log.debug("serializing a DescriptionValueCollection...");
					it = ItemType.COLL;
					final DescriptionValueCollection dvc = (DescriptionValueCollection) obj;
					//final DorotyObjectEnum dorotyType = dvc.getCollType().getType().getDorotyType();
					//log.debug("content of collection field is :" + dorotyType);
					final List dvList = (List) dvc.getJavaObject();
					final DorotyObjectEnum dorotyType = dvc.getCollContentType();
					ItemType collContentType = null;
					switch (dorotyType) {
					case d_boolean:
					case std_boolean:
						collContentType = ItemType.BOOLEAN;
						break;
					case d_date:
					case std_date:
						collContentType = ItemType.DATE;
						break;
					case d_double:
					case std_double:
						collContentType = ItemType.DOUBLE;
						break;
					case d_int:
					case std_int:
						collContentType = ItemType.INT;
						break;
					case d_string:
					case std_string:
						collContentType = ItemType.STRING;
						break;
					case std_long:
						collContentType = ItemType.LONG;
						break;
					case struct_def:
						collContentType = ItemType.D;
						break;
					default:
						log.debug("Unreckognized DorotyType for collection contents: " + dorotyType);
					}
					value = dvList.toString() + "|" + collContentType;
				}
			}
			log.debug("Value fo serialization: " + value);
			item.getValues().add(value);
			item.getTypes().add(it);
		}
		return this.itemFactory.serialize(item);
	}

	public String serializeMetadata(final ResultSetMetaData rsMetadata) throws SQLException, JAXBException {
		log.debug("In metadata serialization...");
		final int colCount = rsMetadata.getColumnCount();
		final List<String> colNames = new ArrayList<String>(colCount);
		final List<Integer> colTypes = new ArrayList<Integer>(colCount);
		for (int i = 1; i <= colCount; i++) {
			colNames.add(i - 1, rsMetadata.getColumnName(i));
			colTypes.add(i - 1, rsMetadata.getColumnType(i));
		}
		log.debug("Created data for the wrapper");
		final RSMetadataWrapper mdWrapper = new RSMetadataWrapper(colCount, colNames, colTypes);
		log.debug("Created wrapper for metadata: " + mdWrapper);
		return this.metadataFactory.serialize(mdWrapper);
	}

	public int getSize() {
		return this.position;
	}

	public ResultSet getResultSet() {
		return this.resultSet;
	}

	public void setResultSet(final ResultSet resultSet) {
		this.resultSet = resultSet;
	}

	public int getPosition() {
		return this.position;
	}

	public void setPosition(final int position) {
		this.position = position;
	}

	public JaxbFactory<ItemWrapper> getItemFactory() {
		return this.itemFactory;
	}

	public void setItemFactory(final JaxbFactory<ItemWrapper> itemFactory) {
		this.itemFactory = itemFactory;
	}

	public void setMetadataFactory(final JaxbFactory<RSMetadataWrapper> metadataFactory) {
		this.metadataFactory = metadataFactory;
	}

	public JaxbFactory<RSMetadataWrapper> getMetadataFactory() {
		return this.metadataFactory;
	}

}
