package eu.dnetlib.dlms.jdbc;

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

import java.net.URL;
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.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import eu.dnetlib.dlms.lowlevel.LowLevelException;
import eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject;
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 setDAO;
    @Resource
    private SetTypeDAO setTypeDAO;
    @Resource
    private ObjTypeDAO objTypeDAO;
    @Resource
    private UnionTypeDAO unionTypeDAO;
    @Resource
    private UnionSetTypeDAO unionSetTypeDAO;
    @Resource
    private UnionSetDAO unionSetDAO;

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

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

	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	// assertFalse(stm.execute("new car()"));
	// TODO: add some fields when you can use the strings.
	assertFalse(stm.execute("new ePubs([])"));
	con.close();
	log.debug("--------------------------");
    }

    @Test
    @DirtiesContext
    public void selectSimpleXPath() throws SQLException {
	log.debug("Testing code for select with a simple xpath");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	// assertTrue(stm.execute("select car/b"));
	assertTrue(stm.execute("select ePubs/composedBy"));
	ResultSet rs = stm.getResultSet();
	LLDigitalObject obj = null;
	while (rs.next()) {
	    obj = (LLDigitalObject) rs.getObject(1);
	    assertNotNull(obj);
	    log.debug(obj);
	}
	rs.close();
	assertTrue(stm.execute("select (id, url) ePubs/composedBy"));
	ResultSet rs2 = stm.getResultSet();
	while (rs2.next()) {
	    long id = rs2.getLong(1);
	    URL theUrl = rs2.getURL(2);
	    log.debug(id);
	    assertTrue(obj.getId() == id);
	    log.debug(theUrl);
	}
	rs2.close();
	assertTrue(stm.execute("select ePubs/b"));
	assertFalse(stm.getResultSet().next());
	stm.getResultSet().close();
	con.close();
	log.debug("--------------------------");
    }

    @Test
    @DirtiesContext
    public void selectXPathWithPredicate() throws SQLException {
	log.debug("Testing code for select with xpath and binary predicate");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	// assertTrue(stm.execute("select ePubs[@dc:language=IT]"));
	assertTrue(stm.execute("select ePubs[./c]"));
	// assertTrue(stm.execute("select ePubs/b[@c = 5]"));
	// assertTrue(stm.execute("select ePubs/b[@hello=1]/c/d[@ciao=2]"));
	con.close();
	log.debug("--------------------------");
    }

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

    /**
     * TODO: string and struct parsing do not work
     * 
     * @throws SQLException
     */
    @Test
    @DirtiesContext
    public void lit() throws SQLException {
	log.debug("Testing code for literals");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm.execute("1;"));
	assertFalse(stm.execute("'aString'"));
	assertFalse(stm.execute("Object aRef = new car(); aRef"));
	// assertFalse(stm.execute("aRef"));
	// assertFalse(stm.execute("[a=1,b=2]"));
	// TODO: string and struct missing here!
	con.close();
	log.debug("--------------------------");
    }

    @Test
    @DirtiesContext
    public void callImport() throws SQLException {
	log.debug("Testing code for call import");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm.execute("Object obj = new car();car.import(obj);"));
	con.close();
	log.debug("--------------------------");
    }

    @Test
    @DirtiesContext
    public void callAddToUnion() throws SQLException {
	log.debug("Testing code for call add");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm
		.execute("Object obj = new car(); UnionOfCars.add(obj);"));
	con.close();
	log.debug("--------------------------");
    }

    @Test
    @DirtiesContext
    public void callDrop() throws SQLException {
	log.debug("Testing code for call");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm.execute("Object obj = new car(); car.drop(obj);"));
	// TODO: drop from the union
	con.close();
	log.debug("--------------------------");
    }

    @Test(expected = LowLevelException.class)
    @DirtiesContext
    public void callCast() throws SQLException {
	log.debug("Testing code for call");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm.execute("Object obj = new car(); car.cast(obj, car); "));
	con.close();
	log.debug("--------------------------");
    }

    /**
     * TODO: predicate parsing does not work.
     * 
     * @throws SQLException
     */
    @Test
    @DirtiesContext
    public void predicate() throws SQLException {
	log.debug("Testing code for predicate");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm.execute("[var = 2]"));
	// composed predicates not supported yet.
	// assertFalse(stm.execute("[@var = 2 AND @x < y]"));
	con.close();
	log.debug("--------------------------");
    }

    @Test
    @DirtiesContext
    public void assign() throws SQLException {
	log.debug("Testing code for assignment");
	Connection con = this.dataSource.getConnection();
	Statement stm = con.createStatement();
	assertFalse(stm.execute("Object var = 2; var = new car()"));
	// assertFalse(stm.execute("var = new stuff()"));
	// assertFalse(stm.execute("var = new stuff(par1, par2, par3)"));
	// assertFalse(stm.execute("var = new stuff(par)"));
	con.close();
	log.debug("--------------------------");
    }
}
