package com.legendzero.lzlib.database;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class Database {

    protected Connection connection;
    protected Logger logger;

    private String identifier;
    private boolean enabled;

    public Database(String identifier, String driverClass) {
        this.connection = null;
        this.logger = Logger.getLogger(identifier);
        this.identifier = identifier;

        this.enabled = this.checkClass(identifier, driverClass);
    }

    private boolean checkClass(String database, String driverClass) {
        if (driverClass != null) {
            try {
                Class.forName(driverClass);
                return true;
            } catch (ClassNotFoundException e) {
                this.logger.log(Level.SEVERE, "Error loading " + database + " driver.", e);
                return false;
            }
        } else {
            return true;
        }
    }

    public final String getIdentifier() {
        return this.identifier;
    }

    public final void setIdentifier(String identifier) {
        this.logger = Logger.getLogger(identifier);
        this.identifier = identifier;
    }

    public final boolean isEnabled() {
        return this.enabled;
    }

    public final void setEnabled(boolean enabled) {
        if (this.enabled != enabled) {
            if (this.enabled) {
                this.closeConnection();
            }
            this.enabled = enabled;
        }
    }

    protected abstract Connection openConnection() throws SQLException;

    protected final Connection getConnection() {
        if (this.enabled) {
            try {
                if (this.connection == null || this.connection.isClosed()) {
                    this.connection = this.openConnection();
                }
            } catch (SQLException e) {
                this.logger.log(Level.SEVERE, "Error opening connection", e);
            }
            return this.connection;
        } else {
            return null;
        }
    }

    public boolean closeConnection() {
        if (this.connection != null) {
            try {
                this.connection.close();
                this.connection = null;
            } catch (SQLException e) {
                this.logger.log(Level.SEVERE, "Error closing connection", e);
                return false;
            }
        }
        return true;
    }

    protected final PreparedStatement prepareStatement(String query) throws SQLException {
        return this.getConnection().prepareStatement(query);
    }

    protected final PreparedStatement map(PreparedStatement statement, Object[] mapping)
            throws SQLException {
        int index = 1;
        for (Object object : mapping) {
            statement.setObject(index++, object);
        }
        return statement;
    }

    public <T> T query(SQLQuery<T> query, Object... mappings) {
        try (PreparedStatement statement = this.prepareStatement(query.getStatement())) {
            try (ResultSet resultSet = this.map(statement, mappings).executeQuery()) {
                return query.apply(resultSet);
            }
        } catch (SQLException e) {
            this.logger.log(Level.WARNING, "Error querying database", e);
        }
        return null;
    }

    public Integer update(SQLUpdate update, Object... mapping) {
        try (PreparedStatement statement = this.prepareStatement(update.getStatement())) {
            return this.map(statement, mapping).executeUpdate();
        } catch (SQLException e) {
            this.logger.log(Level.WARNING, "Error querying database", e);
            return -1;
        }
    }

    public <T> int[] batch(SQLBatch<T> batch, Iterable<T> iterable) {
        Function<? super T, Object>[] mappingFunctions = batch.getMappingFunctions();
        try (PreparedStatement statement = this.prepareStatement(batch.getStatement())) {
            for (T object : iterable) {
                Object[] parameters = new Object[mappingFunctions.length];

                for (int i = 0; i < parameters.length; i++) {
                    parameters[i] = mappingFunctions[i].apply(object);
                }
                this.map(statement, parameters).addBatch();
            }
            return statement.executeBatch();
        } catch (SQLException e) {
            this.logger.log(Level.WARNING, "Error querying database", e);
            return null;
        }
    }

    public <T> int[] batch(SQLBatch<T> batch, Iterator<T> iterator) {
        Function<? super T, Object>[] mappingFunctions = batch.getMappingFunctions();
        try (PreparedStatement statement = this.prepareStatement(batch.getStatement())) {
            while (iterator.hasNext()) {
                T object = iterator.next();
                Object[] parameters = new Object[mappingFunctions.length];

                for (int i = 0; i < parameters.length; i++) {
                    parameters[i] = mappingFunctions[i].apply(object);
                }
                this.map(statement, parameters).addBatch();
            }
            return statement.executeBatch();
        } catch (SQLException e) {
            this.logger.log(Level.WARNING, "Error querying database", e);
            return null;
        }
    }

    public <T> int[] batch(SQLBatch<T> batch, T... values) {
        Function<? super T, Object>[] mappingFunctions = batch.getMappingFunctions();
        try (PreparedStatement statement = this.prepareStatement(batch.getStatement())) {
            for (T object : values) {
                Object[] parameters = new Object[mappingFunctions.length];

                for (int i = 0; i < parameters.length; i++) {
                    parameters[i] = mappingFunctions[i].apply(object);
                }
                this.map(statement, parameters).addBatch();
            }
            return statement.executeBatch();
        } catch (SQLException e) {
            this.logger.log(Level.WARNING, "Error querying database", e);
            return null;
        }
    }
}

@@ -180,16 +180,39 @@
 erator;%0A
+import java.util.List;%0A
 import j
@@ -3956,32 +3956,37 @@
 able) %7B%0A        
+List%3C
 Function%3C? super
@@ -3988,34 +3988,33 @@
 super T, Object%3E
-%5B%5D
+%3E
  mappingFunction
@@ -4234,38 +4234,38 @@
 appingFunctions.
-length
+size()
 %5D;%0A%0A            
@@ -4358,35 +4358,39 @@
 mappingFunctions
-%5Bi%5D
+.get(i)
 .apply(object);%0A
@@ -4747,32 +4747,37 @@
 ator) %7B%0A        
+List%3C
 Function%3C? super
@@ -4779,34 +4779,33 @@
 super T, Object%3E
-%5B%5D
+%3E
  mappingFunction
@@ -5070,38 +5070,38 @@
 appingFunctions.
-length
+size()
 %5D;%0A%0A            
@@ -5194,35 +5194,39 @@
 mappingFunctions
-%5Bi%5D
+.get(i)
 .apply(object);%0A
@@ -5582,16 +5582,21 @@
         
+List%3C
 Function
@@ -5614,18 +5614,17 @@
  Object%3E
-%5B%5D
+%3E
  mapping
@@ -5858,22 +5858,22 @@
 nctions.
-length
+size()
 %5D;%0A%0A    
@@ -5982,19 +5982,23 @@
 unctions
-%5Bi%5D
+.get(i)
 .apply(o

Migrate batch code in Database to new types.