package server;

import java.util.Enumeration;
import java.util.Properties;
import java.io.*;
import javax.swing.JOptionPane;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import dbms.*;
import console.*;
import shared.*;

public class DatabaseServer {
	
	private boolean m_initialized;
	public static String dbmsListFileName;
	public static Properties DBMSList;
	public static DBMS database;
	public static SystemConsole console;
	public static ExtendedClassLoader classLoader;
	private Registry m_registry;
	final public static String DEFAULT_DBMS_LIST_FILENAME = "dbms.cfg";
	final public static String DATABASE_REGISTRY_NAME = "ConnectFourDatabase";
	final public static int DEFAULT_REGISTRY_PORT = 1099;
	
	public DatabaseServer() {
		console = new SystemConsole();
		classLoader = new ExtendedClassLoader();
		DBMSList = new Properties();
		m_initialized = false;
	}
	
	// initialize the server with no parameters (use all defaults)
	public boolean initialize() {
		return initialize(null, null, true, DEFAULT_REGISTRY_PORT);
	}
	
	// initialize the server with RMI registry parameters, use defaults for the rest
	public boolean initialize(boolean integratedRegistry, int registryPort) {
		return initialize(null, null, integratedRegistry, registryPort);
	}
	
	// initialize the server with database name, RMI registry parameters and the default dbms config file
	public boolean initialize(String dbmsName, boolean integratedRegistry, int registryPort) {
		return initialize(null, dbmsName, integratedRegistry, registryPort);
	}
	
	// initialize the server with the specified parameters
	public boolean initialize(String databaseConfigFileName, String dbmsName, boolean integratedRegistry, int registryPort) {
		if(m_initialized) { return false; }
		if(registryPort < 1 || registryPort > 65535) { registryPort = DEFAULT_REGISTRY_PORT; }
		
		// validate the dbms list file name and store it
		dbmsListFileName = (databaseConfigFileName == null) ? DEFAULT_DBMS_LIST_FILENAME : databaseConfigFileName;
		
		// open the dbms list
		InputStream in;
		try {
			in = new FileInputStream(dbmsListFileName);
		}
		catch(FileNotFoundException e) {
			System.err.println("Error opening database management system list \"" + dbmsListFileName + "\": " + e.getMessage()); 
			return false;
		}
		
		// read the list of dbms implementations
		try {
			DBMSList.load(in);
		}
		catch(IOException e) {
			System.err.println("Error loading database management system list: " + e.getMessage());
			return false;
		}
		
		// if no dbms implementations were found, display a message and close the program
		if(DBMSList.size() == 0) {
			System.err.println("No database management systems specified in list \"" + dbmsListFileName + ".");
			return false;
		}
		
		// attempt to load the selected dbms
		Class<?> databaseClass = null;
		try {
			// verify that the selected dbms name is valid, and that it exists
			if(dbmsName == null || DBMSList.getProperty(dbmsName) == null) {
				// if there is only one dbms, load it
				if(DBMSList.size() == 1) {
					dbmsName = (String) DBMSList.keys().nextElement();
				}
				// otherwise, prompt for a dbms to load
				else {
					dbmsName = chooseDBMS();
				}
			}
			
			// if the user cancelled, close the program
			if(dbmsName == null) {
				return false;
			}
			
			// get the name of the class corresponding to the selected dbms
			String dbmsClassName = DBMSList.getProperty(dbmsName);
			if(dbmsClassName == null) { return false; }
			
			// load the dbms
			databaseClass = classLoader.loadClass(dbmsClassName);
		}
		catch(ClassNotFoundException e) {
			System.err.println("Error loading selected database management system: " + e.getMessage());
			return false;
		}
		
		// if the dbms could not be loaded, close the program
		if(databaseClass == null) { return false; }
		
		// instantiate the selected dbms
		try {
			database = (DBMS) databaseClass.newInstance();
		}
		catch(Exception e) {
			System.err.println("Error instantiating selected database management system: " + e.getMessage());
			return false;
		}
		
		// initialize the dbms
		try {
			if(!database.initialize()) { return false; }
		}
		catch(RemoteException e) {
			System.err.println("Error initializing selected database management system: " + e.getMessage());
			return false;
		}
		
		try {
			// if rmi was selected to run locally, create an internal registry on the specified port
			if(integratedRegistry) {
				m_registry = LocateRegistry.createRegistry(registryPort);
			}
			// otherwise, attempt to locate the RMI registry
			else {
				m_registry = LocateRegistry.getRegistry(registryPort);
			}
			
			// bind the dbms implementation to the registry
			m_registry.rebind(DATABASE_REGISTRY_NAME, database);
		}
		catch(Exception e) {
			System.err.println("Error setting up database management system on rmi registry: " + e.getMessage());
			return false;
		}
		
		m_initialized = true;
		
		return true;
	}
	
	// prompt for a database management system to use
	public String chooseDBMS() {
		if(DBMSList.size() == 0) { return null; }
		
		// generate a list of all of the dbms names
		Object[] choices = new Object[DBMSList.size()];
		Enumeration<Object> keys = DBMSList.keys();
		int j = 0;
		while(keys.hasMoreElements()) {
			choices[j++] = keys.nextElement();
		}
		
		return (String) JOptionPane.showInputDialog(null, "Choose a database management system to use:", "DBMS Selection", JOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
	}
	
	// stop the database server
	public void stop() {
		// reset initialization variables
		m_initialized = false;
		
		// unbind the dbms implementation
		try { m_registry.unbind(DATABASE_REGISTRY_NAME); } catch(Exception e) { }
		
		// close the database connection
		try { database.close(); } catch(Exception e) { }
	}
	
}
