package jp.sf.amateras.mirage.scala

import collection.JavaConversions._
import jp.sf.amateras.mirage.parser.{SqlContext, Node, SqlParserImpl}
import java.lang.reflect.Field
import jp.sf.amateras.mirage.{IterationCallback, SqlManagerImpl, SqlExecutor}

/**
 * SqlManager wrapper for Scala.
 */
class SqlManager(sqlManager: jp.sf.amateras.mirage.SqlManagerImpl) {

  // TODO もう少しいい初期化の方法はないかなぁ
  BeanDescFactoryInitializer.initialize()

  def getSingleResult[T](clazz: Class[T], sqlPath: String): Option[T] = someOrNone(sqlManager.getSingleResult(clazz, sqlPath))

  def getSingleResult[T](clazz: Class[T], sqlPath: String, param: AnyRef): Option[T] = someOrNone(sqlManager.getSingleResult(clazz, sqlPath, param))

  def getSingleResult[T](clazz: Class[T], sql: SqlProvider): Option[T] = getSingleResult(clazz, sql, null)

  def getSingleResult[T](clazz: Class[T], sql: SqlProvider, param: AnyRef): Option[T] = {
    val node: Node = new SqlParserImpl(sql.getSql()).parse()
    val context: SqlContext = prepareSqlContext(param)
    node.accept(context)

    someOrNone(getSqlExecutor.getSingleResult(clazz, context.getSql, context.getBindVariables))
  }

  def getResultList[T](clazz: Class[T], sqlPath: String): List[T] = sqlManager.getResultList(clazz, sqlPath).toList

  def getResultList[T](clazz: Class[T], sqlPath: String, param: AnyRef): List[T] = sqlManager.getResultList(clazz, sqlPath, param).toList

  def getResultList[T](clazz: Class[T], sql: SqlProvider): List[T] = getResultList(clazz, sql, null)

  def getResultList[T](clazz: Class[T], sql: SqlProvider, param: AnyRef): List[T] = {
    val node: Node = new SqlParserImpl(sql.getSql()).parse()
    val context: SqlContext = prepareSqlContext(convertParam(param))
    node.accept(context)

    if(clazz == classOf[Map[String, _]]){
      // java.util.MapをScalaのMapに変換
      getSqlExecutor.getResultList(classOf[java.util.Map[String, _]], context.getSql,
        context.getBindVariables).toList.map { entry => entry.toMap }.asInstanceOf[List[T]]
    } else {
      getSqlExecutor.getResultList(clazz, context.getSql, context.getBindVariables).toList
    }
  }

  def iterate[T, R](clazz: Class[T], sql: SqlProvider, callback: (T) => R): R = iterate(clazz, sql, null, callback)

  def iterate[T, R](clazz: Class[T], sql: SqlProvider, param: AnyRef, callback: (T) => R): R = {
    val node: Node = new SqlParserImpl(sql.getSql()).parse()
    val context: SqlContext = prepareSqlContext(convertParam(param))
    node.accept(context)

    getSqlExecutor.iterate(clazz, new IterationCallbackAdapter(callback), context.getSql, context.getBindVariables)
  }

  def iterate[T, R](clazz: Class[T], sql: SqlProvider, callback: (T, R) => R, context: R): R = iterate(clazz, sql, null, callback, context)

  def iterate[T, R](clazz: Class[T], sql: SqlProvider, param: AnyRef, callback: (T, R) => R, context: R): R = {
    val node: Node = new SqlParserImpl(sql.getSql()).parse()
    val sqlContext: SqlContext = prepareSqlContext(convertParam(param))
    node.accept(sqlContext)

    getSqlExecutor.iterate(clazz, new IterationCallbackAdapterWithContext(callback, context), sqlContext.getSql, sqlContext.getBindVariables)
  }

  def executeUpdate(sql: SqlProvider): Int = executeUpdate(sql, null)

