package brn.distsim.ormapper.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

public class DbBinarySaver {

  protected int simulationId;

  protected Connection connection;


  private SimpleDateFormat sqlDate = new SimpleDateFormat(
  "yyyy-MM-dd HH:mm:ss");

  private PreparedStatement expectedEndQuery;

  private PreparedStatement saveQuery;

  protected int counter;

  public DbBinarySaver(String resUrl, String resUser, String resPasswd, String defUrl, String defUser, String defPasswd) throws SQLException, ClassNotFoundException {
    setProperties(resUrl, resUser, resPasswd);
    Connection defConnection = DriverManager.getConnection(defUrl, defUser,
        defPasswd);
    Statement statement = defConnection.createStatement();
    statement.executeUpdate(
        "INSERT INTO simulations SET groupId=1, defined=NOW();",
        Statement.RETURN_GENERATED_KEYS);
    ResultSet key = statement.getGeneratedKeys();
    key.next();
    simulationId = key.getInt(1);
    setProperties(resUrl, resUser, resPasswd);
    initialize();
  }

  public DbBinarySaver(String dbUrl, String dbUser, String dbPasswd, int jobId) throws ClassNotFoundException, SQLException {
    this.simulationId = jobId;
    setProperties(dbUrl, dbUser, dbPasswd);
    initialize();
  }

  private void setProperties(String dbUrl, String dbUser, String dbPasswd)
  throws SQLException, ClassNotFoundException {
    Properties hbProps = new Properties();

    hbProps.put("driver_class", "com.mysql.jdbc.Driver");
    hbProps.put("user", dbUser);
    hbProps.put("password", dbPasswd);
    hbProps.put("batch_size", "50");
    hbProps.put("use_streams_for_binary", "true");
    hbProps.put("isolation", "1");
    hbProps.put("autocommit", "true");
    hbProps.put("batch_versioned_data", "true");

    hbProps.put("cachePrepStmts", "true");
    hbProps.put("cacheCallableStatements", "true");
    hbProps.put("cacheServerConfiguration", "true");
    hbProps.put("useLocalSessionState", "true");
    hbProps.put("elideSetAutoCommits", "true");
    hbProps.put("alwaysSendSetIsolation", "false");
    hbProps.put("enableQueryTimeouts", "true");

    hbProps.put("allowMultiQueries", "true");
    hbProps.put("cacheResultSetMetadata", "true");
    hbProps.put("callableStatementCacheSize", "200");
    hbProps.put("detectServerPreparedStmts", "true");
    hbProps.put("metadataCacheSize", "100");
    hbProps.put("preparedStatementCacheSize", "100");
    hbProps.put("preparedStatementCacheSqlLimit", "256");

    hbProps.put("reportMetricsIntervalMillis", "30000");
    hbProps.put("gatherPerformanceMetrics", "true");

    hbProps.put("emulateUnsupportedPstmts", "false");
    hbProps.put("rewriteBatchedStatements", "true");

    Class.forName("com.mysql.jdbc.Driver");
    connection = DriverManager.getConnection(dbUrl, hbProps);
  }

  private void initialize() throws SQLException {
    connection.setAutoCommit(true);
    connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
    expectedEndQuery = connection.prepareStatement("UPDATE progress "+
        "SET expectedEnd = ? WHERE simulationId = ?;");
    saveQuery = connection.prepareStatement("INSERT INTO binarystore "+
        "VALUES (?, ?, ?, ?, ?);");
    // TODO INSERT DELAYED
    counter = 0;
  }

  public synchronized void save(int id, int type, String path, Object obj)
      throws SQLException, IOException {
    counter++;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);

    byte[] obj_ser = baos.toByteArray();
    ByteArrayInputStream bais = new ByteArrayInputStream(obj_ser);

    try {
      saveQuery.setInt(1, simulationId);
      saveQuery.setInt(2, id);
      saveQuery.setInt(3, type);
      saveQuery.setString(4, path);
      saveQuery.setBinaryStream(5, bais, obj_ser.length);
      saveQuery.addBatch();

      if (0 == counter % 50) {
        counter = 0;
        saveQuery.executeBatch();
      }
    } finally {
      oos.close();
      bais.close();
    }
  }

  public void setExpectedEnd(int timestamp) {
    try {
      expectedEndQuery.setInt(1, simulationId);
      expectedEndQuery.setString(2, sqlDate.format(new Date(timestamp)));
      expectedEndQuery.execute();
      connection.commit();
    } catch (SQLException e) {
      // not that important
      e.printStackTrace();
    }

  }

  public void close() {
    if (null != connection) {
      try {
        if (counter != 0)
          saveQuery.executeBatch();
        connection.close();
      } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      connection = null;
    }
  }

  public void finalize() throws Throwable {
    close();
  }

  public int getSimulationId() {
    return simulationId;
  }
}
