package com.Series40Book.midp.model;

import com.Series40Book.midp.controller.WorkerRunnable;

import java.io.*;
import javax.microedition.rms.*;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.Connector;

/**
 * Manage the history stored in local RMS
 *
 */
public class HistoryManager {

  private static int [] correctNums = null;
  private static int [] wrongNums = null;
  private static long [] times = null;
  private static long [] timestamps = null;

  private static String storeName = "History";
  // indicates whether the in memory arrays are up to date
  private static boolean isUpToDate = false;
  // the last time this mobile is synced with the backend
  private static long lastSyncTime = -1;

  private static String serverurl;


  private HistoryManager () {
  }

  public static void init (String u) throws Exception {
    serverurl = u;
    RecordStore rs =
        RecordStore.openRecordStore(storeName, true);

    try {
      rs.getRecord(1);

    } catch (Exception e) {
      System.out.println("Starting the history manager");
      e.printStackTrace ();

      byte [] buf = long2ByteArray(0);
      rs.addRecord(buf, 0, buf.length);
    }

    rs.closeRecordStore ();
  }

  public static void clear () {
    try {
      RecordStore.deleteRecordStore(storeName);
      init (serverurl);
      isUpToDate = false;

    } catch (Exception e) {
      System.out.println("Clear error");
      e.printStackTrace ();
    }
  }

  static public void storeScore(int correctNum,
            int wrongNum, long time) throws Exception {
    // Get the current time stamp
    long timestamp = System.currentTimeMillis();

    RecordStore rs =
        RecordStore.openRecordStore(storeName, true);

    // Add a new score
    ByteArrayOutputStream bos =
        new ByteArrayOutputStream ();
    DataOutputStream dbos = new DataOutputStream (bos);
    dbos.writeInt(correctNum);
    dbos.writeInt(wrongNum);
    dbos.writeLong(time);
    dbos.writeLong(timestamp);
    dbos.flush();
    byte [] buf = bos.toByteArray ();
    dbos.close();
    bos.close();
    rs.addRecord(buf, 0, buf.length);

    rs.closeRecordStore();

    isUpToDate = false;
  }

  static private void readFromRMS () {

    RecordStore rs = null;

    try {
      rs = RecordStore.openRecordStore(storeName, true);

      // The first record is the last sync time stamp
      int size = rs.getNumRecords() - 1;
      correctNums = new int[size];
      wrongNums = new int[size];
      times = new long[size];
      timestamps = new long[size];

      // RecordEnumeration re = rs.enumerateRecords(null, null, false);
      // int i = 0;
      // while ( re.hasNextElement() ) {
      for (int i = 2; i <= size + 1; i++) {
        // byte [] buf = re.nextRecord ();
        byte [] buf = rs.getRecord(i);
        ByteArrayInputStream bis = new ByteArrayInputStream (buf);
        DataInputStream dbis = new DataInputStream (bis);
        correctNums[i - 2] = dbis.readInt();
        wrongNums[i - 2] = dbis.readInt();
        times[i - 2] = dbis.readLong();
        timestamps[i - 2] = dbis.readLong();
        dbis.close();
        bis.close();
      }
      isUpToDate = true;

    } catch (Exception e) {
      e.printStackTrace ();
      correctNums = new int[] {-1};
      wrongNums = new int[] {-1};
      times = new long[] {-1};
      timestamps = new long[] {-1};

    } finally {
      try {
        rs.closeRecordStore();
      } catch (Exception ex) {}
    }

    /*
    for (int i = 0; i < correctNums.length; i++) {
      System.out.println("Corr " + correctNums[i]);
      System.out.println("Wrong " + wrongNums[i]);
      System.out.println("Time " + times[i]);
    }
    */
  }