  def executeUpdate(sql: SqlProvider, param: AnyRef): Int = {
    val node: Node = new SqlParserImpl(sql.getSql()).parse()
    val context: SqlContext = prepareSqlContext(convertParam(param))
    node.accept(context)

    getSqlExecutor.executeUpdateSql(context.getSql, context.getBindVariables, null)
  }

  def getCount(sql: SqlProvider): Int = getCount(sql, null)

  def getCount(sql: SqlProvider, param: AnyRef): Int = {
    val node: Node = new SqlParserImpl(sql.getSql()).parse()
    val context: SqlContext = prepareSqlContext(convertParam(param))
    node.accept(context)
    val countSql: String = sqlManager.getDialect.getCountSql(context.getSql);

		getSqlExecutor.getSingleResult(classOf[java.lang.Integer], countSql, context.getBindVariables).intValue
  }

  // TODO AnyじゃなくてObject？
  /**
   * Finds the entity by the given primary key.
   * @param clazz the type of entity
   * @param id primary keys
   * @return the entity. If the entity which corresponds to the given primary key is not found, this method returns None.
   */
  def findEntity[T](clazz: Class[T], id: Any*): Option[T] = {
    someOrNone(sqlManager.findEntity(clazz, id.map {e => e.asInstanceOf[java.lang.Object]}: _*))
  }

  /**
   * Inserts the given entity.
   * @param entity the entity to insert
   * @return updated row count
   */
  def insertEntity(entity: AnyRef): Int = sqlManager.insertEntity(entity)

  /**
   * Updates the given entity.
   * @param entity the entity to update
   * @return updated row count
   */
  def updateEntity(entity: AnyRef): Int = sqlManager.updateEntity(entity)

  /**
   * Deletes the given entity.
   * @param entity the entity to delete
   * @return updated row count
   */
  def deleteEntity(entity: AnyRef): Int = sqlManager.deleteEntity(entity)

  /**
   * パラメータがScalaのMapだったらjava.util.Mapに変換します。
   */
  private def convertParam(param: AnyRef): AnyRef =
    if(param.isInstanceOf[Map[String, _]]){
      val map: java.util.Map[String, _] = param.asInstanceOf[Map[String, _]]
      map
    } else {
      param
    }

  /**
   * 引数がnullだったらNone、それ以外だったらSomeでラップして返します。
   */
  // TODO utilに移すべき？
  private def someOrNone[T](value: T): Option[T] = {
    if(value != null){
      Some(value)
    } else {
      None
    }
  }

  // TODO リフレクション以外に方法はない？？
  private def getSqlExecutor: SqlExecutor = {
    val field: Field = classOf[SqlManagerImpl].getDeclaredField("sqlExecutor")
    field.setAccessible(true)
    field.get(sqlManager).asInstanceOf[SqlExecutor]
  }

  // TODO やるならここもリフレクションにしたほうがいいかも
  // TODO SqlManagerImplを継承すればprepareSqlContext()を直接呼び出せるけど…
  private def prepareSqlContext(param: AnyRef): SqlContext = {
    val method = classOf[SqlManagerImpl].getDeclaredMethod("prepareSqlContext", classOf[Object])
    method.setAccessible(true)
    method.invoke(sqlManager, param).asInstanceOf[SqlContext]
  }

  /**
   * イテレーション検索で使用するコールバック関数のアダプタ（引数なし）。
   */
  private class IterationCallbackAdapter[T, R](val callback: (T) => R) extends IterationCallback[T, R] {
    def iterate(entity: T): R = callback(entity)
  }

  /**
   * イテレーション検索で使用するコールバック関数のアダプタ（引数あり）。
   */
  private class IterationCallbackAdapterWithContext[T, R](val callback: (T, R) => R, var context: R) extends IterationCallback[T, R] {
    def iterate(entity: T): R = {
      context = callback(entity, context)
      return context
    }
  }

}