package org.reldb.rel.v0.storage.relvars.external.jdbc;

import java.net.MalformedURLException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;

import org.reldb.rel.exceptions.ExceptionFatal;
import org.reldb.rel.exceptions.ExceptionSemantic;
import org.reldb.rel.v0.generator.Generator;
import org.reldb.rel.v0.generator.SelectAttributes;
import org.reldb.rel.v0.storage.RelDatabase;
import org.reldb.rel.v0.storage.relvars.RelvarCustomMetadata;
import org.reldb.rel.v0.storage.relvars.RelvarExternal;
import org.reldb.rel.v0.storage.relvars.RelvarGlobal;
import org.reldb.rel.v0.storage.relvars.RelvarHeading;
import org.reldb.rel.v0.storage.relvars.external.CSVLineParse;
import org.reldb.rel.v0.storage.relvars.external.ColumnName;
import org.reldb.rel.v0.storage.tables.TableExternal.DuplicateHandling;
import org.reldb.rel.v0.types.Heading;
import org.reldb.rel.v0.types.builtin.TypeCharacter;
import org.reldb.rel.v0.types.builtin.TypeInteger;

public class RelvarJDBCMetadata extends RelvarCustomMetadata {
	public static final long serialVersionUID = 0;

	private String connectionString;
	private String address; 		// "jdbc:postgresql://localhost/database"
	private String user; 			// "sqluser"
	private String password; 		// "sqluserpw"
	private String table; 			// "FEEDBACK.RELVAR"

	// var myvar external jdbc
	// "jdbc:postgresql://localhost/database,sqluser,sqluserpw,FEEDBACK.RELVAR";
	
	private DuplicateHandling duplicates;

	private static boolean driversLoaded = false;
	
	private static boolean osgiTested = false;
	private static boolean isInOSGI = false;
	
	private static boolean isInOSGI() {
		if (osgiTested)
			return isInOSGI;
		try {
			Class.forName("org.osgi.framework.BundleReference");
			isInOSGI = true;
		} catch (ClassNotFoundException e) {
		}
		osgiTested = true;
		return isInOSGI;
	}
	
	public static void loadDrivers(RelDatabase database) {
		if (driversLoaded)
			return;
		if (!isInOSGI()) {
			ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
			ClassLoader newClassLoader;
			try {
				newClassLoader = new JDBCDriverLoader(database.getExtensionDirectory());
			    Thread.currentThread().setContextClassLoader(newClassLoader);
			} catch (MalformedURLException e) {
				throw new ExceptionFatal("RS0471: Malforumed URL in RelvarJDBCMetadata.loadDrivers(): " + e);
			} finally {
			    Thread.currentThread().setContextClassLoader(originalClassLoader);
			}
		}
		driversLoaded = true;
	}
	
	private static String obtainDriverList() {
		String list = "";
		Enumeration<Driver> drivers = DriverManager.getDrivers();
		while (drivers.hasMoreElements()) {
			Driver driver = drivers.nextElement();
			if (!list.isEmpty())
				list += ", ";
			list += driver.getClass().getName();
		}
		return (list.isEmpty()) ? "<none>" : list;
	}
	
	public static RelvarHeading getHeading(RelDatabase database, String spec, DuplicateHandling duplicates) {
		String[] values = CSVLineParse.parseTrimmed(spec);	
		if (values.length != 4)
			throw new ExceptionSemantic("EX0014: Invalid arguments. Expected: URL, USER, PASSWORD, DATABASE.TABLE but got " + spec);
		String address = values[0];
		String user = values[1];
		String password = values[2];
		String table = values[3];
		try {
			loadDrivers(database);
			Connection connect = DriverManager.getConnection(address, user, password);
			Statement statement = connect.createStatement();
			ResultSet resultSet = statement.executeQuery("SELECT * FROM " + table);
			Heading heading = new Heading();
			if (duplicates == DuplicateHandling.DUP_COUNT)
				heading.add("_DUP_COUNT", TypeInteger.getInstance());
			else if (duplicates == DuplicateHandling.AUTOKEY)
				heading.add("_AUTOKEY", TypeInteger.getInstance());
			for (int column = 1; column <= resultSet.getMetaData().getColumnCount(); column++)
				heading.add(ColumnName.cleanName(resultSet.getMetaData().getColumnName(column)), TypeCharacter.getInstance());
			RelvarHeading relvarHeading = new RelvarHeading(heading);
			if (duplicates == DuplicateHandling.AUTOKEY) {
				SelectAttributes keyAttribute = new SelectAttributes();
				keyAttribute.add("_AUTOKEY");
				relvarHeading.addKey(keyAttribute);
			}
			return relvarHeading;
		} catch (SQLException e) {
			throw new ExceptionSemantic("EX0016: " + e.toString() + ". Drivers available: " + obtainDriverList());
		}
	}

	@Override
	public String getSourceDefinition() {
		return "EXTERNAL JDBC \"" + address + "," + user + "," + password + "," + table + "\" " + duplicates;
	}

	public RelvarJDBCMetadata(RelDatabase database, String owner, String spec, DuplicateHandling duplicates) {
		super(database, getHeading(database, spec, duplicates), owner);		
		String[] values = CSVLineParse.parseTrimmed(spec);
		address = values[0];
		user = values[1];
		password = values[2];
		table = values[3];
		this.duplicates = duplicates;
		connectionString = spec;
	}

	@Override
	public RelvarGlobal getRelvar(String name, RelDatabase database) {
		try {
			loadDrivers(database);
			Connection connect = DriverManager.getConnection(address, user, password);
			Statement statement = connect.createStatement();
			statement.executeQuery("select * from " + table);
		} catch (SQLException e) {
			throw new ExceptionSemantic("EX0019: Table " + table + " no longer exists.");
		}
		return new RelvarExternal(name, database, new Generator(database, System.out), this, duplicates);
	}

	@Override
	public void dropRelvar(RelDatabase database) {
	}

	public String getConnectionString() {
		return connectionString;
	}
	
	public String getPath() {
		return address;
	}

	public String getUser() {
		return user;
	}

	public String getPassword() {
		return password;
	}

	public String getTable() {
		return table;
	}

	@Override
	public String tableClassName() {
		return "TableJDBC";
	}

	@Override
	public String getType() {
		return "JDBC";
	}
}
