/**
   * Pulled partially from code, it runs a "PRAGMA quick_check(1)" to see if the database is ok.
   * This method will {@link #restoreBackUp()} if they are enabled on the database if this check
   * fails. So use with caution and ensure that you backup the database often!
   *
   * @return true if the database is ok, false if the consistency has been compromised.
   */
  public boolean isDatabaseIntegrityOk() {
    boolean integrityOk = true;

    SQLiteStatement prog = null;
    try {
      prog = getWritableDatabase().compileStatement("PRAGMA quick_check(1)");
      String rslt = prog.simpleQueryForString();
      if (!rslt.equalsIgnoreCase("ok")) {
        // integrity_checker failed on main or attached databases
        FlowLog.log(
            FlowLog.Level.E,
            "PRAGMA integrity_check on "
                + databaseDefinition.getDatabaseName()
                + " returned: "
                + rslt);

        integrityOk = false;

        if (databaseDefinition.backupEnabled()) {
          integrityOk = restoreBackUp();
        }
      }
    } finally {
      if (prog != null) {
        prog.close();
      }
    }
    return integrityOk;
  }
  /**
   * If integrity check fails, this method will use the backup db to fix itself. In order to prevent
   * loss of data, please backup often!
   */
  public boolean restoreBackUp() {
    boolean success = true;

    File db =
        FlowManager.getContext()
            .getDatabasePath(TEMP_DB_NAME + databaseDefinition.getDatabaseName());
    File corrupt = FlowManager.getContext().getDatabasePath(databaseDefinition.getDatabaseName());
    if (corrupt.delete()) {
      try {
        writeDB(corrupt, new FileInputStream(db));
      } catch (IOException e) {
        FlowLog.logError(e);
        success = false;
      }
    } else {
      FlowLog.log(FlowLog.Level.E, "Failed to delete DB");
    }
    return success;
  }
  /**
   * Saves the database as a backup on the {@link
   * com.raizlabs.android.dbflow.runtime.DBTransactionQueue} as the highest priority ever. This will
   * create a THIRD database to use as a backup to the backup in case somehow the overwrite fails.
   */
  public void backupDB() {
    if (!databaseDefinition.backupEnabled() || !databaseDefinition.areConsistencyChecksEnabled()) {
      throw new IllegalStateException(
          "Backups are not enabled for : "
              + databaseDefinition.getDatabaseName()
              + ". Please consider adding "
              + "both backupEnabled and consistency checks enabled to the Database annotation");
    }
    // highest priority ever!
    TransactionManager.getInstance()
        .addTransaction(
            new BaseTransaction(DBTransactionInfo.create(BaseTransaction.PRIORITY_UI + 1)) {
              @Override
              public Object onExecute() {

                Context context = FlowManager.getContext();
                File backup = context.getDatabasePath(getTempDbFileName());
                File temp =
                    context.getDatabasePath(
                        TEMP_DB_NAME + "-2-" + databaseDefinition.getDatabaseFileName());

                // if exists we want to delete it before rename
                if (temp.exists()) {
                  temp.delete();
                }

                backup.renameTo(temp);
                if (backup.exists()) {
                  backup.delete();
                }
                File existing = context.getDatabasePath(databaseDefinition.getDatabaseFileName());

                try {
                  backup.getParentFile().mkdirs();
                  writeDB(backup, new FileInputStream(existing));

                  temp.delete();
                } catch (Exception e) {
                  FlowLog.logError(e);
                }
                return null;
              }
            });
  }
  /**
   * Supports multiline sql statements with ended with the standard ";"
   *
   * @param db The database to run it on
   * @param file the file name in assets/migrations that we read from
   */
  private void executeSqlScript(SQLiteDatabase db, String file) {
    try {
      final InputStream input =
          FlowManager.getContext()
              .getAssets()
              .open(MIGRATION_PATH + "/" + databaseDefinition.getDatabaseName() + "/" + file);
      final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
      String line;

      // ends line with SQL
      String querySuffix = ";";

      // standard java comments
      String queryCommentPrefix = "\\";
      StringBuffer query = new StringBuffer();

      while ((line = reader.readLine()) != null) {
        line = line.trim();
        boolean isEndOfQuery = line.endsWith(querySuffix);
        if (line.startsWith(queryCommentPrefix)) {
          continue;
        }
        if (isEndOfQuery) {
          line = line.substring(0, line.length() - querySuffix.length());
        }
        query.append(" ").append(line);
        if (isEndOfQuery) {
          db.execSQL(query.toString());
          query = new StringBuffer();
        }
      }

      if (query.length() > 0) {
        db.execSQL(query.toString());
      }
    } catch (IOException e) {
      FlowLog.log(FlowLog.Level.E, "Failed to execute " + file, e);
    }
  }
 /**
  * @return the temporary database file name for when we have backups enabled {@link
  *     BaseDatabaseDefinition#backupEnabled()}
  */
 private String getTempDbFileName() {
   return TEMP_DB_NAME + databaseDefinition.getDatabaseName() + ".db";
 }
  private void executeMigrations(
      final SQLiteDatabase db, final int oldVersion, final int newVersion) {

    // will try migrations file or execute migrations from code
    try {
      final List<String> files =
          Arrays.asList(
              FlowManager.getContext()
                  .getAssets()
                  .list(MIGRATION_PATH + "/" + databaseDefinition.getDatabaseName()));
      Collections.sort(files, new NaturalOrderComparator());

      final Map<Integer, List<String>> migrationFileMap = new HashMap<>();
      for (String file : files) {
        try {
          final Integer version = Integer.valueOf(file.replace(".sql", ""));
          List<String> fileList = migrationFileMap.get(version);
          if (fileList == null) {
            fileList = new ArrayList<>();
            migrationFileMap.put(version, fileList);
          }
          fileList.add(file);
        } catch (NumberFormatException e) {
          FlowLog.log(FlowLog.Level.W, "Skipping invalidly named file: " + file, e);
        }
      }

      final Map<Integer, List<Migration>> migrationMap = databaseDefinition.getMigrations();

      final int curVersion = oldVersion + 1;

      TransactionManager.transact(
          db,
          new Runnable() {
            @Override
            public void run() {

              // execute migrations in order, migration file first before wrapped migration classes.
              for (int i = curVersion; i <= newVersion; i++) {
                List<String> migrationFiles = migrationFileMap.get(i);
                if (migrationFiles != null) {
                  for (String migrationFile : migrationFiles) {
                    executeSqlScript(db, migrationFile);
                    FlowLog.log(FlowLog.Level.I, migrationFile + " executed successfully.");
                  }
                }

                if (migrationMap != null) {
                  List<Migration> migrationsList = migrationMap.get(i);
                  if (migrationsList != null) {
                    for (Migration migration : migrationsList) {
                      // before migration
                      migration.onPreMigrate();

                      // migrate
                      migration.migrate(db);

                      // after migration cleanup
                      migration.onPostMigrate();
                      FlowLog.log(
                          FlowLog.Level.I, migration.getClass() + " executed successfully.");
                    }
                  }
                }
              }
            }
          });
    } catch (IOException e) {
      FlowLog.log(FlowLog.Level.E, "Failed to execute migrations.", e);
    }
  }