package com.quantum.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.quantum.IQuantumConstants;
import com.quantum.QuantumPlugin;
import com.quantum.adapters.AdapterFactory;
import com.quantum.adapters.DatabaseAdapter;
import com.quantum.sql.ConnectionEstablisher;
import com.quantum.sql.MultiSQLServer;

import org.eclipse.jface.preference.IPreferenceStore;

/**
 * Class Bookmark holds the "static" information of a bookmark, that is the data that
 * is saved and loaded from the external file and describes a bookmark. This info will
 * be filled up by the end user.
 * 
 * @author root
 */
public class Bookmark implements Displayable {
	
	public static final int SCHEMA_RULE_USE_ALL = 1;
	public static final int SCHEMA_RULE_USE_DEFAULT = 2;
	public static final int SCHEMA_RULE_USE_SELECTED = 3;
    
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
	private String name = ""; //$NON-NLS-1$
	private String username = ""; //$NON-NLS-1$
    private String password = ""; //$NON-NLS-1$
    private String connectionUrl = ""; //$NON-NLS-1$
    private JDBCDriver driver;
    
    private int schemaRule = SCHEMA_RULE_USE_ALL;
    
    /**
     * A quick list is a list of favourite tables that a person might want to view
     * without having to look at the entire list of tables.
     */
    private Map quickList = new Hashtable();
	private Set schemas = new HashSet();   
	private Connection connection = null;
    private ConnectionEstablisher connectionEstablisher;
    private boolean changed = true;
    private List queries = Collections.synchronizedList(new ArrayList());
    private boolean promptForPassword = false;
    private boolean autoCommit = true;
    private String autoCommitPreference = IQuantumConstants.autoCommitTrue;
	private Database database;
	
	public Bookmark() {
        this(MultiSQLServer.getInstance());
	}
    
    public Bookmark(ConnectionEstablisher connectionEstablisher) {
        this.connectionEstablisher = connectionEstablisher;
    }

	public Bookmark(Bookmark data) {
		this();
		setName(data.getName());
		setUsername(data.getUsername());
		setPassword(data.getPassword());
		setConnect(data.getConnect());
		setJDBCDriver(data.getJDBCDriver());
        setPromptForPassword(data.getPromptForPassword());
        setAutoCommit(data.isAutoCommit());
        setAutoCommitPreference(data.getAutoCommitPreference());
        setSchemaRule(data.getSchemaRule());
        
        this.schemas.addAll(data.schemas);
        this.quickList = new Hashtable(data.quickList);
	}

	/**
	 * Returns the JDBC URL.
	 * @return String
	 */
	public String getConnect() {
		return this.connectionUrl;
	}

	/**
	 * Returns the password.
	 * @return String
	 */
	public String getPassword() {
		if (this.promptForPassword) {
			return null;
		} else {
			return this.password;
		}
	}

	/**
	 * Returns the username.
	 * @return String
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * Sets the connect.
	 * @param connectionUrl The connect to set
	 */
	public void setConnect(String connectionUrl) {
		if (connectionUrl == null) {
			connectionUrl = ""; //$NON-NLS-1$
		}
		this.connectionUrl = connectionUrl;
	}

	/**
	 * Sets the password.
	 * @param password The password to set
	 */
	public void setPassword(String password) {
		if (password == null) {
			password = ""; //$NON-NLS-1$
		}
		this.password = password;
	}

	/**
	 * Sets the username.
	 * @param username The username to set
	 */
	public void setUsername(String username) {
		if (username == null) {
			username = ""; //$NON-NLS-1$
		}
		this.username = username;
	}

	/**
	 * Returns the name.
	 * @return String
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * @param name The name to set
	 */
	public void setName(String name) {
		if (name == null) {
			name = ""; //$NON-NLS-1$
		}
        if (!name.equals(this.name)) {
         
            String oldName = this.name;
            this.name = name;
            this.propertyChangeSupport.firePropertyChange("name", oldName, this.name);
            this.changed = true;
        }
	}