  static public void synchronize (WorkerRunnable worker)
                                    throws Exception {

    RecordStore rs =
        RecordStore.openRecordStore(storeName, true);

    worker.updateWaitStatus ("Getting last sync time");
    // get the last sync time stamp
    lastSyncTime = byteArray2Long(rs.getRecord(1));
    worker.updateWaitStatus("Client last sync: " +
                            lastSyncTime);
    readFromRMS ();

    HttpConnection conn = null;
    DataInputStream hdin = null;
    DataOutputStream hdout = null;

    try {
      worker.updateWaitStatus ("Opening connection");
      conn = (HttpConnection) Connector.open(serverurl);
      conn.setRequestMethod(HttpConnection.POST);
      hdout = conn.openDataOutputStream();
      worker.updateWaitStatus ("Sending sync time");
      hdout.writeInt(OpCodes.SYNC01); // Submit opcode
      // PUT the time stamp to the HTTP sync site
      hdout.writeLong(lastSyncTime);
      hdout.flush();

      worker.updateWaitStatus ("Getting server set");
      hdin = conn.openDataInputStream();
      // GET the server sync time stamp
      long serverSyncTime = hdin.readLong();
      worker.updateWaitStatus("Server last sync: " +
                              serverSyncTime);
      // GET the server update list
      int size = hdin.readInt();
      worker.updateWaitStatus("Size from server: "+size);

      for (int i = 0; i < size; i++) {
        worker.updateWaitStatus ("Getting item " + i);
        int correctNum = hdin.readInt();
        int wrongNum = hdin.readInt();
        long time = hdin.readLong();
        long ts = hdin.readLong();
        // Insert the server update list into the RMS
        ByteArrayOutputStream bos =
            new ByteArrayOutputStream ();
        DataOutputStream dbos =
            new DataOutputStream (bos);
        dbos.writeInt(correctNum);
        dbos.writeInt(wrongNum);
        dbos.writeLong(time);
        dbos.writeLong(ts);
        dbos.flush();
        byte [] buf = bos.toByteArray ();
        dbos.close();
        bos.close();
        rs.addRecord(buf, 0, buf.length);
      }
      // Close everything
      hdin.close();
      hdout.close();
      conn.close();

      // Determine the client update list size
      worker.updateWaitStatus ("Determine client set");
      size = 0;
      for (int i = 0; i < timestamps.length; i++) {
        if (timestamps[i] > serverSyncTime) {
          size++;
        }
      }

      // PUT the client update list
      worker.updateWaitStatus ("Writing to the server");
      conn = (HttpConnection) Connector.open(serverurl);
      conn.setRequestMethod(HttpConnection.POST);
      hdout = conn.openDataOutputStream();
      hdout.writeInt(OpCodes.SYNC02); // Submit opcode
      hdout.writeInt(size);

      for (int i = 0; i < timestamps.length; i++) {
        if (timestamps[i] > serverSyncTime) {
          worker.updateWaitStatus ("Sending item " + i);
          hdout.writeInt(correctNums[i]);
          hdout.writeInt(wrongNums[i]);
          hdout.writeLong(times[i]);
          hdout.writeLong(timestamps[i]);
        }
      }
      hdout.flush();
      // Close everything
      hdout.close();
      conn.close();

      // Update the "last sync time" timestamp,
      // which is the first record in the store
      long timestamp = System.currentTimeMillis();
      byte [] buf = long2ByteArray(timestamp);
      rs.setRecord(1, buf, 0, buf.length);
      worker.updateWaitStatus("Update sync: " +
                              timestamp);

      // Close the record store
      rs.closeRecordStore();

      isUpToDate = false;

    } catch (Exception e) {
      e.printStackTrace();
      throw new Exception ("Connection error");
    } finally {
      try {
        if (conn != null) conn.close();
        if (hdout != null) hdout.close();
        if (hdin != null) hdin.close();
      } catch (Exception e) {}
    }
  }

  static public int [] getCorrectNums () {
    if (!isUpToDate) {
      readFromRMS ();
    }
    return correctNums;
  }

  static public int [] getWrongNums () {
    if (!isUpToDate) {
      readFromRMS ();
    }
    return wrongNums;
  }

  static public long [] getTimes () {
    if (!isUpToDate) {
      readFromRMS ();
    }
    return times;
  }

  static public byte [] long2ByteArray (long l) {
    try {
      ByteArrayOutputStream bos = new ByteArrayOutputStream ();
      DataOutputStream dbos = new DataOutputStream (bos);
      dbos.writeLong(l);
      dbos.flush();
      byte [] buf = bos.toByteArray ();
      dbos.close();
      bos.close();
      return buf;
    } catch (Exception e) {
      System.out.println("Error in converting long to ByteArray");
      e.printStackTrace ();
      return null;
    }
  }

  static public long byteArray2Long (byte [] b) {
    try {
      ByteArrayInputStream bis = new ByteArrayInputStream (b);
      DataInputStream dbis = new DataInputStream (bis);
      long l = dbis.readLong();
      dbis.close();
      bis.close();
      return l;
    } catch (Exception e) {
      System.out.println("Error in converting ByteArray to long");
      e.printStackTrace ();
      return 0;
    }
  }

}
