package com.wecise.odb.jdbc;

import com.wecise.odb.Error;
import com.wecise.odb.ObjectPool;
import redis.clients.jedis.Jedis;

import java.sql.*;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class ODBConnection implements Connection {
    private String address;
    private String keyspace;
    private Jedis[] connectors;
    private ObjectPool pool;
    private boolean autoCommit = false;
    private boolean closed = false;
    private int numConns = 2; // number of connections per host (default: 2)

    ODBConnection(String address, String keyspace) throws SQLException {
        this.address = address;
        this.keyspace = keyspace;
        String[] cluster = address.split(",");
        connectors = new Jedis[cluster.length * numConns];
        int num = 0;
        for (String c: cluster) {
            String[] a = c.split(":");
            String host = a[0];
            int port = 11001;
            if (a.length > 1) {
                port = Integer.parseInt(a[1]);
            }
            for (int k = 0; k < numConns; k++) {
                Jedis jc = new Jedis(host, port);
                String s = jc.ping();
                if (!"PONG".equals(s)) {
                    throw new SQLException("Unable to connect to " + host + ":" + port + ", no PONG message received");
                }
                this.connectors[num] = jc;
                num++;
            }
        }
        this.pool = new ObjectPool(connectors);
    }

    public Jedis getConnector() {
        Jedis conn;
        ArrayList<Jedis> offlineSockets = new ArrayList<>();
        while (true) {
            conn = (Jedis) this.pool.get();
            if (conn.isConnected()) {
                break;
            } else {
                offlineSockets.add(conn);
            }
        }
        for (Jedis jc: offlineSockets) {
            releaseConnector(jc);
        }

        return conn;
    }

    public void releaseConnector(Jedis socket) {
        this.pool.release(socket);
    }

    @Override
    public Statement createStatement() throws SQLException {
        return new ODBStatement(this);
    }

    @Override
    public PreparedStatement prepareStatement(String s) throws SQLException {
        return new ODBPreparedStatement(this, s);
    }

    @Override
    public CallableStatement prepareCall(String s) throws SQLException {
        throw new SQLFeatureNotSupportedException(Error.methodNotSupported + ": Connection.prepareCall(String)");
    }

    @Override
    public String nativeSQL(String s) throws SQLException {
        throw new SQLFeatureNotSupportedException(Error.methodNotSupported + ": Connection.nativeSQL(String)");
    }

    @Override
    public void setAutoCommit(boolean b) throws SQLException {
        this.autoCommit = b;
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return this.autoCommit;
    }

    @Override
    public void commit() throws SQLException {

    }

    @Override
    public void rollback() throws SQLException {

    }

    @Override
    public void close() throws SQLException {
        if (!this.closed) {
            this.pool.closeObjectPool();
            for (Jedis c: this.connectors) {
                if (c.isConnected()) {
                    c.close();
                }
            }
        }
        this.closed = true;
    }

    @Override
    public boolean isClosed() throws SQLException {
        return this.closed;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return new ODBDatabaseMetaData(this);
    }

    @Override
    public void setReadOnly(boolean b) throws SQLException {

    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return false;
    }

    @Override
    public void setCatalog(String s) throws SQLException {

    }

    @Override
    public String getCatalog() throws SQLException {
        return null;
    }

    @Override
    public void setTransactionIsolation(int i) throws SQLException {

    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return 0;
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {

    }

    @Override
    public Statement createStatement(int i, int i1) throws SQLException {
        return new ODBStatement(this);
    }

    @Override
    public PreparedStatement prepareStatement(String s, int i, int i1) throws SQLException {
        return new ODBPreparedStatement(this, s);
    }

    @Override
    public CallableStatement prepareCall(String s, int i, int i1) throws SQLException {
        throw new SQLFeatureNotSupportedException(Error.methodNotSupported + ": Connection.prepareCall(String, int, int)");
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return null;
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {

    }

    @Override
    public void setHoldability(int i) throws SQLException {

    }

    @Override
    public int getHoldability() throws SQLException {
        return 0;
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return null;
    }

    @Override
    public Savepoint setSavepoint(String s) throws SQLException {
        return null;
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {

    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {

    }

    @Override
    public Statement createStatement(int i, int i1, int i2) throws SQLException {
        return new ODBStatement(this);
    }

    @Override
    public PreparedStatement prepareStatement(String s, int i, int i1, int i2) throws SQLException {
        return new ODBPreparedStatement(this, s);
    }

    @Override
    public CallableStatement prepareCall(String s, int i, int i1, int i2) throws SQLException {
        throw new SQLFeatureNotSupportedException(Error.methodNotSupported + ": Connection.prepareCall(String, int, int, int)");
    }

    @Override
    public PreparedStatement prepareStatement(String s, int i) throws SQLException {
        return new ODBPreparedStatement(this, s);
    }

    @Override
    public PreparedStatement prepareStatement(String s, int[] ints) throws SQLException {
        return new ODBPreparedStatement(this, s);
    }

    @Override
    public PreparedStatement prepareStatement(String s, String[] strings) throws SQLException {
        return new ODBPreparedStatement(this, s);
    }

    @Override
    public Clob createClob() throws SQLException {
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return null;
    }

    @Override
    public boolean isValid(int i) throws SQLException {
        return false;
    }

    @Override
    public void setClientInfo(String s, String s1) throws SQLClientInfoException {

    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {

    }

    @Override
    public String getClientInfo(String s) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String s, Object[] objects) throws SQLException {
        return null;
    }

    @Override
    public Struct createStruct(String s, Object[] objects) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String s) throws SQLException {

    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {

    }

    @Override
    public void setNetworkTimeout(Executor executor, int i) throws SQLException {

    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }

    @Override
    public <T> T unwrap(Class<T> aClass) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> aClass) throws SQLException {
        return false;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getKeyspace() {
        return keyspace;
    }

    public void setKeyspace(String keyspace) {
        this.keyspace = keyspace;
    }
}