	public boolean isEmpty() {
		if (name.equals("") && //$NON-NLS-1$
		username.equals("") && //$NON-NLS-1$
		password.equals("") && //$NON-NLS-1$
		connectionUrl.equals("") && //$NON-NLS-1$
		driver.equals("") && //$NON-NLS-1$
		driver == null) {
			return true;
		}
		return false;
	}
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("["); //$NON-NLS-1$
		buffer.append("name="); //$NON-NLS-1$
		buffer.append(name);
		buffer.append(", "); //$NON-NLS-1$
		buffer.append("username="); //$NON-NLS-1$
		buffer.append(username);
		buffer.append(", "); //$NON-NLS-1$
		buffer.append("password=****"); //$NON-NLS-1$
		buffer.append(", "); //$NON-NLS-1$
		buffer.append("connect="); //$NON-NLS-1$
		buffer.append(connectionUrl);
		buffer.append(", "); //$NON-NLS-1$
		buffer.append("driver="); //$NON-NLS-1$
		buffer.append(driver);
		buffer.append("]"); //$NON-NLS-1$
		return buffer.toString();
	}
	
    public Connection connect(PasswordFinder passwordFinder) throws ConnectionException {
        boolean isConnected = isConnected();
        if (this.connection == null) {
            this.connection = this.connectionEstablisher.connect(this, passwordFinder);
        }
        
        if (isConnected() != isConnected) {
            this.propertyChangeSupport.firePropertyChange(
                "connected", isConnected, isConnected());
        }
        return this.connection;
    }

    /**
	 * Returns the connection object. 
	 * @return the Connection object associated with the current JDBC source.
	 * 
	 */
    public Connection getConnection() throws NotConnectedException {
    	if (this.connection == null) {
            throw new NotConnectedException();
    	}
        return this.connection;
    }

    /**
	 * @return true if the BookmarkNode is connected to a JDBC source
	 */
    public boolean isConnected() {
    	return (connection != null);
    }

    /**
	 * Sets the connection member. From that moment on the BookmarkNode is "connected" (open)
	 * @param connection : a valid connection to a JDBC source
	 */
    public void setConnection(Connection connection) {
    	this.connection = connection;
    }

    public void disconnect() throws SQLException {
        boolean isConnected = isConnected();
        try {
            if (this.connection != null) {
                this.connectionEstablisher.disconnect(this.connection);
            }
        } finally {
            this.connection = null;
            this.database = null;
            if (isConnected() != isConnected) {
                this.propertyChangeSupport.firePropertyChange(
                    "connected", isConnected, isConnected());
            }
        }
    }
    public void addSchema(String schema) {
        if (schema != null && schema.trim().length() > 0) {
            addSchema(new Schema(schema));
        }
    }

    public void addSchema(Schema qualifier) {
        if (qualifier != null) {
            this.schemas.add(qualifier);
            this.changed = true;
            this.propertyChangeSupport.firePropertyChange("schemas", null, null);
        }
    }

    public void setSchemaSelections(Schema[] schemas) {
        this.schemas.clear();
        for (int i = 0, length = (schemas == null) ? 0 : schemas.length;
            i < length;
            i++) {
            this.schemas.add(schemas[i]);
            
        }
        this.changed = true;
        this.propertyChangeSupport.firePropertyChange("schemas", null, null);
    }

    /**
     * @return a list of all the schemas that have been set up.
     */
    public Schema[] getSchemaSelections() {
        List list = new ArrayList(this.schemas);
        Collections.sort(list);
        return (Schema[]) list.toArray(new Schema[list.size()]);
    }
    
    public Schema[] getSchemas() throws NotConnectedException, SQLException {
    	Schema[] schemas = null;
    	if (useUsernameAsSchema()) {
    		// do nothing
    	} else if (useAllSchemas()) {
	    	schemas = getDatabase().getSchemas();
    	} else {
    		schemas = verifySchemas(getSchemaSelections());
    	}
    	return (schemas == null || schemas.length == 0) 
				? new Schema[] { getDefaultSchema() } 
    			: schemas;
    }

	/**
	 * @param schemaSelections
	 * @return
	 * @throws SQLException
	 * @throws NotConnectedException
	 */
	private Schema[] verifySchemas(Schema[] schemaSelections) 
			throws NotConnectedException, SQLException {
		Schema[] schemasFromDatabase = getDatabase().getSchemas();
		List list = Arrays.asList(schemasFromDatabase);
		for (int i = 0, length = schemaSelections == null ? 0 : schemaSelections.length; 
				i < length; i++) {
			schemaSelections[i].setExists(list.contains(schemaSelections[i]));
		}
		return schemaSelections;
	}

	/**
	 * @return
	 * @throws NotConnectedException
	 * @throws SQLException
	 */
	private Schema getDefaultSchema() throws NotConnectedException, SQLException {
		String username = getDatabase().getUsername();
		String actual = getAdapter().getDefaultSchema(username);
		return new Schema(actual, username, true);
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object obj) {
		if (!(obj instanceof Bookmark)) return false;
		String name = ((Bookmark)obj).getName();
		if (name.equals(this.getName())) return true;
		return false;
	}

    /**
     * @param listener
     */
    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
        this.propertyChangeSupport.addPropertyChangeListener(listener);
    }

    /**
     * @param listener
     */
    public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
        this.propertyChangeSupport.removePropertyChangeListener(listener);
    }

    public void addQuickListEntry(String type, String schemaName, String name, boolean isSynonym) {
        Entity entity = EntityFactory.getInstance().create(this, schemaName, name, type, isSynonym);
        this.quickList.put(entity.getQualifiedName(), entity);
        this.propertyChangeSupport.firePropertyChange("quickList", null, null);
        this.changed = true;
    }
    
    public void addQuickListEntry(Entity entity) {
        addQuickListEntry(entity.getType(), entity.getSchema(), entity.getName(), entity.isSynonym());
    }
    
    public void removeQuickListEntry(Entity entity) {
        if (entity != null  && this.quickList.containsKey(entity.getQualifiedName())) {
            this.quickList.remove(entity.getQualifiedName());
            this.propertyChangeSupport.firePropertyChange("quickList", null, null);
            this.changed = true;
        }
    }
    
    public Entity[] getQuickListEntries() {
        return (Entity[]) this.quickList.values().toArray(new Entity[this.quickList.size()]);
    }
    
    public boolean hasQuickList() {
        return !this.quickList.isEmpty();
    }
    /**
     * @return
     */
    public boolean isChanged() {
        return changed;
    }

    /**
     * @param b
     */
    public void setChanged(boolean b) {
        changed = b;
    }
    
    public Database getDatabase() throws NotConnectedException {
        if (!isConnected()) {
            throw new NotConnectedException();
        }
        if (this.database == null) {
        	this.database = new Database(this);
        }
        return this.database;
    }
    
    public DatabaseAdapter getAdapter() {
        return this.driver == null 
				? null 
				: AdapterFactory.getInstance().getAdapter(this.driver.getType());
    }

    public Entity[] getEntitiesForSchema(Schema schema, String type) throws SQLException {
        try {
            Entity[] entities = getDatabase().getEntities(this, schema, type);
            return entities;
        } catch (NotConnectedException e) {
            return new Entity[0];
        }
    }
    
    public Entity getEntity(Schema schema, String name) throws SQLException {
        Entity result = null;
        if (schema != null && name != null) {
            Entity[] entities = getEntitiesForSchema(schema, null);
            for (int i = 0, length = (entities == null) ? 0 : entities.length;
                result == null && i < length;
                i++) {
                if (schema.equals(entities[i].getSchema()) &&
                    name.equals(entities[i].getName())) {
                    result = entities[i];
                }
            }
        }
        return result;
    }
    
    public boolean isInQuickList(Entity entity) {
        return this.quickList.containsKey(entity.getQualifiedName());
    }
    
    /**
     * 
     * @param queryString
     */
    public void addQuery(String queryString) {
        if (this.queries.contains(queryString)) {
            this.queries.remove(queryString);
        }
        this.queries.add(queryString);
        
        int size = getQueryHistorySize();
        
        while (this.queries.size() > size) {
            this.queries.remove(0);
        }
        this.propertyChangeSupport.firePropertyChange("queries", null, null);
        this.changed = true;
    }
    
    public String[] getQueries() {
        return (String[]) this.queries.toArray(new String[this.queries.size()]);
    }
    
    private int getQueryHistorySize() {
        IPreferenceStore store =
            QuantumPlugin.getDefault().getPreferenceStore();
        return store.getInt(getClass().getName() + ".queryHistorySize"); //$NON-NLS-1$
    }
    /**
     * @return
     */
    public boolean getPromptForPassword() {
        return promptForPassword;
    }

    /**
     * @param b
     */
    public void setPromptForPassword(boolean b) {
        promptForPassword = b;
    }

	/**
	 * @return
	 */
	public boolean isAutoCommit() {
		return autoCommit;
	}

	/**
	 * @return
	 */
	public String getAutoCommitPreference() {
		return autoCommitPreference;
	}

	/**
	 * @param b
	 */
	public void setAutoCommit(boolean b) {
		autoCommit = b;
	}

	/**
	 * @param string
	 */
	public void setAutoCommitPreference(String string) {
		autoCommitPreference = string;
	}

	// Returns true or false indicating whether this bookmark must be set to AutoCommit on connection or not 
	public boolean getDefaultAutoCommit(){
		if (autoCommitPreference.equals(IQuantumConstants.autoCommitTrue)) return true;
		else if (autoCommitPreference.equals(IQuantumConstants.autoCommitFalse)) return false;
		else if (autoCommitPreference.equals(IQuantumConstants.autoCommitSaved)) return autoCommit;
		
		return true;	
	}
	
	public void setJDBCDriver(JDBCDriver jdbcDriver) {
		this.driver = BookmarkCollection.getInstance().findDriver(jdbcDriver);
        this.changed = true;
	}
	
	public JDBCDriver getJDBCDriver() {
		return this.driver;
	}
	public boolean useAllSchemas() {
		return this.schemaRule == SCHEMA_RULE_USE_ALL;
	}
	public boolean useUsernameAsSchema() {
		return this.schemaRule == SCHEMA_RULE_USE_DEFAULT;
	}
	public boolean useSelectedSchemas() {
		return this.schemaRule == SCHEMA_RULE_USE_SELECTED;
	}
	public int getSchemaRule() {
		return this.schemaRule;
	}
	public void setSchemaRule(int schemaRule) {
        if (this.schemaRule != schemaRule) {
            this.schemaRule = schemaRule;
            this.propertyChangeSupport.firePropertyChange("schemas", null, null);
        }
	}

	public String getDisplayName() {
		return this.name;
	}

	/**
	 * @param query
	 */
	public void removeQuery(String query) {
		if (this.queries.remove(query)) {
	        this.propertyChangeSupport.firePropertyChange("queries", null, null);
	        this.changed = true;
		}
	}
}