package org.maxgamer.QuickShop.Database;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;

public class Database {
    private DatabaseCore core;

    /**
     * Creates a new database and validates its connection.
     * 
     * If the connection is invalid, this will throw a ConnectionException.
     * 
     * @param core
     *            The core for the database, either MySQL or SQLite.
     * @throws ConnectionException
     *             If the connection was invalid
     */
    public Database(DatabaseCore core) throws ConnectionException {
        try {
            try {
                if (!core.getConnection().isValid(10)) {
                    throw new ConnectionException("Database doesn not appear to be valid!");
                }
            } catch (AbstractMethodError e) {
                // You don't need to validate this core.
            }
        } catch (SQLException e) {
            throw new ConnectionException(e.getMessage());
        }
        this.core = core;
    }

    /**
     * Returns the database core object, that this database runs on.
     * 
     * @return the database core object, that this database runs on.
     */
    public DatabaseCore getCore() {
        return core;
    }

    /**
     * Fetches the connection to this database for querying. Try to avoid doing
     * this in the main thread.
     * 
     * @return Fetches the connection to this database for querying.
     */
    public Connection getConnection() {
        return core.getConnection();
    }

    /**
     * Executes the given statement either immediately, or soon.
     * 
     * @param query
     *            The query
     * @param objs
     *            The string values for each ? in the given query.
     */
    public void execute(String query, Object... objs) {
        BufferStatement bs = new BufferStatement(query, objs);
        core.queue(bs);
    }

    /**
     * Returns true if the table exists
     * 
     * @param table
     *            The table to check for
     * @return True if the table is found
     */
    public boolean hasTable(String table) throws SQLException {
        ResultSet rs = getConnection().getMetaData().getTables(null, null, "%", null);
        while (rs.next()) {
            if (table.equalsIgnoreCase(rs.getString("TABLE_NAME"))) {
                rs.close();
                return true;
            }
        }
        rs.close();
        return false;
    }

    /**
     * Closes the database
     */
    public void close() {
        this.core.close();
    }

    /**
     * Returns true if the given table has the given column
     * 
     * @param table
     *            The table
     * @param column
     *            The column
     * @return True if the given table has the given column
     * @throws SQLException
     *             If the database isn't connected
     */
    public boolean hasColumn(String table, String column) throws SQLException {
        if (!hasTable(table))
            return false;
        String query = "SELECT * FROM " + table + " LIMIT 0,1";
        try {
            PreparedStatement ps = this.getConnection().prepareStatement(query);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                rs.getString(column); // Throws an exception if it can't find
                                      // that column
                return true;
            }
        } catch (SQLException e) {
            return false;
        }
        return false; // Uh, wtf.
    }

    /**
     * Represents a connection error, generally when the server can't connect to
     * MySQL or something.
     */
    public static class ConnectionException extends Exception {
        private static final long serialVersionUID = 8348749992936357317L;

        public ConnectionException(String msg) {
            super(msg);
        }
    }

    /**
     * Copies the contents of this database into the given database. Does not
     * delete the contents of this database, or change any settings. This may
     * take a long time, and will print out progress reports to System.out
     * 
     * This method does not create the tables in the new database. You need to
     * do that yourself.
     * 
     * @param db
     *            The database to copy data to
     * @throws SQLException
     *             if an error occurs.
     */
    public void copyTo(Database db) throws SQLException {
        ResultSet rs = getConnection().getMetaData().getTables(null, null, "%", null);
        List<String> tables = new LinkedList<String>();
        while (rs.next()) {
            tables.add(rs.getString("TABLE_NAME"));
        }
        rs.close();
        core.flush();
        // For each table
        for (String table : tables) {
            if (table.toLowerCase().startsWith("sqlite_autoindex_"))
                continue;
            System.out.println("Copying " + table);
            // Wipe the old records
            db.getConnection().prepareStatement("DELETE FROM " + table).execute();
            // Fetch all the data from the existing database
            rs = getConnection().prepareStatement("SELECT * FROM " + table).executeQuery();
            int n = 0;
            // Build the query
            String query = "INSERT INTO " + table + " VALUES (";
            // Append another placeholder for the value
            query += "?";
            for (int i = 2; i <= rs.getMetaData().getColumnCount(); i++) {
                // Add the rest of the placeholders and values. This is so we
                // have (?, ?, ?) and not (?, ?, ?, ).
                query += ", ?";
            }
            // End the query
            query += ")";
            PreparedStatement ps = db.getConnection().prepareStatement(query);
            while (rs.next()) {
                n++;
                for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
                    ps.setObject(i, rs.getObject(i));
                }
                ps.addBatch();
                if (n % 100 == 0) {
                    ps.executeBatch();
                    System.out.println(n + " records copied...");
                }
            }
            ps.executeBatch();
            // Close the resultset of that table
            rs.close();
        }
        // Success!
        db.getConnection().close();
        this.getConnection().close();
    }
}