public class RapidORMConfig {
    /**
     * Print out SQLs if DEBUG is true
     */
    public static boolean DEBUG = true;

    /**
     * Bind a field object to {@link com.wangjie.rapidorm.core.config.ColumnConfig} with
     * reflection if is true.
     */
    @Deprecated
    public static boolean BIND_FIELD_COLUMN_WITH_REFLECTION = false;

} public class ColumnConfig {
    /**
     * Field NOT NULL only when `{@link com.wangjie.rapidorm.constants.RapidORMConfig#BIND_FIELD_COLUMN_WITH_REFLECTION}`
     * is TRUE, default is null.
     */
    private Field field;
    /**
     * Column name of database
     */
    private String columnName;
    /**
     * Whether it is a primary key
     */
    private boolean primaryKey;
    /**
     * Whether it is autoincrement
     * It is only works when this column is the one and only one primary key
     */
    private boolean autoincrement;
    /**
     * Default value, Not recommended
     */
    private String defaultValue;
    /**
     * Whether it is not null
     */
    private boolean notNull;
    /**
     * Whether it is unique constraint
     */
    private boolean unique;
    private boolean uniqueCombo;
    @Deprecated
    private boolean index;

    //    private int ordinals;
    private String dbType;

//    private int kpSequence;
//    private int noKpSequence;

    public String getColumnName() {
        return columnName;
    }

    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }

    public boolean isPrimaryKey() {
        return primaryKey;
    }

    public void setPrimaryKey(boolean primaryKey) {
        this.primaryKey = primaryKey;
    }

    public boolean isAutoincrement() {
        return autoincrement;
    }

    public void setAutoincrement(boolean autoincrement) {
        this.autoincrement = autoincrement;
    }

    public String getDefaultValue() {
        return defaultValue;
    }

    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }

    public boolean isNotNull() {
        return notNull;
    }

    public void setNotNull(boolean notNull) {
        this.notNull = notNull;
    }

    public boolean isUnique() {
        return unique;
    }

    public void setUnique(boolean unique) {
        this.unique = unique;
    }

    public boolean isUniqueCombo() {
        return uniqueCombo;
    }

    public void setUniqueCombo(boolean uniqueCombo) {
        this.uniqueCombo = uniqueCombo;
    }

    public boolean isIndex() {
        return index;
    }

    public void setIndex(boolean index) {
        this.index = index;
    }

    public Field getField() {
        return field;
    }

    public void setField(Field field) {
        this.field = field;
    }

