package edu.washington.escience.myriad.column;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.almworks.sqlite4java.SQLiteException;
import com.almworks.sqlite4java.SQLiteStatement;
import com.google.common.base.Preconditions;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;

import edu.washington.escience.myriad.TupleBatch;
import edu.washington.escience.myriad.proto.TransportProto.ColumnMessage;
import edu.washington.escience.myriad.proto.TransportProto.ColumnMessage.ColumnMessageType;
import edu.washington.escience.myriad.proto.TransportProto.StringColumnMessage;

/**
 * A column of String values.
 * 
 * @author dhalperi
 * 
 */
public final class StringColumn implements Column {
  /**
   * The positions of the starts of each String in this column. Used to pack variable-length Strings
   * and yet still have fast lookup.
   */
  private final int[] startIndices;
  /** Internal structure for startIndices. */
  private final ByteBuffer startIndicesBytes;
  /**
   * The positions of the ends of each String in this column. Used to pack variable-length Strings
   * and yet still have fast lookup.
   */
  private final int[] endIndices;
  /** Internal structure for endIndices. */
  private final ByteBuffer endIndicesBytes;
  /** Contains the packed character data. */
  private final StringBuilder data;
  /** Number of elements in this column. */
  private int numStrings;

  /** Constructs an empty column that can hold up to TupleBatch.BATCH_SIZE elements. */
  public StringColumn() {
    this.startIndicesBytes =
        ByteBuffer.allocate(TupleBatch.BATCH_SIZE * (Integer.SIZE / Byte.SIZE));
    this.startIndices = startIndicesBytes.asIntBuffer().array();
    this.endIndicesBytes = ByteBuffer.allocate(TupleBatch.BATCH_SIZE * (Integer.SIZE / Byte.SIZE));
    this.endIndices = endIndicesBytes.asIntBuffer().array();
    this.data = new StringBuilder();
    this.numStrings = 0;
  }

  /**
   * Constructs an empty column that can hold up to TupleBatch.BATCH_SIZE elements, but uses the
   * averageStringSize to seed the initial size of the internal buffer that stores the
   * variable-length Strings.
   * 
   * @param averageStringSize expected average size of the Strings that will be stored in this
   *          column.
   */
  public StringColumn(final int averageStringSize) {
    this.startIndicesBytes =
        ByteBuffer.allocate(TupleBatch.BATCH_SIZE * (Integer.SIZE / Byte.SIZE));
    this.startIndices = startIndicesBytes.asIntBuffer().array();
    this.endIndicesBytes = ByteBuffer.allocate(TupleBatch.BATCH_SIZE * (Integer.SIZE / Byte.SIZE));
    this.endIndices = endIndicesBytes.asIntBuffer().array();
    this.data = new StringBuilder(averageStringSize * TupleBatch.BATCH_SIZE);
    this.numStrings = 0;
  }

  @Override
  public Object get(final int row) {
    return getString(row);
  }

  @Override
  public void getIntoJdbc(final int row, final PreparedStatement statement, final int jdbcIndex)
      throws SQLException {
    statement.setString(jdbcIndex, getString(row));
  }

  @Override
  public void getIntoSQLite(final int row, final SQLiteStatement statement, final int sqliteIndex)
      throws SQLiteException {
    statement.bind(sqliteIndex, getString(row));
  }

  /**
   * Returns the element at the specified row in this column.
   * 
   * @param row row of element to return.
   * @return the element at the specified row in this column.
   */
  public String getString(final int row) {
    Preconditions.checkElementIndex(row, numStrings);
    return data.substring(startIndices[row], endIndices[row]);
  }

  @Override
  public void put(final Object value) {
    putString((String) value);
  }

  @Override
  public void putFromJdbc(final ResultSet resultSet, final int jdbcIndex) throws SQLException {
    putString(resultSet.getString(jdbcIndex));
  }

  @Override
  public void putFromSQLite(final SQLiteStatement statement, final int index)
      throws SQLiteException {
    putString(statement.columnString(index));
  }

  /**
   * Inserts the specified element at end of this column.
   * 
   * @param value element to be inserted.
   */
  public void putString(final String value) {
    startIndices[numStrings] = data.length();
    data.append(value);
    endIndices[numStrings] = data.length();
    numStrings++;
  }

  @Override
  public void serializeToProto(final CodedOutputStream output) throws IOException {
    /* Note that we do *not* build the inner class. We pass its builder instead. */
    StringColumnMessage.Builder inner =
        StringColumnMessage.newBuilder().setData(ByteString.copyFromUtf8(data.toString()));
    inner.setStartIndices(ByteString.copyFrom(startIndicesBytes));
    inner.setEndIndices(ByteString.copyFrom(endIndicesBytes));
    ColumnMessage.newBuilder().setType(ColumnMessageType.STRING).setNumTuples(size())
        .setStringColumn(inner).build().writeTo(output);
  }

  @Override
  public int size() {
    return numStrings;
  }
}
@@ -97,16 +97,43 @@
 Buffer;%0A
+import java.nio.IntBuffer;%0A
 import j
@@ -1027,21 +1027,25 @@
 e final 
-int%5B%5D
+IntBuffer
  startIn
@@ -1311,13 +1311,17 @@
 nal 
-int%5B%5D
+IntBuffer
  end
@@ -1844,32 +1844,24 @@
 sIntBuffer()
-.array()
 ;%0A    this.e
@@ -1997,32 +1997,24 @@
 sIntBuffer()
-.array()
 ;%0A    this.d
@@ -2633,32 +2633,24 @@
 sIntBuffer()
-.array()
 ;%0A    this.e
@@ -2794,16 +2794,8 @@
 er()
-.array()
 ;%0A  
@@ -3690,21 +3690,25 @@
 tIndices
-%5Brow%5D
+.get(row)
 , endInd
@@ -3715,13 +3715,17 @@
 ices
-%5Brow%5D
+.get(row)
 );%0A 
@@ -4324,39 +4324,29 @@
 startIndices
-%5BnumStrings%5D = 
+.put(
 data.length(
@@ -4342,24 +4342,25 @@
 ata.length()
+)
 ;%0A    data.a
@@ -4391,23 +4391,13 @@
 ices
-%5BnumStrings%5D = 
+.put(
 data
@@ -4405,16 +4405,17 @@
 length()
+)
 ;%0A    nu
@@ -4959,20 +4959,16 @@
 size())%0A
-    
     .set

rewrite to remove UnsupportedOperationAception