package eu.dnetlib.dlms.jdbc;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.ExpectedException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import eu.dnetlib.dlms.lowlevel.objects.Set;
import eu.dnetlib.dlms.lowlevel.objects.SetDAO;
import eu.dnetlib.dlms.lowlevel.types.ObjType;
import eu.dnetlib.dlms.lowlevel.types.ObjTypeDAO;
import eu.dnetlib.dlms.lowlevel.types.SetType;
import eu.dnetlib.dlms.lowlevel.types.SetTypeDAO;
import eu.dnetlib.dlms.union.objects.UnionSet;
import eu.dnetlib.dlms.union.objects.UnionSetDAO;
import eu.dnetlib.dlms.union.types.UnionSetType;
import eu.dnetlib.dlms.union.types.UnionSetTypeDAO;
import eu.dnetlib.dlms.union.types.UnionType;
import eu.dnetlib.dlms.union.types.UnionTypeDAO;

/**
 * @author alessia
 * 
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestDQLParser {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(TestDQLParser.class);
	/** DataSource istance. */
	@Resource
	private DorotyDataSource dataSource;
	@Resource
	private SetDAO simpleSetDAO;
	@Resource
	private SetTypeDAO setTypeDAO;
	@Resource
	private ObjTypeDAO objTypeDAO;
	@Resource
	private UnionTypeDAO unionTypeDAO;
	@Resource
	private UnionSetTypeDAO unionSetTypeDAO;
	@Resource
	private UnionSetDAO simpleUnionSetDAO;

	@Before
	public void before() {
		// save a set with name car, otherwise it will not work
		final ObjType type = this.objTypeDAO.create();
		type.setName("CarType");
		this.objTypeDAO.save(type);
		final SetType carSetType = this.setTypeDAO.create(type);
		carSetType.setName("car");
		this.setTypeDAO.save(carSetType);
		final Set carSet = this.simpleSetDAO.create();
		carSet.setInfo("Set per macchine...ehe he eh");
		carSet.setObjectType(carSetType);
		this.simpleSetDAO.save(carSet);
		// now the union of cars!
		final UnionType unionType = this.unionTypeDAO.create();
		unionType.addType(type);
		unionType.setName("unionOfCarsType");
		this.unionTypeDAO.save(unionType);
		final UnionSetType unionSetType = this.unionSetTypeDAO.create(unionType);
		unionSetType.setName("UnionOfCars");
		unionSetType.addSetType(carSetType);
		this.unionSetTypeDAO.save(unionSetType);
		final UnionSet unionCars = this.simpleUnionSetDAO.create();
		unionCars.setInfo("UnionSet of cars");
		unionCars.setObjectType(unionSetType);
		unionCars.addSet(carSet);
		this.simpleUnionSetDAO.save(unionCars);
	}

	@Test
	@DirtiesContext
	public void inst() throws SQLException {
		log.debug("Testing code for new object");

		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("new car()"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void selectSimpleXPath() throws SQLException {
		log.debug("Testing code for select with a simple xpath");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("select car/@b"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getLong(1));
			log.debug(rs.getLong(1));
			assertNotNull(rs.getURL(2));
			log.debug(rs.getURL(2));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void selectXPathWithPredicate() throws SQLException {
		log.debug("Testing code for select with xpath and binary predicate");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("select car[@ciao=2]/@b"));
		//assertTrue(stm.execute("select (id, url) car/composedBy[@hello=\"hi\"]"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getLong(1));
			log.debug(rs.getLong(1));
			assertNotNull(rs.getURL(2));
			log.debug(rs.getURL(2));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void declInt() throws SQLException {
		log.debug("Testing code for declaration and instantiation");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("Object o = new car();"));
		final PreparedStatement prepStm = con.prepareStatement(" new car(?);");
		prepStm.setString(1, "object info");
		assertTrue(prepStm.execute());
		final ResultSet rs = prepStm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	/**
	 * 
	 * 
	 * @throws SQLException
	 */
	@Test
	@DirtiesContext
	public void lit() throws SQLException {
		log.debug("Testing code for literals");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("1;"));
		assertTrue(stm.execute("\"aString\""));
		assertTrue(stm.execute("Object aRef = new car(); aRef"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void callImport() throws SQLException {
		log.debug("Testing code for call import");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("Object obj = new car();car.import(obj);"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void callAddToUnion() throws SQLException {
		log.debug("Testing code for call add");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("Object obj = new car(); UnionOfCars.add(obj);"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void callDrop() throws SQLException {
		log.debug("Testing code for call");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("Object obj = new car(); car.drop(obj);"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	@ExpectedException(SQLException.class)
	public void callCast() throws SQLException {
		log.debug("Testing code for call");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		//exception will be thrown because it makes no sense to cast an object to a set it already belongs.
		assertTrue(stm.execute("Object obj = new car(); car.cast(obj, car); "));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	/**
	 * 
	 * @throws SQLException
	 */
	@Test
	@DirtiesContext
	public void predicate() throws SQLException {
		log.debug("Testing code for predicate");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("{var = 2}"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

	@Test
	@DirtiesContext
	public void assign() throws SQLException {
		log.debug("Testing code for assignment");
		final Connection con = this.dataSource.getConnection();
		final Statement stm = con.createStatement();
		assertTrue(stm.execute("Object var = 2; var = new car()"));
		final ResultSet rs = stm.getResultSet();
		while (rs.next()) {
			assertNotNull(rs.getObject(1));
			log.debug(rs.getObject(1));
		}
		rs.close();
		con.close();
		log.debug("--------------------------");
	}

}
