package com.kurukurupapa.tryandroid.fw;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import com.kurukurupapa.tryandroid.fw.util.DbUtil;
import com.kurukurupapa.tryandroid.fw.util.LogUtil;
import com.kurukurupapa.tryandroid.fw.util.ReflectionUtil;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DbHelper extends SQLiteOpenHelper {
	private static final String DEFAULT_DB_NAME = "db";
	private static final int DEFAULT_DB_VERSION = 1;

	private Class<?>[] entityClassArray = null;

	public DbHelper(Context context) {
		this(context, DEFAULT_DB_NAME, DEFAULT_DB_VERSION, null);
	}

	public DbHelper(Context context, Class<?>[] entityArray) {
		this(context, DEFAULT_DB_NAME, DEFAULT_DB_VERSION, entityArray);
	}

	public DbHelper(Context context, int dbVersion, Class<?>[] entityArray) {
		this(context, DEFAULT_DB_NAME, dbVersion, entityArray);
	}

	public DbHelper(Context context, String dbName, int dbVersion,
			Class<?>[] entityArray) {
		super(context, dbName, null, dbVersion);
		this.entityClassArray = entityArray;
	}

	@Override
	public void onCreate(SQLiteDatabase sqlitedatabase) {
		createTables(sqlitedatabase);
	}

	public void createTables() {
		createTables(getWritableDatabase());
	}

	protected void createTables(SQLiteDatabase sqlitedatabase) {
		if (entityClassArray == null) {
			return;
		}

		// エンティティに対応するテーブル作成用SQLを作成する。
		for (Class<?> e : entityClassArray) {
			createTable(sqlitedatabase, e);
		}
	}

	protected void createTable(SQLiteDatabase sqlitedatabase, Class<?> entity) {
		String execSql = DbUtil.getCreateTableSql(entity);
		LogUtil.d(execSql);
		sqlitedatabase.execSQL(execSql);
	}

	@Override
	public void onUpgrade(SQLiteDatabase sqlitedatabase, int oldVersion,
			int newVersion) {

		// テーブルを削除する。
		dropTables(sqlitedatabase);

		// テーブルを再作成する。
		createTables(sqlitedatabase);
	}

	public void dropTables() {
		dropTables(getWritableDatabase());
	}

	protected void dropTables(SQLiteDatabase sqlitedatabase) {
		if (entityClassArray == null) {
			return;
		}

		// テーブルを削除する（データも消えてしまう）。
		for (Class<?> e : entityClassArray) {
			dropTable(sqlitedatabase, e);
		}
	}

	protected void dropTable(SQLiteDatabase sqlitedatabase, Class<?> entity) {
		String execSql = DbUtil.getDropTableSql(entity);
		LogUtil.d(execSql);
		sqlitedatabase.execSQL(execSql);
	}

	public <T> List<T> query(Class<T> targetClass, String where,
			String[] whereArgs, String orderBy) {
		return query(targetClass, where, whereArgs, null, null, orderBy, null);
	}

	public <T> List<T> query(Class<T> targetClass, String where,
			String[] whereArgs, String orderBy, int limit) {
		return query(targetClass, where, whereArgs, null, null, orderBy,
				String.valueOf(limit));
	}

	public <T> T querySingle(Class<T> targetClass, String where,
			String[] whereArgs, String orderBy) {
		List<T> list = query(targetClass, where, whereArgs, null, null,
				orderBy, "1");
		if (list.size() == 0) {
			return null;
		} else {
			return list.get(0);
		}
	}

	public <T> T querySingle(Class<T> targetClass, String orderBy) {
		return querySingle(targetClass, null, null, orderBy);
	}

	/**
	 * レコード検索処理
	 *
	 * @param <T>
	 *            エンティティクラス
	 * @param targetClass
	 *            エンティティクラス
	 * @param where
	 *            検索条件。パラメータ部分は"?"のように記述する。nullの場合、条件なし。
	 * @param whereArgs
	 *            検索条件のパラメータ配列。不要な場合null。
	 * @param groupBy
	 *            未指定の場合null。
	 * @param having
	 *            未指定の場合null。
	 * @param orderBy
	 *            ソート条件。未指定の場合null。
	 * @param limit
	 *            取得件数。未指定の場合null。
	 * @return エンティティのリスト
	 */
	public <T> List<T> query(Class<T> targetClass, String where,
			String[] whereArgs, String groupBy, String having, String orderBy,
			String limit) {
		List<T> result = new ArrayList<T>();
		SQLiteDatabase db = null;
		Cursor cursor = null;
		String tableName = DbUtil.getTableName(targetClass);

		try {
			db = getReadableDatabase();
			LogUtil.d("where=[" + where + "],whereArgs="
					+ ReflectionUtil.toString(whereArgs));
			List<Field> fieldList = DbUtil.getColumnFieldList(targetClass);
			cursor = db.query(tableName, DbUtil.getColumnNameArray(fieldList),
					where, whereArgs, groupBy, having, orderBy, limit);
			while (cursor.moveToNext()) {
				// レコード取得
				result.add(DbUtil.createEntity(targetClass, fieldList, cursor));
			}
		} finally {
			if (cursor != null) {
				cursor.close();
			}
			if (db != null) {
				db.close();
			}
		}

		LogUtil.i("table=" + tableName + ",count=" + result.size());
		return result;
	}

	/**
	 * レコード件数取得処理
	 *
	 * @param <T>
	 *            エンティティクラス
	 * @param targetClass
	 *            エンティティクラス
	 * @param where
	 *            検索条件。パラメータ部分は"?"のように記述する。nullの場合、条件なし。
	 * @param whereArgs
	 *            検索条件のパラメータ配列。不要な場合null。
	 * @param groupBy
	 *            未指定の場合null。
	 * @param having
	 *            未指定の場合null。
	 * @param orderBy
	 *            ソート条件。未指定の場合null。
	 * @param limit
	 *            取得件数。未指定の場合null。
	 * @return エンティティのリスト
	 */
	public <T> int queryCount(Class<T> targetClass, String where,
			String[] whereArgs, String groupBy, String having, String orderBy,
			String limit) {
		int count = 0;
		SQLiteDatabase db = null;
		Cursor cursor = null;

		try {
			db = getReadableDatabase();
			LogUtil.d("where=[" + where + "],whereArgs="
					+ ReflectionUtil.toString(whereArgs));
			cursor = db.query(DbUtil.getTableName(targetClass),
					DbUtil.getColumnNameArray(targetClass), where, whereArgs,
					groupBy, having, orderBy, limit);
			count = cursor.getCount();
		} finally {
			if (cursor != null) {
				cursor.close();
			}
			if (db != null) {
				db.close();
			}
		}

		LogUtil.i("count=" + count);
		return count;
	}

	public <T> int queryCount(Class<T> targetClass) {
		return queryCount(targetClass, null, null, null, null, null, null);
	}

	public <T> int queryCount(Class<T> targetClass, String where,
			String[] whereArgs) {
		return queryCount(targetClass, where, whereArgs, null, null, null, null);
	}

	/**
	 * レコード検索処理
	 *
	 * @param <T>
	 *            エンティティクラス
	 * @param targetClass
	 *            エンティティクラス
	 * @param sql
	 *            SQL文。パラメータ部分は"?"のように記述する。
	 * @param args
	 *            検索条件のパラメータ配列。不要な場合null。
	 * @return エンティティのリスト
	 */
	public <T> List<T> rawQuery(Class<T> targetClass, String sql, String[] args) {
		List<T> result = new ArrayList<T>();
		SQLiteDatabase db = null;
		Cursor cursor = null;
		String tableName = DbUtil.getTableName(targetClass);

		try {
			db = getReadableDatabase();
			LogUtil.d("sql=[" + sql + "],args=" + ReflectionUtil.toString(args));
			cursor = db.rawQuery(sql, args);
			while (cursor.moveToNext()) {
				// レコード取得
				result.add(DbUtil.createEntity(targetClass, cursor));
			}
		} finally {
			if (cursor != null) {
				cursor.close();
			}
			if (db != null) {
				db.close();
			}
		}

		LogUtil.i("table=" + tableName + ",count=" + result.size());
		return result;
	}

	/**
	 * レコード挿入処理
	 *
	 * @param <T>
	 * @param obj
	 * @return
	 */
	public <T> int insert(T obj) {
		long id = 0;
		SQLiteDatabase db = null;
		String tableName = DbUtil.getTableName(obj);
		try {
			db = getWritableDatabase();
			LogUtil.d("Object=" + ReflectionUtil.toString(obj));
			id = db.insertOrThrow(tableName, null,
					DbUtil.createContentValues(obj, true));
		} finally {
			if (db != null) {
				db.close();
			}
		}
		LogUtil.i("table=" + tableName + ",id=" + id);
		return (int) id;
	}

	/**
	 * レコード更新処理
	 *
	 * エンティティには、"id"カラムが存在し、主キーとして定義されていること。
	 *
	 * @param <T>
	 *            エンティティクラス
	 * @param obj
	 *            更新対象のエンティティインスタンス
	 * @return 更新したレコード数
	 */
	public <T> int update(T obj) {
		int count = 0;
		SQLiteDatabase db = null;
		String tableName = DbUtil.getTableName(obj);
		try {
			db = getWritableDatabase();
			LogUtil.d("Object=" + ReflectionUtil.toString(obj));
			count = db.update(tableName,
					DbUtil.createContentValues(obj, false),
					DbUtil.getIdWhere(obj), null);
		} finally {
			if (db != null) {
				db.close();
			}
		}
		LogUtil.i("table=" + tableName + ",count=" + count);
		return count;
	}

	/**
	 * レコード削除処理
	 *
	 * エンティティには、"id"カラムが存在し、主キーとして定義されていること。
	 *
	 * @param <T>
	 *            エンティティクラス
	 * @param obj
	 *            削除対象のエンティティインスタンス
	 * @return 削除したレコード数
	 */
	public <T> int delete(T obj) {
		return delete(obj.getClass(), DbUtil.getIdWhere(obj), null);
	}

	public int delete(Class<?> entityClass) {
		return delete(entityClass, null, null);
	}

	public int delete(Class<?> entityClass, String where, String[] whereArgs) {
		int count = 0;
		SQLiteDatabase db = null;
		String tableName = DbUtil.getTableName(entityClass);
		try {
			db = getWritableDatabase();
			LogUtil.d("where=" + where + ",whereArgs="
					+ ReflectionUtil.toString(whereArgs));
			count = db.delete(tableName, where, whereArgs);
		} finally {
			if (db != null) {
				db.close();
			}
		}
		LogUtil.i("table=" + tableName + ",count=" + count);
		return count;
	}

	public <T> List<T> findAll(Class<T> targetClass, String orderBy) {
		return query(targetClass, null, null, null, null, orderBy, null);
	}

}