//    public void setOrdinals(int ordinals) {
//        this.ordinals = ordinals;
//    }
//
//    public int getOrdinals() {
//        return ordinals;
//    }

    public String getDbType() {
        return dbType;
    }

    public void setDbType(String dbType) {
        this.dbType = dbType;
    }
} public abstract class TableConfig<T> {
    /**
     * Table name in database
     */
    protected String tableName;
    /**
     * Table class in code
     */
    protected Class<T> tableClazz;

    protected List<ColumnConfig> allColumnConfigs = new ArrayList<>();
    protected HashMap<String, ColumnConfig> allFieldColumnConfigMapper = new HashMap<>();
    protected List<ColumnConfig> pkColumnConfigs = new ArrayList<>();
    protected List<ColumnConfig> noPkColumnConfigs = new ArrayList<>();

    protected List<ColumnConfig> indexColumnConfigsCache;
    protected List<ColumnConfig> uniqueComboColumnConfigsCache;

    private InsertStatement<T> insertStatement;
    private UpdateStatement<T> updateStatement;
    private DeleteStatement<T> deleteStatement;
    private IsExistStatement<T> isExistStatement;


    public TableConfig(@NonNull Class<T> tableClazz) {
        this.tableClazz = tableClazz;
        parseAllConfigs();

        if (RapidORMConfig.BIND_FIELD_COLUMN_WITH_REFLECTION) {
            bindFieldColumnWithReflection();
        }

    }

    /**
     * Implementation by subclass which auto generated by rapidORM
     */
    protected abstract void parseAllConfigs();

    private void bindFieldColumnWithReflection() {
        ReflectionUtils.doWithFieldsWithSuper(tableClazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws Exception {
                Column column = field.getAnnotation(Column.class);
                if (null == column) {
                    return;
                }
                ColumnConfig columnConfig = allFieldColumnConfigMapper.get(field.getName());
                if (null != columnConfig) {
                    columnConfig.setField(field);
                }
            }
        });
        allFieldColumnConfigMapper = null;
    }


    protected ColumnConfig buildColumnConfig(String name,
                                             boolean autoincrement,
                                             boolean notNull,
                                             String defaultValue,
                                             boolean index,
                                             boolean unique,
                                             boolean uniqueCombo,
                                             boolean primaryKey,
                                             String dbType) {
        ColumnConfig columnConfig = new ColumnConfig();
        // column name
        columnConfig.setColumnName(name);
        // autoincrement
        columnConfig.setAutoincrement(autoincrement);
        // notNull
        columnConfig.setNotNull(notNull);
        // defaultValue
        columnConfig.setDefaultValue(defaultValue);
        // index
        columnConfig.setIndex(index);
        // unique
        columnConfig.setUnique(unique);
        // uniqueCombo
        columnConfig.setUniqueCombo(uniqueCombo);
        // primaryKey
        columnConfig.setPrimaryKey(primaryKey);
        // dbType
        columnConfig.setDbType(dbType);
        return columnConfig;
    }

    public List<ColumnConfig> getUniqueComboColumnConfigs() {
        List<ColumnConfig> uniqueComboColumnConfigs = new ArrayList<>();
        for (ColumnConfig columnConfig : allColumnConfigs) {
            if (columnConfig.isUniqueCombo()) {
                uniqueComboColumnConfigs.add(columnConfig);
            }
        }
        return uniqueComboColumnConfigs;
    }

    public List<ColumnConfig> getUniqueComboColumnConfigsFromCache() {
        if (null == uniqueComboColumnConfigsCache) {
            uniqueComboColumnConfigsCache = getUniqueComboColumnConfigs();
        }
        return uniqueComboColumnConfigsCache;
    }

    public List<ColumnConfig> getIndexColumnConfigs() {
        List<ColumnConfig> indexColumnConfigs = new ArrayList<>();
        for (ColumnConfig columnConfig : allColumnConfigs) {
            if (columnConfig.isIndex()) {
                indexColumnConfigs.add(columnConfig);
            }
        }
        return indexColumnConfigs;
    }

    public List<ColumnConfig> getIndexColumnConfigsFromCache() {
        if (null == indexColumnConfigsCache) {
            indexColumnConfigsCache = getIndexColumnConfigs();
        }
        return indexColumnConfigsCache;
    }

    public String getTableName() {
        return tableName;
    }

    public Class getTableClazz() {
        return tableClazz;
    }

    public List<ColumnConfig> getAllColumnConfigs() {
        return allColumnConfigs;
    }

    public List<ColumnConfig> getPkColumnConfigs() {
        return pkColumnConfigs;
    }

    public List<ColumnConfig> getNoPkColumnConfigs() {
        return noPkColumnConfigs;
    }

    public InsertStatement<T> getInsertStatement() {
        if (null == insertStatement) {
            insertStatement = new InsertStatement<>(this);
        }
        return insertStatement;
    }

    public UpdateStatement<T> getUpdateStatement() {
        if (null == updateStatement) {
            updateStatement = new UpdateStatement<>(this);
        }
        return updateStatement;
    }

    public DeleteStatement<T> getDeleteStatement() {
        if (null == deleteStatement) {
            deleteStatement = new DeleteStatement<>(this);
        }
        return deleteStatement;
    }

    public IsExistStatement<T> getIsExistStatement() {
        if (null == isExistStatement) {
            isExistStatement = new IsExistStatement<>(this);
        }
        return isExistStatement;
    }

    private List<ColumnConfig> insertColumnConfigs;

    public List<ColumnConfig> getInsertColumnConfigs() {
        if (null == insertColumnConfigs) {
            insertColumnConfigs = SqlUtil.getInsertColumnConfigs(this);
        }
        return insertColumnConfigs;
    }

    //    public abstract void bindInsertArgs(T model, List<Object> insertArgs);
    public abstract int bindInsertArgs(T model, RapidORMSQLiteStatementDelegate statement, int indexOffset);

    public abstract int bindUpdateArgs(T model, RapidORMSQLiteStatementDelegate statement, int indexOffset);

    public abstract int bindPkArgs(T model, RapidORMSQLiteStatementDelegate statement, int indexOffset);

    public abstract T parseFromCursor(Cursor cursor);

    public abstract void createTable(RapidORMSQLiteDatabaseDelegate db, boolean ifNotExists) throws Exception;

} public class DatabaseProcessor {
    private static class Holder {
        private static DatabaseProcessor instance = new DatabaseProcessor();
    }

    public static DatabaseProcessor getInstance() {
        return Holder.instance;
    }

    private Map<Class, TableConfig> tableConfigMapper;

    private boolean isInitialized = false;

    @SuppressWarnings("unchecked")
    public <T> void createTable(RapidORMSQLiteDatabaseDelegate db, Class<T> clazz, boolean ifNotExists) {
        checkInitialized();
        TableConfig<T> tableConfig = tableConfigMapper.get(clazz);
        if (null == tableConfig) {
            throw new RapidORMRuntimeException("tableConfigMapper not initialized, had you invoke super() method in the sub class of RapidORMConnection ?");
        }
        try {
            tableConfig.createTable(db, ifNotExists);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void createAllTable(RapidORMSQLiteDatabaseDelegate db, boolean ifNotExists) {
        checkInitialized();
        for (Class tableClass : tableConfigMapper.keySet()) {
            createTable(db, tableClass, ifNotExists);
        }
    }

    public <T> void dropTable(RapidORMSQLiteDatabaseDelegate db, Class<T> clazz) {
        checkInitialized();
        TableConfig<T> tableConfig = tableConfigMapper.get(clazz);
        if (null == tableConfig) {
            return;
        }
        try {
            db.execSQL("drop table " + tableConfig.getTableName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void dropAllTable(RapidORMSQLiteDatabaseDelegate db) {
        checkInitialized();
        Set<Class> entrySet = tableConfigMapper.keySet();
        for (Class anEntrySet : entrySet) {
            dropTable(db, anEntrySet);
        }
    }

    private RapidORMConnection rapidORMConnection;
    private RapidORMDatabaseOpenHelperDelegate rapidORMDatabaseOpenHelperDelegate;
    private RapidORMSQLiteDatabaseDelegate db;
//    protected List<Class> allTableClass;

    private DatabaseProcessor() {
    }

    /**
     * RapidORMConnection构造的时候就初始化好所有的tableClass
     */
    @SuppressWarnings("unchecked")
    public void initializeConnection(RapidORMConnection rapidORMConnection, @NonNull Map<Class, TableConfig> tableConfigMapper) {
        if (isInitialized) {
            return;
        }
        isInitialized = true;

        this.rapidORMConnection = rapidORMConnection;
        this.tableConfigMapper = tableConfigMapper;
    }

    public void resetRapidORMDatabaseOpenHelper(RapidORMDatabaseOpenHelperDelegate rapidORMDatabaseOpenHelper) {
        this.rapidORMDatabaseOpenHelperDelegate = rapidORMDatabaseOpenHelper;
        if (null != db) {
            db.close();
        }
        this.db = null;
    }

    public void initializeDatabase(RapidORMSQLiteDatabaseDelegate db) {
        this.db = db;
    }

    public RapidORMSQLiteDatabaseDelegate getDb() {
        checkInitialized();
        if (null == db) {
            synchronized (this) {
                if (null == db) {
                    if (null == this.rapidORMDatabaseOpenHelperDelegate) {
                        if (!rapidORMConnection.resetDatabaseIfCrash()) {
                            android.os.Process.killProcess(Process.myPid());
                        }
                    }
                    db = (RapidORMSQLiteDatabaseDelegate) this.rapidORMDatabaseOpenHelperDelegate.getWritableDatabase();
                }
            }
        }
        return db;
    }

    @SuppressWarnings("unchecked")
    public <T> TableConfig<T> getTableConfig(Class<T> clazz) {
        checkInitialized();
        return (TableConfig<T>) tableConfigMapper.get(clazz);
    }

    private void checkInitialized() {
        if (!isInitialized) {
            throw new RapidORMRuntimeException("DatabaseProcessor is not initialized, had you invoke super() method in the sub class of RapidORMConnection ?");
        }
    }

} public abstract class RapidORMSQLiteDatabaseDelegate<SQLiteDatabase> {
    protected SQLiteDatabase db;

    public SQLiteDatabase getDb() {
        return db;
    }

    public RapidORMSQLiteDatabaseDelegate(SQLiteDatabase db) {
        this.db = db;
    }

    public abstract void execSQL(String sql) throws Exception;

    public abstract boolean isDbLockedByCurrentThread();

    public abstract void execSQL(String sql, Object[] bindArgs) throws Exception;

    public abstract Cursor rawQuery(String sql, String[] selectionArgs);

    public abstract void beginTransaction();

    public abstract void setTransactionSuccessful();

    public abstract void endTransaction();

    public abstract RapidORMSQLiteStatementDelegate compileStatement(String sql) throws Exception;

    public abstract void close();

} public abstract class RapidORMSQLiteStatementDelegate {
    public abstract void execute();

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public abstract int executeUpdateDelete();

    public abstract long executeInsert();

    public abstract long simpleQueryForLong();

    public abstract String simpleQueryForString();

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public abstract ParcelFileDescriptor simpleQueryForBlobFileDescriptor();

    public abstract String toString();

    public abstract void bindNull(int index);

    public abstract void bindLong(int index, long value);

    public abstract void bindDouble(int index, double value);

    public abstract void bindString(int index, String value);

    public abstract void bindBlob(int index, byte[] value);

    public abstract void clearBindings();

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public abstract void bindAllArgsAsStrings(String[] bindArgs);

} public class DeleteBuilder<T> extends RapidBuilder {
    private Where where;
    private List<Object> values;
    private TableConfig<T> tableConfig;

    private BaseDao<T> dao;

    public DeleteBuilder(BaseDao<T> dao) {
        values = new ArrayList<>();
        this.dao = dao;
    }

    public Where getWhere() {
        return where;
    }

    public DeleteBuilder<T> setWhere(Where where) {
        this.where = where;
        return this;
    }

    public List<Object> getValues() {
        return values;
    }

    public Object[] getValuesAsObjectArray() {
        return values.toArray();
    }

    public TableConfig<T> getTableConfig() {
        return tableConfig;
    }

    public void setTableConfig(TableConfig<T> tableConfig) {
        this.tableConfig = tableConfig;
    }

    @Override
    public String generateSql() {
        if (null == tableConfig) {
            throw new RapidORMRuntimeException("[generateSql() method of QueryBuilder] TableConfig is null!");
        }
        values.clear();
        StringBuilder sql = new StringBuilder(" DELETE FROM ");
        SqlUtil.formatName(sql, tableConfig.getTableName());
        if (null != where) {
            sql.append(" WHERE ")
                    .append(where.getWhere());
            values = where.getValues();
        }
        return sql.toString();
    }

    public void delete()throws Exception{
        dao.rawExecute(generateSql(), getValuesAsObjectArray());
    }

    @Deprecated
    public void delete(BaseDao<T> baseDao)throws Exception{
        baseDao.rawExecute(generateSql(), getValuesAsObjectArray());
    }
} public class QueryBuilder<T> extends RapidBuilder {

    class OrderCase {
        public boolean isAsc;
        public String column;

        public OrderCase(String column, boolean isAsc) {
            this.column = column;
            this.isAsc = isAsc;
        }
    }

    private List<String> selectColumns;
    private Where where;
    private Integer limit;
    private TableConfig<T> tableConfig;
    private List<Object> values;
    private List<OrderCase> orderCase;
    private boolean distinct;

    private BaseDao<T> dao;

    public QueryBuilder(BaseDao<T> dao) {
        selectColumns = new ArrayList<>();
        values = new ArrayList<>();
        orderCase = new ArrayList<>();
        this.dao = dao;
    }

    public QueryBuilder<T> setWhere(Where where) {
        this.where = where;
        return this;
    }

    public Integer getLimit() {
        return limit;
    }

    public QueryBuilder<T> setLimit(Integer limit) {
        this.limit = limit;
        return this;
    }

    public QueryBuilder<T> setDistinct(boolean distinct) {
        this.distinct = distinct;
        return this;
    }

    public QueryBuilder<T> addSelectColumns(List<String> selectColumns) {
        if (null != selectColumns) {
            this.selectColumns.addAll(selectColumns);
        }
        return this;
    }

    public QueryBuilder<T> addSelectColumn(String... selectColumn) {
        this.selectColumns.addAll(Arrays.asList(selectColumn));
        return this;
    }

    public QueryBuilder<T> addOrder(String column, boolean isAsc) {
        orderCase.add(new OrderCase(column, isAsc));
        return this;
    }

    public QueryBuilder<T> setTableConfig(TableConfig<T> tableConfig) {
        this.tableConfig = tableConfig;
        return this;
    }

    public List<Object> getValues() {
        return values;
    }

    public String[] getValuesAsStringArray() {
        return objectListToStringArray(values);
    }


    @Override
    public String generateSql() {
        if (null == tableConfig) {
            throw new RapidORMRuntimeException("[generateSql() method of QueryBuilder] TableConfig is null!");
        }
        values.clear();

        StringBuilder sql = new StringBuilder(" SELECT ");
        if (distinct) {
            sql.append(" DISTINCT ");
        }
        sql.append(null == selectColumns || 0 == selectColumns.size() ? "*" : CollectionJoiner.join(selectColumns, ","));
        sql.append(" FROM ");
        SqlUtil.formatName(sql, tableConfig.getTableName());
        sql.append(" ");

        if (null != where) {
            sql.append(" WHERE ")
                    .append(where.getWhere());
            values = where.getValues();
        }

        if (0 < orderCase.size()) {
            sql.append(" ORDER BY ");
            CollectionJoiner.join(orderCase, ",", sql, new CollectionJoiner.OnCollectionJoiner<OrderCase>() {

                @Override
                public void joinContent(StringBuilder builder, OrderCase obj) {
                    SqlUtil.formatName(builder, obj.column).append(" ").append(obj.isAsc ? " ASC " : " DESC ");
                }
            });
        }

        if (null != limit) {
            sql.append(" LIMIT ? ");
            values.add(limit);
        }
        return sql.toString();
    }

    public List<T> query() throws Exception {
        return dao.rawQuery(generateSql(), getValuesAsStringArray());
    }

    public T queryFirst() throws Exception {
        List<T> list = dao.rawQuery(generateSql(), getValuesAsStringArray());
        return null == list || 0 == list.size() ? null : list.get(0);
    }

    @Deprecated
    public List<T> query(BaseDao<T> baseDao) throws Exception {
        return baseDao.rawQuery(generateSql(), getValuesAsStringArray());
    }

    @Deprecated
    public T queryFirst(BaseDao<T> baseDao) throws Exception {
        List<T> list = baseDao.rawQuery(generateSql(), getValuesAsStringArray());
        return null == list || 0 == list.size() ? null : list.get(0);
    }

} public class UpdateBuilder<T> extends RapidBuilder {
    class UpdateCase {
        public String column;
        public Object value;

        public UpdateCase(String column, Object value) {
            this.column = column;
            this.value = value;
        }
    }

    private Where where;
    private List<UpdateCase> updateCases;
    private List<Object> values;
    private TableConfig<T> tableConfig;

    private BaseDao<T> dao;

    public UpdateBuilder(BaseDao<T> dao) {
        updateCases = new ArrayList<>();
        values = new ArrayList<>();
        this.dao = dao;
    }

    public TableConfig<T> getTableConfig() {
        return tableConfig;
    }

    public void setTableConfig(TableConfig<T> tableConfig) {
        this.tableConfig = tableConfig;
    }

    public Where getWhere() {
        return where;
    }

    public UpdateBuilder<T> setWhere(Where where) {
        this.where = where;
        return this;
    }

    public UpdateBuilder<T> addUpdateColumn(String column, Object value) {
        updateCases.add(new UpdateCase(column, SqlUtil.convertValue(value)));
        return this;
    }

    public List<Object> getValues() {
        return values;
    }

    public String[] getValuesAsStringArray() {
        return objectListToStringArray(values);
    }

    @Override
    public String generateSql() {
        if (null == updateCases || 0 == updateCases.size()) {
            throw new RapidORMRuntimeException("UPDATE statements must have at least one SET column.");
        }
        values.clear();

        StringBuilder sql = new StringBuilder(" UPDATE ");
        SqlUtil.formatName(sql, tableConfig.getTableName());
        sql.append(" SET ");

        CollectionJoiner.join(updateCases, ",", sql, new CollectionJoiner.OnCollectionJoiner<UpdateCase>() {

            @Override
            public void joinContent(StringBuilder builder, UpdateCase obj) {
                values.add(obj.value);
                SqlUtil.formatName(builder, obj.column).append("=?");
            }
        });

        if (null != where) {
            sql.append(" WHERE ")
                    .append(where.getWhere());
            values.addAll(where.getValues());
        }
        return sql.toString();
    }

    public void update() throws Exception{
        dao.rawExecute(generateSql(), getValuesAsStringArray());
    }

    @Deprecated
    public void update(BaseDao<T> baseDao) throws Exception{
        baseDao.rawExecute(generateSql(), getValuesAsStringArray());
    }
} public class SqlUtil {

    public static <T> List<ColumnConfig> getInsertColumnConfigs(TableConfig<T> tableConfig) {
        List<ColumnConfig> insertColumns;
        List<ColumnConfig> pkColumnConfigs = tableConfig.getPkColumnConfigs();
        ColumnConfig firstPkColumnConfig;
        if (
                1 == pkColumnConfigs.size()
                        && (firstPkColumnConfig = pkColumnConfigs.get(0)).isPrimaryKey()
                        && firstPkColumnConfig.isAutoincrement()
                ) {
            insertColumns = tableConfig.getNoPkColumnConfigs();
        } else {
            insertColumns = tableConfig.getAllColumnConfigs();
        }
        return insertColumns;
    }


    public static <T> String generateSqlDeleteAll(@NonNull TableConfig<T> tableConfig) {
        return "DELETE FROM " + tableConfig.getTableName();
    }

    public static <T> String generateSqlQueryAll(@NonNull TableConfig<T> tableConfig) {
        return "SELECT * FROM " + tableConfig.getTableName();
    }

    public static StringBuilder appendPlaceholders(StringBuilder builder, int count) {
        for (int i = 0; i < count; i++) {
            if (i < count - 1) {
                builder.append("?,");
            } else {
                builder.append('?');
            }
        }
        return builder;
    }

    public static StringBuilder formatName(StringBuilder builder, String name) {
        return builder.append("`").append(name).append("`");
    }



    public static Object convertValue(Object fieldValue){
        if(null == fieldValue){
            return null;
        }
        if(TypeUtil.isBooleanIncludePrimitive(fieldValue.getClass())){
            return ((Boolean)fieldValue) ? 1 : 0;
        }
        return fieldValue;
    }

} public class TypeUtil {
    public static boolean isLongIncludePrimitive(Class valueType) {
        return long.class == valueType || Long.class == valueType;
    }

    public static boolean isIntegerIncludePrimitive(Class valueType) {
        return int.class == valueType || Integer.class == valueType;
    }

    public static boolean isShortIncludePrimitive(Class valueType) {
        return short.class == valueType || Short.class == valueType;
    }

    public static boolean isDoubleIncludePrimitive(Class valueType) {
        return double.class == valueType || Double.class == valueType;
    }

    public static boolean isFloatIncludePrimitive(Class valueType) {
        return float.class == valueType || Float.class == valueType;
    }

    public static boolean isBooleanIncludePrimitive(Class<?> fieldType) {
        return boolean.class == fieldType || Boolean.class == fieldType;
    }

    public static boolean isBlobIncludePrimitive(Class<?> fieldType) {
        return byte[].class == fieldType || Byte[].class == fieldType || Blob.class == fieldType;
    }
}
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.wangjie.rapidorm.constants.RapidORMConfig;
import com.wangjie.rapidorm.core.config.ColumnConfig;
import com.wangjie.rapidorm.core.config.TableConfig;
import com.wangjie.rapidorm.core.connection.DatabaseProcessor;
import com.wangjie.rapidorm.core.delegate.database.RapidORMSQLiteDatabaseDelegate;
import com.wangjie.rapidorm.core.delegate.sqlitestatement.RapidORMSQLiteStatementDelegate;
import com.wangjie.rapidorm.core.generate.builder.DeleteBuilder;
import com.wangjie.rapidorm.core.generate.builder.QueryBuilder;
import com.wangjie.rapidorm.core.generate.builder.UpdateBuilder;
import com.wangjie.rapidorm.core.generate.statement.util.SqlUtil;
import com.wangjie.rapidorm.exception.RapidORMException;
import com.wangjie.rapidorm.exception.RapidORMRuntimeException;
import com.wangjie.rapidorm.util.TypeUtil;
import com.wangjie.rapidorm.util.func.RapidOrmFunc1;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
package com.wangjie.rapidorm.core.dao;




/**
 * Author: wangjie
 * Email: tiantian.china.2@gmail.com
 * Date: 6/25/15.
 */
public class BaseDaoImpl<T> implements BaseDao<T> {
    private static final String TAG = BaseDaoImpl.class.getSimpleName();
//    protected final byte[] LOCK = new byte[0];

    protected final Class<T> clazz;
    protected final TableConfig<T> tableConfig;
    protected final String insertStatement;
    protected final String updateStatement;
    protected final String deleteStatement;
    protected final String isExistStatement;

    private RapidORMSQLiteStatementDelegate insertStmt;
    private RapidORMSQLiteStatementDelegate updateStmt;
    private RapidORMSQLiteStatementDelegate deleteStmt;
    @Nullable
    private RapidORMSQLiteStatementDelegate isExistStmt;

    private boolean pkColumnIsEmpty;

    public BaseDaoImpl(Class<T> clazz) {
        this.clazz = clazz;
        this.tableConfig = DatabaseProcessor.getInstance().getTableConfig(clazz);
        insertStatement = tableConfig.getInsertStatement().getStatement();
        updateStatement = tableConfig.getUpdateStatement().getStatement();
        deleteStatement = tableConfig.getDeleteStatement().getStatement();
        isExistStatement = tableConfig.getIsExistStatement().getStatement();

        pkColumnIsEmpty = tableConfig.getPkColumnConfigs().isEmpty();

        RapidORMSQLiteDatabaseDelegate db = getDatabase();
        try {
            insertStmt = db.compileStatement(insertStatement);
        } catch (Exception e) {
            throw new RapidORMRuntimeException(e);
        }
        try {
            updateStmt = pkColumnIsEmpty ? null : db.compileStatement(updateStatement);
        } catch (Exception e) {
            throw new RapidORMRuntimeException(e);
        }
        try {
            deleteStmt = db.compileStatement(deleteStatement);
        } catch (Exception e) {
            throw new RapidORMRuntimeException(e);
        }
        try {
            isExistStmt = pkColumnIsEmpty ? null : db.compileStatement(isExistStatement);
        } catch (Exception e) {
            throw new RapidORMRuntimeException(e);
        }
    }

    @Override
    public void insert(@NonNull final T model) throws Exception {
        final RapidORMSQLiteDatabaseDelegate db = getDatabase();
        if (db.isDbLockedByCurrentThread()) {
            synchronized (tableConfig) {
                insertInternal(model);
            }
        } else {
            // Do TX to acquire a connection before locking the stmt to avoid deadlocks
            executeInTx(db, new RapidOrmFunc1() {
                @Override
                public void call() throws Exception {
                    synchronized (tableConfig) {
                        insertInternal(model);
                    }
                }
            });
        }
    }

    protected void insertInternal(@NonNull T model) throws Exception {
        insertStmt.clearBindings();
        tableConfig.bindInsertArgs(model, insertStmt, 0);

        if (RapidORMConfig.DEBUG)
            Log.i(TAG, "insertInternal ==> sql: " + insertStatement + " >> model: " + model);

        insertStmt.executeInsert();
    }

    @Override
    public void update(@NonNull final T model) throws Exception {
        final RapidORMSQLiteDatabaseDelegate db = getDatabase();
        if (db.isDbLockedByCurrentThread()) {
            synchronized (tableConfig) {
                updateInternal(model);
            }
        } else {
            // Do TX to acquire a connection before locking the stmt to avoid deadlocks
            executeInTx(db, new RapidOrmFunc1() {
                @Override
                public void call() throws Exception {
                    synchronized (tableConfig) {
                        updateInternal(model);
                    }
                }
            });
        }
    }

    protected void updateInternal(T model) throws Exception {
        if (null == updateStmt) {
            throw new RapidORMException("Table " + tableConfig.getTableName() + " have no primary key column. Please use `UpdateBuilder` to update");
        }
        updateStmt.clearBindings();
        int indexOff = tableConfig.bindUpdateArgs(model, updateStmt, 0);
        tableConfig.bindPkArgs(model, updateStmt, indexOff);

        if (RapidORMConfig.DEBUG)
            Log.i(TAG, "updateInternal ==> sql: " + updateStatement + " >> model: " + model);

        updateStmt.executeUpdateDelete();
    }

    @Override
    public void delete(@NonNull final T model) throws Exception {
        final RapidORMSQLiteDatabaseDelegate db = getDatabase();
        if (db.isDbLockedByCurrentThread()) {
            synchronized (tableConfig) {
                deleteInternal(model);
            }
        } else {
            // Do TX to acquire a connection before locking the stmt to avoid deadlocks
            executeInTx(db, new RapidOrmFunc1() {
                @Override
                public void call() throws Exception {
                    synchronized (tableConfig) {
                        deleteInternal(model);
                    }
                }
            });
        }
    }

    protected void deleteInternal(@NonNull T model) throws Exception {
        List<ColumnConfig> pkColumnConfigs = tableConfig.getPkColumnConfigs();