package com.apollographql.apollo.cache.normalized.sql;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.apollographql.apollo.api.internal.Optional;
import com.apollographql.apollo.cache.normalized.CacheStore;
import com.apollographql.apollo.cache.normalized.Record;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper.COLUMN_KEY;
import static com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper.COLUMN_RECORD;
import static com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper.TABLE_RECORDS;

public final class SqlStore extends CacheStore {
  SQLiteDatabase database;
  private final FieldsAdapter parser;
  private final ApolloSqlHelper dbHelper;
  private final String[] allColumns = {ApolloSqlHelper.COLUMN_ID,
      ApolloSqlHelper.COLUMN_KEY,
      ApolloSqlHelper.COLUMN_RECORD};

  public static SqlStore create(ApolloSqlHelper helper, FieldsAdapter adapter) {
    return new SqlStore(helper, adapter);
  }

  private SqlStore(ApolloSqlHelper dbHelper, FieldsAdapter parser) {
    this.dbHelper = dbHelper;
    database = dbHelper.getWritableDatabase();
    this.parser = parser;
  }

  @Nullable public Record loadRecord(String key) {
    return selectRecordForKey(key).orNull();
  }

  @Nonnull public Set<String> merge(Record apolloRecord) {
    Optional<Record> optionalOldRecord = selectRecordForKey(apolloRecord.key());
    Set<String> changedKeys;
    if (!optionalOldRecord.isPresent()) {
      createRecord(apolloRecord.key(), parser.toJson(apolloRecord.fields()));
      changedKeys = Collections.emptySet();
    } else {
      Record oldRecord = optionalOldRecord.get();
      changedKeys = oldRecord.mergeWith(apolloRecord);
      if (!changedKeys.isEmpty()) {
        updateRecord(oldRecord.key(), parser.toJson(oldRecord.fields()));
      }
    }
    return changedKeys;
  }

  @Nonnull @Override public Set<String> merge(Collection<Record> recordSet) {
    Set<String> changedKeys = Collections.emptySet();
    try {
      database.beginTransaction();
      changedKeys = super.merge(recordSet);
      database.setTransactionSuccessful();
    } finally {
      database.endTransaction();
    }
    return changedKeys;
  }

  long createRecord(String key, String fields) {
    ContentValues values = contentValuesForRecord(key, fields);
    long recordId = database.insert(TABLE_RECORDS, null, values);
    return recordId;
  }

  void updateRecord(String key, String fields) {
    ContentValues values = contentValuesForRecord(key, fields);
    String selection = COLUMN_KEY + " = ?";
    String[] selectionArgs = new String[]{key};
    database.update(TABLE_RECORDS, values, selection, selectionArgs);
  }

  private ContentValues contentValuesForRecord(String key, String fields) {
    ContentValues values = new ContentValues(2);
    values.put(COLUMN_KEY, key);
    values.put(COLUMN_RECORD, fields);
    return values;
  }

  Optional<Record> selectRecordForKey(String key) {
    Cursor cursor = database.query(ApolloSqlHelper.TABLE_RECORDS,
        allColumns, ApolloSqlHelper.COLUMN_KEY + " = ?", new String[]{key},
        null, null, null);
    if (cursor == null || !cursor.moveToFirst()) {
      return Optional.absent();
    }
    try {
      return Optional.of(cursorToRecord(cursor));
    } catch (IOException exception) {
      return Optional.absent();
    } finally {
      cursor.close();
    }
  }

  void deleteRecord(String key) {
    System.out.println("Record deleted with key: " + key);
    database.delete(ApolloSqlHelper.TABLE_RECORDS, ApolloSqlHelper.COLUMN_ID
        + " = ?", new String[]{key});
  }

  Record cursorToRecord(Cursor cursor) throws IOException {
    String key = cursor.getString(1);
    String jsonOfFields = cursor.getString(2);
    return new Record(key, parser.from(jsonOfFields));
  }

  public void close() {
    dbHelper.close();
  }
}

@@ -53,46 +53,8 @@
 l;%0A%0A
-import android.content.ContentValues;%0A
 impo
@@ -127,16 +127,64 @@
 atabase;
+%0Aimport android.database.sqlite.SQLiteStatement;
 %0A%0Aimport
@@ -849,16 +849,400 @@
 Store %7B%0A
+  private static final String INSERT_STATEMENT =%0A      String.format(%22INSERT INTO %25s (%25s,%25s) VALUES (?,?)%22,%0A          TABLE_RECORDS,%0A          COLUMN_KEY,%0A          COLUMN_RECORD);%0A  private static final String UPDATE_STATEMENT =%0A      String.format(%22UPDATE %25s SET %25s=?, %25s=? WHERE %25s=?%22,%0A          TABLE_RECORDS,%0A          COLUMN_KEY,%0A          COLUMN_RECORD,%0A          COLUMN_KEY);%0A
   SQLite
@@ -1479,16 +1479,115 @@
 CORD%7D;%0A%0A
+  private final SQLiteStatement insertStatement;%0A  private final SQLiteStatement updateStatement;%0A%0A
   public
@@ -1878,16 +1878,150 @@
 parser;%0A
+    insertStatement = database.compileStatement(INSERT_STATEMENT);%0A    updateStatement = database.compileStatement(UPDATE_STATEMENT);%0A
   %7D%0A%0A  @
@@ -3127,57 +3127,76 @@
     
-ContentValues values = contentValuesForRecord(key
+insertStatement.bindString(1, key);%0A    insertStatement.bindString(2
 , fi
@@ -3198,24 +3198,25 @@
 2, fields);%0A
+%0A
     long rec
@@ -3227,51 +3227,38 @@
 d = 
-database.insert(TABLE_RECORDS, null, values
+insertStatement.executeInsert(
 );%0A 
@@ -3339,449 +3339,163 @@
     
-ContentValues values = contentValuesForRecord(key, fields);%0A    String selection = COLUMN_KEY + %22 = ?%22;%0A    String%5B%5D selectionArgs = new String%5B%5D%7Bkey%7D
+updateStatement.bindString(1, key)
 ;%0A    
-database.update(TABLE_RECORDS, values, selection, selectionArgs);%0A  %7D%0A%0A  private ContentValues contentValuesForRecord(
+updateStatement.bindString(2, fields);%0A    updateStatement.bind
 String
+(3,
  key
-, String fields) %7B%0A    ContentValues values = new ContentValues(2);%0A    values.put(COLUMN_KEY, key);%0A    values.put(COLUMN_RECORD, fields);%0A    return values
+);%0A%0A    updateStatement.executeInsert()
 ;%0A  

use compiled statements (#373)