/*
 * Copyright (C) 2013 Jerzy Chalupski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.getbase.android.schema;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MigrationsHelper {
  private static final String TAG = MigrationsHelper.class.getSimpleName();

  private static int tempTableIndex = 0;

  public void performMigrations(SQLiteDatabase db, TableMigration... migrations) {
    for (TableMigration migration : migrations) {
      final String tempTable = "tmp_" + tempTableIndex++;
      db.execSQL("ALTER TABLE " + migration.tableName + " RENAME TO " + tempTable);
      ImmutableSet<String> oldColumns = getColumns(db, tempTable);

      db.execSQL(migration.createTableStatement);
      final String tempNewTable = "tmp_" + tempTableIndex++;
      db.execSQL("ALTER TABLE " + migration.tableName + " RENAME TO " + tempNewTable);
      ImmutableSet<String> newColumns = getColumns(db, tempNewTable);

      db.execSQL("ALTER TABLE " + tempNewTable + " RENAME TO " + migration.tableName);

      Set<String> commonColumns = Sets.intersection(oldColumns, newColumns);
      Set<String> droppedColumns = Sets.difference(oldColumns, newColumns);
      if (!droppedColumns.isEmpty()) {
        Log.w(TAG, "Dropping columns " + Joiner.on(",").join(droppedColumns) + " during migration of " + migration.tableName);
      }

      Set<String> addedColumns = Sets.difference(Sets.difference(newColumns, oldColumns), migration.mappings.keySet());
      if (!addedColumns.isEmpty()) {
        Log.w(TAG, "Will try to add new columns " + Joiner.on(",").join(addedColumns) + " during migration of " + migration.tableName);
      }

      SetView<String> unmappedColumns = Sets.difference(commonColumns, migration.mappings.keySet());
      String insertColumnsString = Joiner.on(",").join(Sets.union(unmappedColumns, migration.mappings.keySet()));
      String selectColumnsString = Joiner.on(",").join(Sets.union(unmappedColumns, ImmutableSet.copyOf(migration.mappings.values())));

      db.execSQL("INSERT INTO " + migration.tableName + "(" + insertColumnsString + ") SELECT " + selectColumnsString + " FROM " + tempTable);
      db.execSQL("DROP TABLE " + tempTable);
    }
  }

  public static class TableMigration {
    private final String tableName;
    private final String createTableStatement;
    private final ImmutableMap<String, String> mappings;

    private TableMigration(String tableName, String createTableStatement, ImmutableMap<String, String> mappings) {
      this.tableName = tableName;
      this.createTableStatement = createTableStatement;
      this.mappings = mappings;
    }

    public static Builder of(String table) {
      return new Builder(table);
    }

    public static class Builder {
      private Map<String, String> mMappings = new HashMap<String, String>();
      private String mCreateTableStatement;
      private String mTable;

      Builder(String table) {
        mTable = checkNotNull(table);
      }

      public Builder to(String createTableStatement) {
        mCreateTableStatement = checkNotNull(createTableStatement);
        return this;
      }

      public Builder withMapping(String newColumn, String oldColumnExpression) {
        mMappings.put(checkNotNull(newColumn), checkNotNull(oldColumnExpression));
        return this;
      }

      public TableMigration build() {
        checkState(mTable != null);
        checkState(mCreateTableStatement != null);

        return new TableMigration(mTable, mCreateTableStatement, ImmutableMap.copyOf(mMappings));
      }
    }
  }

  private static ImmutableSet<String> getColumns(SQLiteDatabase db, String table) {
    Cursor cursor = db.query(table, null, null, null, null, null, null, "0");
    if (cursor != null) {
      try {
        return ImmutableSet.copyOf(cursor.getColumnNames());
      } finally {
        cursor.close();
      }
    }
    return ImmutableSet.of();
  }
}

@@ -896,16 +896,60 @@
 bleSet;%0A
+import com.google.common.collect.Iterables;%0A
 import c
@@ -2836,34 +2836,71 @@
 n(%22,%22).join(
-Sets.union
+Iterables.unmodifiableIterable(Iterables.concat
 (unmappedCol
@@ -2934,16 +2934,17 @@
 ySet()))
+)
 ;%0A      
@@ -2996,56 +2996,73 @@
 oin(
-Sets.union(unmappedColumns, ImmutableSet.copyOf(
+Iterables.unmodifiableIterable(Iterables.concat(unmappedColumns, 
 migr

Enable duplicated mapping values in table migrations