package net.liftweb.mapper

/*
 * Copyright 2006-2009 WorldWide Conferencing, LLC
 *
 * 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.
 */

import _root_.scala.collection.mutable.{ListBuffer, HashMap}
import _root_.java.lang.reflect.Method
import _root_.java.sql.{ResultSet, Types, PreparedStatement, Statement}
import _root_.scala.xml.{Elem, Node, Text, NodeSeq, Null, TopScope, UnprefixedAttribute, MetaData}
import _root_.net.liftweb.util.Helpers._
import _root_.net.liftweb.util.{Box, Empty, Full, Failure, NamedPF}
import _root_.net.liftweb.http.{LiftRules, S, SHtml, FieldError}
import _root_.java.util.Date
import _root_.net.liftweb.http.js._

trait BaseMetaMapper {
  type RealType <: Mapper[RealType]

  def beforeSchemifier: Unit
  def afterSchemifier: Unit

  def dbTableName: String
  def mappedFields: Seq[BaseMappedField];
  def dbAddTable: Box[() => Unit]

  def dbIndexes: List[Index[RealType]]
}

/**
 * Rules and functions shared by all Mappers
 */
object MapperRules {
  /**
   * This function converts a header name into the appropriate
   * XHTML format for displaying across the headers of a
   * formatted block.  The default is <th> for use
   * in XHTML tables.  If you change this function, the change
   * will be used for all MetaMappers, unless they've been
   * explicitly changed.
   */
  var displayNameToHeaderElement: String => NodeSeq = in => <th>{in}</th>

  /**
   * This function converts an element into the appropriate
   * XHTML format for displaying across a line
   * formatted block.  The default is <td> for use
   * in XHTML tables.  If you change this function, the change
   * will be used for all MetaMappers, unless they've been
   * explicitly changed.
   */
  var displayFieldAsLineElement: NodeSeq => NodeSeq = in => <td>{in}</td>

  /**
   * This function is the global (for all MetaMappers that have
   * not changed their formatFormElement function) that
   * converts a name and form for a given field in the
   * model to XHTML for presentation in the browser.  By
   * default, a table row ( <tr> ) is presented, but
   * you can change the function to display something else.
   */
  var formatFormElement: (NodeSeq, NodeSeq) => NodeSeq =
  (name, form) =>
  <xml:group><tr>
      <td>{name}</td>
      <td>{form}</td>
             </tr></xml:group>
}

trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] {
  self: A =>

  type RealType = A

  def beforeValidation: List[A => Any] = Nil
  def beforeValidationOnCreate: List[A => Any] = Nil
  def beforeValidationOnUpdate: List[A => Any] = Nil
  def afterValidation: List[A => Any] = Nil
  def afterValidationOnCreate: List[A => Any] = Nil
  def afterValidationOnUpdate: List[A => Any] = Nil

  def beforeSave: List[A => Any] = Nil
  def beforeCreate: List[(A) => Any] = Nil
  def beforeUpdate: List[(A) => Any] = Nil

  def afterSave: List[(A) => Any] = Nil
  def afterCreate: List[(A) => Any] = Nil
  def afterUpdate: List[(A) => Any] = Nil

  def beforeDelete: List[(A) => Any] = Nil
  def afterDelete: List[(A) => Any] = Nil

  /**
   * If there are model-specific validations to perform, override this
   * method and return an additional list of validations to perform
   */
  def validation: List[A => List[FieldError]] = Nil

  private def clearPostCommit(in: A) {
    in.addedPostCommit = false
  }

  def afterCommit: List[A => Unit] = clearPostCommit _ :: Nil

  def dbDefaultConnectionIdentifier: ConnectionIdentifier = DefaultConnectionIdentifier

  def findAll(): List[A] =
  findMapDb(dbDefaultConnectionIdentifier, Nil :_*)(v => Full(v))

  def findAllDb(dbId:ConnectionIdentifier): List[A] =
  findMapDb(dbId, Nil :_*)(v => Full(v))

  def countByInsecureSql(query: String, checkedBy: IHaveValidatedThisSQL): scala.Long =
  countByInsecureSqlDb(dbDefaultConnectionIdentifier, query, checkedBy)

  def countByInsecureSqlDb(dbId: ConnectionIdentifier, query: String, checkedBy: IHaveValidatedThisSQL): scala.Long =
  DB.use(dbId)(DB.prepareStatement(query, _)(DB.exec(_)(rs => if (rs.next) rs.getLong(1) else 0L)))

  def findAllByInsecureSql(query: String, checkedBy: IHaveValidatedThisSQL): List[A] = findAllByInsecureSqlDb(dbDefaultConnectionIdentifier, query, checkedBy)

  /**
   * Execute a PreparedStatement and return a List of Mapper instances. {@code f} is
   * where the user will do the work of creating the PreparedStatement and
   * preparing it for execution.
   *
   * @param f A function that takes a SuperConnection and returns a PreparedStatement.
   * @return A List of Mapper instances.
   */
  def findAllByPreparedStatement(f: SuperConnection => PreparedStatement): List[A] = {
    DB.use(dbDefaultConnectionIdentifier) {
      conn =>
      findAllByPreparedStatement(dbDefaultConnectionIdentifier, f(conn))
    }
  }

  def findAllByPreparedStatement(dbId: ConnectionIdentifier, stmt: PreparedStatement): List[A] = findAllByPreparedStatementDb(dbId, stmt)(a => Full(a))

  def findAllByPreparedStatementDb[T](dbId: ConnectionIdentifier, stmt: PreparedStatement)(f: A => Box[T]): List[T] = {
    DB.exec(stmt) {
      rs => createInstances(dbId, rs, Empty, Empty, f)
    }
  }

  def findAllByInsecureSqlDb(dbId: ConnectionIdentifier, query: String, checkedBy: IHaveValidatedThisSQL): List[A] =
  findMapByInsecureSqlDb(dbId, query, checkedBy)(a => Full(a))


  def findMapByInsecureSql[T](query: String, checkedBy: IHaveValidatedThisSQL)
  (f: A => Box[T]): List[T] =
  findMapByInsecureSqlDb(dbDefaultConnectionIdentifier, query, checkedBy)(f)

  def findMapByInsecureSqlDb[T](dbId: ConnectionIdentifier, query: String, checkedBy: IHaveValidatedThisSQL)(f: A => Box[T]): List[T] = {
    DB.use(dbId) {
      conn =>
      DB.prepareStatement(query, conn) {
        st =>
        DB.exec(st) {
          rs =>
          createInstances(dbId, rs, Empty, Empty, f)
        }
      }
    }
  }

  def dbAddTable: Box[() => Unit] = Empty

  def count: Long = countDb(dbDefaultConnectionIdentifier, Nil :_*)

  def count(by: QueryParam[A]*): Long = countDb(dbDefaultConnectionIdentifier, by:_*)

  def countDb(dbId: ConnectionIdentifier, by: QueryParam[A]*): Long = {
    DB.use(dbId) {
      conn =>
      val bl = by.toList
      val (query, start, max) = addEndStuffs(addFields("SELECT COUNT(*) FROM "+dbTableName+"  ", false, bl), bl, conn)

      DB.prepareStatement(query, conn) {
        st =>
        setStatementFields(st, bl, 1)
        DB.exec(st) {
          rs =>
          if (rs.next) rs.getLong(1)
          else 0
        }
      }
    }
  }

  type KeyDude = T forSome {type T}
  type OtherMapper = T forSome {type T <: KeyedMapper[KeyDude, T]}
  type OtherMetaMapper = T forSome {type T <: KeyedMetaMapper[KeyDude, OtherMapper]}
  //type OtherMapper = KeyedMapper[_, (T forSome {type T})]
  //type OtherMetaMapper = KeyedMetaMapper[_, OtherMapper]

  def findAllFields(fields: Seq[SelectableField],
                    by: QueryParam[A]*): List[A] =
  findMapFieldDb(dbDefaultConnectionIdentifier,
                 fields, by :_*)(v => Full(v))

  def findAllFieldsDb(dbId: ConnectionIdentifier,
                      fields: Seq[SelectableField],
                      by: QueryParam[A]*):
  List[A] = findMapFieldDb(dbId, fields, by :_*)(v => Full(v))

  private def dealWithPrecache(ret: List[A], by: Seq[QueryParam[A]]): List[A] = {

    val precache = by.flatMap{case j: PreCache[A] => List(j) case _ => Nil}
    for (j <- precache) {
      type FT = j.field.FieldType
    type MT = T forSome {type T <: KeyedMapper[FT, T]}


      val ol: List[MT] = j.field.dbKeyToTable.
      asInstanceOf[MetaMapper[A]].
      findAll(new InThing[A]{
          type JoinType = FT
          type InnerType = A

          val outerField: MappedField[JoinType, A] =
          j.field.dbKeyToTable.primaryKeyField.asInstanceOf[MappedField[JoinType, A]]
          val innerField: MappedField[JoinType, A] = j.field.asInstanceOf[MappedField[JoinType, A]]
          val innerMeta: MetaMapper[A] = j.field.fieldOwner.getSingleton

          val queryParams: List[QueryParam[A]] = by.toList
        }.asInstanceOf[QueryParam[A]] ).asInstanceOf[List[MT]]

      val map: Map[FT, MT] =
      Map(ol.map(v => (v.primaryKeyField.is, v)) :_*)

      for (i <- ret) {

        val field: MappedForeignKey[FT, A, _] =
        getActualField(i, j.field).asInstanceOf[MappedForeignKey[FT, A, _]]

        map.get(field.is) match {
          case Some(v) => field.primeObj(Full(v))
            case _ => field.primeObj(Empty)
        }
        //field.primeObj(Box(map.get(field.is).map(_.asInstanceOf[QQ])))
      }
    }

    ret
  }

  def findAll(by: QueryParam[A]*): List[A] =
  dealWithPrecache(findMapDb(dbDefaultConnectionIdentifier, by :_*)
                (v => Full(v)), by)


  def findAllDb(dbId: ConnectionIdentifier,by: QueryParam[A]*): List[A] =
  dealWithPrecache(findMapDb(dbId, by :_*)(v => Full(v)), by)

  def bulkDelete_!!(by: QueryParam[A]*): Boolean = bulkDelete_!!(dbDefaultConnectionIdentifier, by :_*)
  def bulkDelete_!!(dbId: ConnectionIdentifier, by: QueryParam[A]*): Boolean = {
    DB.use(dbId) {
      conn =>
      val bl = by.toList
      val (query, start, max) = addEndStuffs(addFields("DELETE FROM "+dbTableName+" ", false, bl), bl, conn)

      DB.prepareStatement(query, conn) {
        st =>
        setStatementFields(st, bl, 1)
        st.executeUpdate
        true
      }
    }
  }

  private def distinct(in: Seq[QueryParam[A]]): String =
  in.filter{case Distinct() => true case _ => false} match {
    case Nil => ""
      case _ => " DISTINCT "
  }

  def findMap[T](by: QueryParam[A]*)(f: A => Box[T]) =
  findMapDb(dbDefaultConnectionIdentifier, by :_*)(f)

  def findMapDb[T](dbId: ConnectionIdentifier,
                   by: QueryParam[A]*)(f: A => Box[T]): List[T] =
  findMapFieldDb(dbId, mappedFields, by :_*)(f)

  def findMapFieldDb[T](dbId: ConnectionIdentifier, fields: Seq[SelectableField],
                        by: QueryParam[A]*)(f: A => Box[T]): List[T] = {
    DB.use(dbId) {
      conn =>
      val bl = by.toList
      val (query, start, max) = addEndStuffs(addFields("SELECT "+
                                                        distinct(by)+
                                                       fields.map(_.dbSelectString).
                                                       mkString(", ")+
                                                       " FROM "+dbTableName+
                                                       "  ", false, bl), bl, conn)
      DB.prepareStatement(query, conn) {
        st =>
        setStatementFields(st, bl, 1)
        DB.exec(st)(createInstances(dbId, _, start, max, f))
      }
    }
  }

  def create: A = createInstance


  private[mapper] def addFields(what: String, whereAdded: Boolean, by: List[QueryParam[A]]): String = {

    var wav = whereAdded

    def whereOrAnd = if (wav) " AND " else {wav = true; " WHERE "}

    by match {
      case Nil => what
      case x :: xs => {
          var updatedWhat = what
          x match {
            case Cmp(field, opr, Full(_), _) =>
              (1 to field.dbColumnCount).foreach {
                cn =>
                updatedWhat = updatedWhat + whereOrAnd +field.dbColumnNames(field.name)(cn - 1)+" "+opr+" ? "
              }

            case Cmp(field, opr, _, Full(otherField)) =>
              (1 to field.dbColumnCount).foreach {
                cn =>
                updatedWhat = updatedWhat + whereOrAnd +field.dbColumnNames(field.name)(cn - 1)+" "+opr+" "+
                otherField.dbColumnNames(otherField.name)(cn - 1)
              }

            case Cmp(field, opr, Empty, Empty) =>
              (1 to field.dbColumnCount).foreach (cn => updatedWhat = updatedWhat + whereOrAnd +field.dbColumnNames(field.name)(cn - 1)+" "+opr+" ")

              // For vals, add "AND $fieldname = ? [OR $fieldname = ?]*" to the query. The number
              // of fields you add onto the query is equal to vals.length
            case ByList(field, vals) =>
              vals match {
                case Nil =>
                  updatedWhat = updatedWhat + whereOrAnd + " 0 = 1 "

                case _ => {
                    updatedWhat = updatedWhat +
                    vals.map(v => field.dbColumnName+ " = ?").mkString(whereOrAnd+" (", " OR ", ")")
                  }
              }


            case in: InRaw[A, _] =>
              updatedWhat = updatedWhat + whereOrAnd + (in.rawSql match {
                  case null | "" => " 0 = 1 "
                  case sql => " "+in.field.dbColumnName+" IN ( "+sql+" ) "
                })

            case (in: InThing[A]) =>
              updatedWhat = updatedWhat + whereOrAnd +
              in.outerField.dbColumnName+
              " IN ("+in.innerMeta.addFields("SELECT "+
                                             in.distinct+
                                             in.innerField.dbColumnName+
                                             " FROM "+
                                             in.innerMeta.dbTableName+" ",false,
                                             in.queryParams)+" ) "


              // Executes a subquery with {@code query}
            case BySql(query, _,  _*) =>
              updatedWhat = updatedWhat + whereOrAnd + " ( "+ query +" ) "
            case _ =>
          }
          addFields(updatedWhat, wav, xs)
        }
    }
  }


  private[mapper] def setStatementFields(st: PreparedStatement, by: List[QueryParam[A]], curPos: Int): Int = {
    by match {
      case Nil => curPos
      case Cmp(field, _, Full(value), _) :: xs =>
        st.setObject(curPos, field.convertToJDBCFriendly(value), field.targetSQLType)
        setStatementFields(st, xs, curPos + 1)

      case ByList(field, vals) :: xs => {
          var newPos = curPos
          vals.foreach(v => {
              st.setObject(newPos,
                           field.convertToJDBCFriendly(v),
                           field.targetSQLType)
              newPos = newPos + 1
            })
          setStatementFields(st, xs, newPos)
        }

      case (in: InThing[A]) :: xs =>
        val newPos = in.innerMeta.setStatementFields(st, in.queryParams,
                                                     curPos)
        setStatementFields(st, xs, newPos)

      case BySql(query, who, params @ _*) :: xs => {
          params.toList match {
            case Nil => setStatementFields(st, xs, curPos)
            case List(i: Int) =>
              st.setInt(curPos, i)
              setStatementFields(st, xs, curPos + 1)
            case List(lo: Long) =>
              st.setLong(curPos, lo)
              setStatementFields(st, xs, curPos + 1)
            case List(s: String) =>
              st.setString(curPos, s)
              setStatementFields(st, xs, curPos + 1)
            case List(d: Date) =>
              st.setDate(curPos, new _root_.java.sql.Date(d.getTime))
              setStatementFields(st, xs, curPos + 1)
            case List(field: BaseMappedField) => st.setObject(curPos, field.jdbcFriendly, field.targetSQLType)
              setStatementFields(st, xs, curPos + 1)

            case p :: ps =>
              setStatementFields(st, BySql[A](query, who, p) :: BySql[A](query, who, ps: _*) :: xs, curPos)
          }
        }
      case _ :: xs => {
          setStatementFields(st, xs, curPos)
        }
    }
  }

  // def find(by: QueryParam): Box[A] = find(List(by))

  private def _addOrdering(in: String, params: List[QueryParam[A]]): String = {
    params.flatMap{
      case OrderBy(field, order) => List(field.dbColumnName+" "+order.sql)
      case OrderBySql(sql, _) => List(sql)
      case _ => Nil
    } match {
      case Nil => in
      case xs => in + " ORDER BY "+xs.mkString(" , ")
    }
  }

  def addEndStuffs(in: String, params: List[QueryParam[A]], conn: SuperConnection): (String, Box[Long], Box[Long]) = {
    val tmp = _addOrdering(in, params)
    val max = params.foldRight(Empty.asInstanceOf[Box[Long]]){(a,b) => a match {case MaxRows(n) => Full(n); case _ => b}}
    val start = params.foldRight(Empty.asInstanceOf[Box[Long]]){(a,b) => a match {case StartAt(n) => Full(n); case _ => b}}

    if (conn.brokenLimit_?) (tmp, start, max) else {
      val ret = (max, start) match {
        case (Full(max), Full(start)) => tmp + " LIMIT "+max+" OFFSET "+start
        case (Full(max), _) => tmp + " LIMIT "+max
        case (_, Full(start)) => tmp + " LIMIT "+conn.driverType.maxSelectLimit+" OFFSET "+start
        case _ => tmp
      }
      (ret, Empty, Empty)
    }
  }

  def delete_!(toDelete : A): Boolean = indexMap.map(im =>
    DB.use(toDelete.connectionIdentifier) {
      conn =>
      _beforeDelete(toDelete)
      val ret = DB.prepareStatement("DELETE FROM "+dbTableName +" WHERE "+im+" = ?", conn) {
        st =>
        val indVal = indexedField(toDelete)
        indVal.map{indVal =>
          st.setObject(1, indVal.jdbcFriendly(im), indVal.targetSQLType(im))

          st.executeUpdate == 1
        } openOr false
      }
      _afterDelete(toDelete)
      ret
    }
  ).openOr(false)


  type AnyBound = T forSome {type T}

  private[mapper] def ??(meth: Method, inst: A) = meth.invoke(inst).asInstanceOf[MappedField[AnyBound, A]]

  def dirty_?(toTest: A): Boolean = mappedFieldList.exists(
    mft =>
    ??(mft.method, toTest).dirty_?
  )

  def indexedField(toSave: A): Box[MappedField[Any, A]] =
    indexMap.map(im => ??(mappedColumns(im), toSave))

  def saved_?(toSave: A): Boolean = (for (im <- indexMap; indF <- indexedField(toSave)) yield (indF.dbIndexFieldIndicatesSaved_?)).openOr(true)

  def whatToSet(toSave : A) : String = {
    mappedColumns.filter{c => ??(c._2, toSave).dirty_?}.map{c => c._1 + " = ?"}.toList.mkString("", ",", "")
  }

  /**
   * Run the list of field validations, etc.  This is the raw validation,
   * without the notifications.  This method can be over-ridden.
   */
  protected def runValidationList(toValidate: A): List[FieldError] =
  mappedFieldList.flatMap(f => ??(f.method, toValidate).validate) :::
  validation.flatMap{
    case pf: PartialFunction[A, List[FieldError]] =>
      if (pf.isDefinedAt(toValidate)) pf(toValidate)
      else Nil

    case f => f(toValidate)
  }

  final def validate(toValidate: A): List[FieldError] = {
    val saved_? = this.saved_?(toValidate)
    _beforeValidation(toValidate)
    if (saved_?) _beforeValidationOnUpdate(toValidate) else _beforeValidationOnCreate(toValidate)

    val ret: List[FieldError] = runValidationList(toValidate)

    _afterValidation(toValidate)
    if (saved_?) _afterValidationOnUpdate(toValidate) else _afterValidationOnCreate(toValidate)

    ret
  }

  val elemName = getClass.getSuperclass.getName.split("\\.").toList.last

  def toXml(what: A): Elem =
  Elem(null,elemName,
       mappedFieldList.foldRight[MetaData](Null) {(p, md) => val fld = ??(p.method, what)
                                                  new UnprefixedAttribute(p.name, Text(fld.toString), md)}
       ,TopScope)

  /**
   * Returns true if none of the fields are dirty
   */
  def clean_?(toCheck: A): Boolean = mappedColumns.foldLeft(true)((bool, ptr) => bool && !(??(ptr._2, toCheck).dirty_?))

  def save(toSave: A): Boolean = {
    /**
     * @return true if there was exactly one row in the result set, false if not.
     */
    def runAppliers(rs: ResultSet) : Boolean = {
      try {
        if (rs.next) {
          val meta = rs.getMetaData
          toSave.runSafe {
            findApplier(indexMap.open_!, rs.getObject(1)) match {
              case Full(ap) => ap.apply(toSave, rs.getObject(1))
              case _ =>
            }
          }
          !rs.next
        } else false
      } finally {
        rs.close
      }
    }

    /**
     * Checks whether the result set has exactly one row.
     */
    def hasOneRow(rs: ResultSet) : Boolean = {
      try {
        val firstRow = rs.next
        (firstRow && !rs.next)
      } finally {
        rs.close
      }
    }

    if (saved_?(toSave) && clean_?(toSave)) true else {
      val ret = DB.use(toSave.connectionIdentifier) {
        conn =>
        _beforeSave(toSave)
        val ret = if (saved_?(toSave)) {
          _beforeUpdate(toSave)
          val ret: Boolean = if (!dirty_?(toSave)) true else {
            val ret: Boolean = DB.prepareStatement("UPDATE "+dbTableName+" SET "+whatToSet(toSave)+" WHERE "+indexMap.open_! +" = ?", conn) {
              st =>
              var colNum = 1

              for (col <- mappedColumns) {
                val colVal = ??(col._2, toSave)
                if (!columnPrimaryKey_?(col._1) && colVal.dirty_?) {
                  colVal.targetSQLType(col._1) match {
                    case Types.VARCHAR => st.setString(colNum, colVal.jdbcFriendly(col._1).asInstanceOf[String])

                    case _ => st.setObject(colNum, colVal.jdbcFriendly(col._1), colVal.targetSQLType(col._1))
                  }
                  colNum = colNum + 1
                }
              }

              indexedField(toSave).foreach(indVal =>  st.setObject(colNum, indVal.jdbcFriendly(indexMap.open_!),
                                                                   indVal.targetSQLType(indexMap.open_!)))
              st.executeUpdate
              true
            }
            ret
          }
          _afterUpdate(toSave)
          ret
        } else {
          _beforeCreate(toSave)

          val query = "INSERT INTO "+dbTableName+" ("+columnNamesForInsert+") VALUES ("+columnQueriesForInsert+")"

          def prepStat(st : PreparedStatement, postQuery: Box[String]): Boolean = {
            var colNum = 1

            for (col <- mappedColumns) {
              if (!columnPrimaryKey_?(col._1)) {
                val colVal = col._2.invoke(toSave).asInstanceOf[MappedField[AnyRef, A]]
                colVal.targetSQLType(col._1) match {
                  case Types.VARCHAR => st.setString(colNum, colVal.jdbcFriendly(col._1).asInstanceOf[String])

                  case _ => st.setObject(colNum, colVal.jdbcFriendly(col._1), colVal.targetSQLType(col._1))
                }

                // st.setObject(colNum, colVal.getJDBCFriendly(col._1), colVal.getTargetSQLType(col._1))
                colNum = colNum + 1
              }
            }

            val oneRowUpdated : Boolean = (conn.brokenAutogeneratedKeys_?, postQuery, indexMap) match {
              case (true, _, Empty)  => hasOneRow(st.executeQuery)

              case (true, Full(qry), _)     =>
                st.executeUpdate
                DB.prepareStatement(qry, conn)(st => runAppliers(st.executeQuery))

              case (true, _, _)     => runAppliers(st.executeQuery)

              case (false, _, Empty) => st.executeUpdate == 1

              case (false, _, _)    =>
                val uc = st.executeUpdate
                runAppliers(st.getGeneratedKeys)
            }
            oneRowUpdated
          }

          val ret = if (conn.wickedBrokenAutogeneratedKeys_?) {
            DB.prepareStatement(query, conn) {
              st => prepStat(st, Full("SELECT lastval()"))
            }
          } else if (conn.brokenAutogeneratedKeys_?) {
            val pkName = (mappedColumnInfo.filter(_._2.dbPrimaryKey_?).map(_._1)).toList.mkString(",")
            DB.prepareStatement(query + " RETURNING " + pkName, conn) {
              st => prepStat(st, Empty)
            }
          } else {
            DB.prepareStatement(query, Statement.RETURN_GENERATED_KEYS, conn) {
              st => prepStat(st, Empty)
            }
          }

          _afterCreate(toSave)
          ret
        }
        _afterSave(toSave)
        ret
      }

      // clear dirty and get rid of history
      for (col <- mappedColumns) {
        val colVal = ??(col._2, toSave)
        if (!columnPrimaryKey_?(col._1) && colVal.dirty_?) {
          colVal.resetDirty
          colVal.doneWithSave
        }
      }

      ret
    }
  }

  def columnPrimaryKey_?(name : String) = mappedColumnInfo.get(name).map(_.dbPrimaryKey_?) getOrElse false

  def createInstances(dbId: ConnectionIdentifier, rs: ResultSet, start: Box[Long], omax: Box[Long]) : List[A] = createInstances(dbId, rs, start, omax, v => Full(v))


  def createInstances[T](dbId: ConnectionIdentifier, rs: ResultSet, start: Box[Long], omax: Box[Long], f: A => Box[T]) : List[T] = {
    var ret = new ListBuffer[T]
    val bm = buildMapper(rs)
    var pos = (start openOr 0L) * -1L
    val max = omax openOr _root_.java.lang.Long.MAX_VALUE

    while (pos < max && rs.next()) {
      if (pos >= 0L) {
        f(createInstance(dbId, rs, bm._1, bm._2)).foreach(v => ret += v)
      }
      pos = pos + 1L
    }

    ret.toList
  }

  def appendFieldToStrings(in: A): String = mappedFieldList.map(p => ??(p.method, in).asString).mkString(",")

  private val columnNameToMappee = new HashMap[String, Box[(ResultSet, Int, A) => Unit]]

  def buildMapper(rs: ResultSet): (Int, Array[(ResultSet,Int,A) => Unit]) = synchronized {
    val meta = rs.getMetaData
    val colCnt = meta.getColumnCount
    val ar = new Array[(ResultSet, Int, A) => Unit](colCnt + 1)
    for (pos <- 1 to colCnt) {
      val colName = meta.getColumnName(pos).toLowerCase
      val optFunc = columnNameToMappee.get(colName) match {
        case None => {
            val colType = meta.getColumnType(pos)
            val fieldInfo = mappedColumns.get(colName)
            val setTo =
            if (fieldInfo != None) {
              val tField = fieldInfo.get.invoke(this).asInstanceOf[MappedField[AnyRef, A]]
              Some(colType match {
                  case Types.INTEGER | Types.BIGINT => {
                      val bsl = tField.buildSetLongValue(fieldInfo.get, colName)
                      (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getLong(pos), rs.wasNull)}
                  case Types.VARCHAR => {
                      val bsl = tField.buildSetStringValue(fieldInfo.get, colName)
                      (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getString(pos))}
                  case Types.DATE | Types.TIME | Types.TIMESTAMP =>
                    val bsl = tField.buildSetDateValue(fieldInfo.get, colName)
                    (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getTimestamp(pos))
                  case Types.BOOLEAN | Types.BIT =>{
                      val bsl = tField.buildSetBooleanValue(fieldInfo.get, colName)
                      (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getBoolean(pos), rs.wasNull)}
                  case _ => {
                      (rs: ResultSet, pos: Int, objInst: A) => {
                        val res = rs.getObject(pos)
                        findApplier(colName, res).foreach(f => f(objInst, res))
                      }
                    }
                })
            } else None

            columnNameToMappee(colName) = Box(setTo)
            Box(setTo)
          }
        case Some(of) => of
      }
      ar(pos) = optFunc openOr null
    }
    (colCnt, ar)
  }

  def createInstance(dbId: ConnectionIdentifier, rs : ResultSet, colCnt: Int, mapFuncs: Array[(ResultSet,Int,A) => Unit]) : A = {
    val ret = createInstance.connectionIdentifier(dbId)
    val ra = ret// .asInstanceOf[Mapper[A]]

    var pos = 1
    while (pos <= colCnt) {
      mapFuncs(pos) match {
        case null =>
        case f => f(rs, pos, ra)
      }
      pos = pos + 1
    }
    ret
  }

  protected def  findApplier(name: String, inst: AnyRef): Box[((A, AnyRef) => Unit)] = synchronized {
    val clz = inst match {
      case null => null
      case _ => inst.getClass.asInstanceOf[Class[(C forSome {type C})]]
    }
    val look = (name.toLowerCase, if (clz ne null) Full(clz) else Empty)
    Box(mappedAppliers.get(look) orElse {
        val newFunc = createApplier(name, inst)
        mappedAppliers(look) = newFunc
        Some(newFunc)
      })
  }


  private def createApplier(name : String, inst : AnyRef /*, clz : Class*/) : (A, AnyRef) => Unit = {
    val accessor = mappedColumns.get(name)
    if ((accessor eq null) || accessor == None) null else {
      (accessor.get.invoke(this).asInstanceOf[MappedField[AnyRef, A]]).buildSetActualValue(accessor.get, inst, name)
    }
  }

  def fieldMapperPF(transform: (BaseOwnedMappedField[A] => NodeSeq), actual: A): PartialFunction[String, NodeSeq => NodeSeq] = {
    Map.empty ++ mappedFieldList.map ( mf =>
      (mf.name, ((ignore: NodeSeq) => transform(??(mf.method, actual))))
    )
  }

  def checkFieldNames(in: A): Unit = mappedFieldList.foreach(f =>
    ??(f.method, in) match {
      case field if (field.i_name_! eq null) => field.setName_!(f.name)
      case _ =>
    })

  /**
   * Get a field by the field name
   * @param fieldName -- the name of the field to get
   * @param actual -- the instance to get the field on
   *
   * @return Box[The Field] (Empty if the field is not found)
   */
  def fieldByName[T](fieldName: String, actual: A): Box[MappedField[T, A]] =
    Box(_mappedFields.get(fieldName)).
  map(meth => ??(meth, actual).asInstanceOf[MappedField[T,A]])

  /**
   * A partial function that takes an instance of A and a field name and returns the mapped field
   */
  lazy val fieldMatcher: PartialFunction[(A, String), MappedField[Any, A]] = {
    case (actual, fieldName) if _mappedFields.contains(fieldName) => fieldByName[Any](fieldName, actual).open_! // we know this is defined
  }

  def createInstance: A = rootClass.newInstance.asInstanceOf[A]

  def fieldOrder: List[BaseOwnedMappedField[A]] = Nil

  protected val rootClass = this.getClass.getSuperclass

  private val mappedAppliers = new HashMap[(String, Box[Class[(C forSome {type C})]]), (A, AnyRef) => Unit];

  private val _mappedFields  = new HashMap[String, Method];

  private[mapper] var mappedFieldList: List[FieldHolder[A]] = Nil; // new Array[Triple[String, Method, MappedField[Any,Any]]]();

  private var mappedCallbacks: List[(String, Method)] = Nil

  private val mappedColumns = new HashMap[String, Method];

  // private val mappedFieldInfo = new HashMap[String, MappedField[AnyRef, A]]
  private val mappedColumnInfo = new HashMap[String, MappedField[AnyRef, A]]



  private var indexMap: Box[String] = Empty

  this.runSafe {
    val tArray = new ListBuffer[FieldHolder[A]]

    def isMagicObject(m: Method) = m.getReturnType.getName.endsWith("$"+m.getName+"$") && m.getParameterTypes.length == 0
    def isMappedField(m: Method) = classOf[MappedField[Nothing, A]].isAssignableFrom(m.getReturnType)
    def isLifecycle(m: Method) = classOf[LifecycleCallbacks].isAssignableFrom(m.getReturnType)

    mappedCallbacks = for (v <- this.getClass.getSuperclass.getMethods.toList if isMagicObject(v) && isLifecycle(v)) yield (v.getName, v)

    for (v <- this.getClass.getSuperclass.getMethods  if isMagicObject(v) && isMappedField(v)) {
      v.invoke(this) match {
        case mf: MappedField[AnyRef, A] if !mf.ignoreField_? =>
          mf.setName_!(v.getName)
          tArray += FieldHolder(mf.name, v, mf)
          for (colName <- mf.dbColumnNames(v.getName)) {
            mappedColumnInfo(colName) = mf
            mappedColumns(colName) = v
          }
          if (mf.dbPrimaryKey_?) {
            indexMap = Full(mf.dbColumnName) // Full(v.getName.toLowerCase)
          }

        case _ =>
      }
    }

    def findPos(in: AnyRef): Box[Int] = {
      tArray.toList.zipWithIndex.filter(mft => in eq mft._1.field) match {
        case Nil => Empty
        case x :: xs => Full(x._2)
      }
    }

    val resArray = new ListBuffer[FieldHolder[A]];

    fieldOrder.foreach(f => findPos(f).foreach(pos => resArray += tArray.remove(pos)))

    tArray.foreach(mft => resArray += mft)

    mappedFieldList = resArray.toList
    mappedFieldList.foreach(ae => _mappedFields(ae.name) = ae.method)
  }

  val columnNamesForInsert = (mappedColumnInfo.filter(!_._2.dbPrimaryKey_?).map(_._1)).toList.mkString(",")

  val columnQueriesForInsert = {
    (mappedColumnInfo.filter(!_._2.dbPrimaryKey_?).map(p => "?")).toList.mkString(",")
  }

  private def fixTableName(name: String) = clean(name.toLowerCase) match {
    case name if DB.reservedWords.contains(name) => name+"_t"
    case name => name
  }

  private def internalTableName_$_$ =
  getClass.getSuperclass.getName.split("\\.").toList.last;

  /**
   * This function converts a header name into the appropriate
   * XHTML format for displaying across the headers of a
   * formatted block.  The default is <th> for use
   * in XHTML tables.  If you change this function, the change
   * will be used for this MetaMapper unless you override the
   * htmlHeades method
   */
  var displayNameToHeaderElement: String => NodeSeq =
  MapperRules.displayNameToHeaderElement

  def htmlHeaders: NodeSeq =
  mappedFieldList.filter(_.field.dbDisplay_?).
  flatMap(mft => displayNameToHeaderElement(mft.field.displayName))

  def mappedFields: Seq[BaseMappedField] = mappedFieldList.map(f => f.field)

  /**
   * This function converts an element into the appropriate
   * XHTML format for displaying across a line
   * formatted block.  The default is <td> for use
   * in XHTML tables.  If you change this function, the change
   * will be used for this MetaMapper unless you override the
   * doHtmlLine method.
   */
  var displayFieldAsLineElement: NodeSeq => NodeSeq =
  MapperRules.displayFieldAsLineElement


  def doHtmlLine(toLine: A): NodeSeq =
  mappedFieldList.filter(_.field.dbDisplay_?).
  flatMap(mft => displayFieldAsLineElement(??(mft.method, toLine).asHtml))

  def asJs(actual: A): JsExp = {
    JE.JsObj(("$lift_class", JE.Str(dbTableName)) :: mappedFieldList.
             map(f => ??(f.method, actual)).filter(_.renderJs_?).flatMap(_.asJs).toList :::
             actual.suplementalJs(Empty) :_*)
  }

  /**
   *
   */
  def asJSON(actual: A, sb: StringBuilder): StringBuilder = {
    sb.append('{')
    mappedFieldList.foreach{
      f =>
      sb.append(f.name)
      sb.append(':')
      ??(f.method, actual).is
      // FIXME finish JSON
    }
    sb.append('}')
    sb
  }

  def asHtml(toLine: A): NodeSeq =
  Text(internalTableName_$_$) :: Text("={ ") ::
  (for (mft <- mappedFieldList if mft.field.dbDisplay_? ;
        val field = ??(mft.method, toLine)) yield
   <span>{field.displayName}={field.asHtml} </span>
  ) :::List(Text(" }"))


  /**
   * This function converts a name and form for a given field in the
   * model to XHTML for presentation in the browser.  By
   * default, a table row ( <tr> ) is presented, but
   * you can change the function to display something else.
   */
  var formatFormElement: (NodeSeq, NodeSeq) => NodeSeq =
  MapperRules.formatFormElement

  def formatFormLine(displayName: NodeSeq, form: NodeSeq): NodeSeq =
  formatFormElement(displayName, form)

  def toForm(toMap: A): NodeSeq =
  mappedFieldList.map(e => ??(e.method, toMap)).
  filter(_.dbDisplay_?).flatMap (
    field =>
    field.toForm.toList.
    flatMap(form => formatFormLine(Text(field.displayName), form))
  )

  /**
   * Get the fields (in order) for displaying a form
   */
  def formFields(toMap: A): List[MappedField[_, A]] =
  mappedFieldList.map(e => ??(e.method, toMap)).filter(_.dbDisplay_?)


  /**
   * map the fields titles and forms to generate a list
   * @param func called with displayHtml, fieldId, form
   */
  def mapFieldTitleForm[T](toMap: A,
                           func: (NodeSeq, Box[NodeSeq], NodeSeq) => T): List[T] =
  formFields(toMap).flatMap(field => field.toForm.
                            map(fo => func(field.displayHtml, field.fieldId, fo)))


  /**
   * flat map the fields titles and forms to generate a list
   * @param func called with displayHtml, fieldId, form
   */
  def flatMapFieldTitleForm[T](toMap: A,
                               func: (NodeSeq, Box[NodeSeq], NodeSeq) => Seq[T]): List[T] =
  formFields(toMap).flatMap(field => field.toForm.toList.
                            flatMap(fo => func(field.displayHtml,
                                               field.fieldId, fo)))


  /**
   * Given the prototype field (the field on the Singleton), get the field from the instance
   * @param actual -- the Mapper instance
   * @param protoField -- the field from the MetaMapper (Singleton)
   *
   * @return the field from the actual object
   */
  def getActualField[T](actual: A, protoField: MappedField[T, A]): MappedField[T, A] =
  ??(_mappedFields(protoField.name), actual).asInstanceOf[MappedField[T,A]]


  /**
   * Given the prototype field (the field on the Singleton), get the field from the instance
   * @param actual -- the Mapper instance
   * @param protoField -- the field from the MetaMapper (Singleton)
   *
   * @return the field from the actual object
   */
  def getActualBaseField(actual: A, protoField: BaseOwnedMappedField[A]): BaseOwnedMappedField[A] =
  ??(_mappedFields(protoField.name), actual) // .asInstanceOf[MappedField[T,A]]

  /**
   * The name of the database table.  Override this method if you
   * want to change the table to something other than the name of the Mapper class
   */
  def dbTableName = _dbTableName

  private[mapper] lazy val _dbTableName = fixTableName(internalTableName_$_$)

  /*
   private val _dbTableName: String = {
   fixTableName(internalTableName_$_$)
   }
   */


  private def setupInstanceForPostCommit(inst: A) {
    if (!inst.addedPostCommit) {
      DB.appendPostFunc(inst.connectionIdentifier, () => afterCommit.foreach(_(inst)))
      inst.addedPostCommit = true
    }
  }

  private def eachField(what: A, toRun: List[(A) => Any])(f: (LifecycleCallbacks) => Any) {
    mappedCallbacks.foreach (e =>
      e._2.invoke(what) match {
        case lccb: LifecycleCallbacks => f(lccb)
        case _ =>
      })
    toRun.foreach{tf => tf(what)}
  }
  private def _beforeValidation(what: A) {setupInstanceForPostCommit(what); eachField(what, beforeValidation) {field => field.beforeValidation}  }
  private def _beforeValidationOnCreate(what: A) {eachField(what, beforeValidationOnCreate) {field => field.beforeValidationOnCreate}  }
  private def _beforeValidationOnUpdate(what: A) {eachField(what, beforeValidationOnUpdate) {field => field.beforeValidationOnUpdate}  }
  private def _afterValidation(what: A) { eachField(what, afterValidation) {field => field.afterValidation}  }
  private def _afterValidationOnCreate(what: A) {eachField(what, afterValidationOnCreate) {field => field.afterValidationOnCreate}  }
  private def _afterValidationOnUpdate(what: A) {eachField(what, afterValidationOnUpdate) {field => field.afterValidationOnUpdate}  }

  private def _beforeSave(what: A) {setupInstanceForPostCommit(what); eachField(what, beforeSave) {field => field.beforeSave}  }
  private def _beforeCreate(what: A) { eachField(what, beforeCreate) {field => field.beforeCreate}  }
  private def _beforeUpdate(what: A) { eachField(what, beforeUpdate) {field => field.beforeUpdate}  }

  private def _afterSave(what: A) {eachField(what, afterSave) {field => field.afterSave}  }
  private def _afterCreate(what: A) {eachField(what, afterCreate) {field => field.afterCreate}  }
  private def _afterUpdate(what: A) {eachField(what, afterUpdate) {field => field.afterUpdate}  }

  private def _beforeDelete(what: A) {setupInstanceForPostCommit(what); eachField(what, beforeDelete) {field => field.beforeDelete}  }
  private def _afterDelete(what: A) {eachField(what, afterDelete) {field => field.afterDelete}  }

  def beforeSchemifier {}
  def afterSchemifier {}

  def dbIndexes: List[Index[A]] = Nil

  implicit def fieldToItem[T](in: MappedField[T, A]): IndexItem[A] = IndexField(in)
  implicit def boundedFieldToItem(in: (MappedField[String, A], Int)): BoundedIndexField[A] = BoundedIndexField(in._1, in._2)

  // protected def getField(inst : Mapper[A], meth : Method) = meth.invoke(inst, null).asInstanceOf[MappedField[AnyRef,A]]
}

object OprEnum extends Enumeration {
  val Eql = Value(1, "=")
  val <> = Value(2, "<>")
  val >= = Value(3, ">=")
  val != = <>
  val <= = Value(4, "<=")
  val > = Value(5, ">")
  val < = Value(6, "<")
  val IsNull = Value(7, "IS NULL")
  val IsNotNull = Value(8, "IS NOT NULL")
  val Like = Value(9, "LIKE")
}


case class Index[A <: Mapper[A]](columns: IndexItem[A]*)

abstract class IndexItem[A <: Mapper[A]] {
  def field: BaseMappedField
  def indexDesc: String
}

case class IndexField[A <: Mapper[A], T](field: MappedField[T, A]) extends IndexItem[A] {
  def indexDesc: String = field.dbColumnName
}
case class BoundedIndexField[A <: Mapper[A]](field: MappedField[String, A], len: Int) extends IndexItem[A] {
  def indexDesc: String = field.dbColumnName+"("+len+")"
}

sealed trait QueryParam[O<:Mapper[O]]
case class Cmp[O<:Mapper[O], T](field: MappedField[T,O], opr: OprEnum.Value, value: Box[T], otherField: Box[MappedField[T, O]]) extends QueryParam[O]
case class OrderBy[O<:Mapper[O], T](field: MappedField[T,O],
                                    order: AscOrDesc) extends QueryParam[O]

trait AscOrDesc {
  def sql: String
}

case object Ascending extends AscOrDesc {
  def sql: String = " ASC "
}

case object Descending extends AscOrDesc {
  def sql: String = " DESC "
}

case class Distinct[O <: Mapper[O]]() extends QueryParam[O]

case class OrderBySql[O <: Mapper[O]](sql: String,
                                     checkedBy: IHaveValidatedThisSQL) extends QueryParam[O]

case class ByList[O<:Mapper[O], T](field: MappedField[T,O], vals: List[T]) extends QueryParam[O]
case class BySql[O<:Mapper[O]](query: String,
                                     checkedBy: IHaveValidatedThisSQL,
                                     params: Any*) extends QueryParam[O]
case class MaxRows[O<:Mapper[O]](max: Long) extends QueryParam[O]
case class StartAt[O<:Mapper[O]](start: Long) extends QueryParam[O]
case class Ignore[O <: Mapper[O]]() extends QueryParam[O]

abstract class InThing[OuterType <: Mapper[OuterType]] extends QueryParam[OuterType] {
  type JoinType
  type InnerType <: Mapper[InnerType]

  def outerField: MappedField[JoinType, OuterType]
  def innerField: MappedField[JoinType, InnerType]
  def innerMeta: MetaMapper[InnerType]
  def queryParams: List[QueryParam[InnerType]]

  def distinct: String =
  queryParams.filter{case Distinct() => true case _ => false} match {
    case Nil => ""
      case _ => " DISTINCT "
  }
}

case class PreCache[TheType <: Mapper[TheType]](field: MappedForeignKey[_, TheType, _])
extends QueryParam[TheType]

case class InRaw[TheType <:
                 Mapper[TheType], T](field: MappedField[T, TheType],
                                     rawSql: String,
                                     checkedBy: IHaveValidatedThisSQL)
extends QueryParam[TheType]

object In {
  def fk[InnerMapper <: Mapper[InnerMapper], JoinTypeA,
         Zoom <% QueryParam[InnerMapper],
         OuterMapper <:KeyedMapper[JoinTypeA, OuterMapper]]
  (fielda: MappedForeignKey[JoinTypeA, InnerMapper, OuterMapper],
   qp: Zoom*): InThing[OuterMapper]
  = {
    new InThing[OuterMapper] {
      type JoinType = JoinTypeA
      type InnerType = InnerMapper

      val outerField: MappedField[JoinType, OuterMapper] = fielda.dbKeyToTable.primaryKeyField
      val innerField: MappedField[JoinType, InnerMapper] = fielda
      val innerMeta: MetaMapper[InnerMapper] = fielda.fieldOwner.getSingleton

      val queryParams: List[QueryParam[InnerMapper]] =
      qp.map{v => val r: QueryParam[InnerMapper] = v; r}.toList
    }
  }

  def apply[InnerMapper <: Mapper[InnerMapper], JoinTypeA,
            Zoom <% QueryParam[InnerMapper],
            OuterMapper <: Mapper[OuterMapper]]
  (outerField: MappedField[JoinTypeA, OuterMapper],
   innerField: MappedField[JoinTypeA, InnerMapper],
   qp: Zoom*): InThing[OuterMapper]
  = {
    new InThing[OuterMapper] {
      type JoinType = JoinTypeA
      type InnerType = InnerMapper

      val outerField: MappedField[JoinType, OuterMapper] = outerField
      val innerField: MappedField[JoinType, InnerMapper] = innerField
      val innerMeta: MetaMapper[InnerMapper] = innerField.fieldOwner.getSingleton

      val queryParams: List[QueryParam[InnerMapper]] = {

        qp.map{v => val r: QueryParam[InnerMapper] = v; r}.toList
      }
    }
  }


}

object Like {
  def apply[O <: Mapper[O]](field: MappedField[String, O], value: String) =
  Cmp[O, String](field, OprEnum.Like, Full(value), Empty)
}

object By {
  import OprEnum._

  def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, Eql, Full(value), Empty)
  def apply[O <: Mapper[O],T,  Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Q) =
  Cmp[O,T](field, Eql, Full(value.primaryKeyField.is), Empty)

  def apply[O <: Mapper[O],T, Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Box[Q]) =
  value match {
    case Full(v) => Cmp[O,T](field, Eql, Full(v.primaryKeyField.is), Empty)
    case _ => Cmp(field, IsNull, Empty, Empty)
  }
}

object NotBy {
  import OprEnum._

  def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, <>, Full(value), Empty)
  def apply[O <: Mapper[O],T,  Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Q) =
  Cmp[O,T](field, <>, Full(value.primaryKeyField.is), Empty)
  def apply[O <: Mapper[O],T, Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Box[Q]) =
  value match {
    case Full(v) => Cmp[O,T](field, <>, Full(v.primaryKeyField.is), Empty)
    case _ => Cmp(field, IsNotNull, Empty, Empty)
  }
}

object ByRef {
  import OprEnum._

  def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, Eql, Empty, Full(otherField))
}

object NotByRef {
  import OprEnum._

  def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, <>, Empty, Full(otherField))
}

object By_> {
  import OprEnum._

  def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, >, Full(value), Empty)
  def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, >, Empty, Full(otherField))
}

object By_< {
  import OprEnum._

  def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, <, Full(value), Empty)
  def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, <, Empty, Full(otherField))
}

object NullRef {
  import OprEnum._
  def apply[O <: Mapper[O], T](field: MappedField[T, O]) = Cmp(field, IsNull, Empty, Empty)
}

object NotNullRef {
  import OprEnum._
  def apply[O <: Mapper[O], T](field: MappedField[T, O]) = Cmp(field, IsNotNull, Empty, Empty)
}

trait LongKeyedMetaMapper[A <: LongKeyedMapper[A]] extends KeyedMetaMapper[Long, A] { self: A => }


trait KeyedMetaMapper[Type, A<:KeyedMapper[Type, A]] extends MetaMapper[A] with KeyedMapper[Type, A] {
  self: A  with MetaMapper[A] with KeyedMapper[Type, A] =>

  private def testProdArity(prod: Product): Boolean = {
    var pos = 0
    while (pos < prod.productArity) {
      if (!prod.productElement(pos).isInstanceOf[QueryParam[A]]) return false
      pos = pos + 1
    }
    true
  }

  type Q = MappedForeignKey[AnyBound, A, OO] with MappedField[AnyBound, A] forSome
  {type OO <: KeyedMapper[AnyBound, OO]}

  def asSafeJs(actual: A, f: KeyObfuscator): JsExp = {
    val pk = actual.primaryKeyField
    val first = (pk.name, JE.Str(f.obscure(self, pk.is)))
    JE.JsObj(first :: ("$lift_class", JE.Str(dbTableName)) :: mappedFieldList.
             map(f => this.??(f.method, actual)).
             filter(f => !f.dbPrimaryKey_? && f.renderJs_?).flatMap{
        case fk:  Q =>
          val key = f.obscure(fk.dbKeyToTable, fk.is)
          List((fk.name, JE.Str(key)),
               (fk.name+"_obj",
                JE.AnonFunc("index", JE.JsRaw("return index["+key.encJs+"];").cmd)))

        case x => x.asJs}.toList :::
             actual.suplementalJs(Full(f)) :_*)
  }

  private def convertToQPList(prod: Product): Array[QueryParam[A]] = {
    var pos = 0
    val ret = new Array[QueryParam[A]](prod.productArity)
    while (pos < prod.productArity) {
      ret(pos) = prod.productElement(pos).asInstanceOf[QueryParam[A]]
      pos = pos + 1
    }
    ret
  }

  private def anyToFindString(in: Any): Box[String] =
  in match {
    case Empty | None | null | Failure(_, _, _) => Empty
    case Full(n) => anyToFindString(n)
    case Some(n) => anyToFindString(n)
    case v => Full(v.toString)
  }

  def find(key: Any): Box[A] =
  key match {
    case qp: QueryParam[A] => find(List(qp.asInstanceOf[QueryParam[A]]) :_*)
    case prod: Product if (testProdArity(prod)) => find(convertToQPList(prod) :_*)
    case key => anyToFindString(key) flatMap (find(_))
  }

  def findDb(dbId: ConnectionIdentifier, key: Any): Box[A] =
  key match {
    case qp: QueryParam[A] => findDb(dbId, List(qp.asInstanceOf[QueryParam[A]]) :_*)
    case prod: Product if (testProdArity(prod)) => findDb(dbId, convertToQPList(prod) :_*)
    case key => anyToFindString(key) flatMap (find(dbId, _))
  }

  def find(key: String): Box[A] = dbStringToKey(key) flatMap (realKey => findDbByKey(selectDbForKey(realKey), realKey))

  def find(dbId: ConnectionIdentifier, key: String): Box[A] =  dbStringToKey(key) flatMap (realKey =>  findDbByKey(dbId, realKey))

  def findByKey(key: Type): Box[A] = findDbByKey(selectDbForKey(key), key)

  def dbStringToKey(in: String): Box[Type] = primaryKeyField.convertKey(in)

  private def selectDbForKey(key: Type): ConnectionIdentifier =
  if (dbSelectDBConnectionForFind.isDefinedAt(key)) dbSelectDBConnectionForFind(key)
  else dbDefaultConnectionIdentifier

  def dbSelectDBConnectionForFind: PartialFunction[Type, ConnectionIdentifier] = Map.empty

  def findDbByKey(dbId: ConnectionIdentifier, key: Type): Box[A] =
  findDbByKey(dbId, mappedFields, key)

  def findDbByKey(dbId: ConnectionIdentifier, fields: Seq[SelectableField],
                  key: Type): Box[A] =
  DB.use(dbId) { conn =>
    val field = primaryKeyField

    DB.prepareStatement("SELECT "+
                        fields.map(_.dbSelectString).
                        mkString(", ")+
                        " FROM "+dbTableName+" WHERE "+field.dbColumnName+" = ?", conn) {
      st =>
      st.setObject(1, field.makeKeyJDBCFriendly(key), field.targetSQLType(field.dbColumnName))
      DB.exec(st) {
        rs =>
        val mi = buildMapper(rs)
        if (rs.next) Full(createInstance(dbId, rs, mi._1, mi._2))
        else Empty
      }
    }
  }

  def find(by: QueryParam[A]*): Box[A] =
    findDb(dbDefaultConnectionIdentifier, by :_*)

  def findDb(dbId: ConnectionIdentifier, by: QueryParam[A]*): Box[A] =
  findDb(dbId, mappedFields, by :_*)

  def findDb(dbId: ConnectionIdentifier, fields: Seq[SelectableField],
             by: QueryParam[A]*): Box[A] = {
    DB.use(dbId) {
      conn =>
      val bl = by.toList
      val (query, start, max) = addEndStuffs(addFields("SELECT "+
                                                       fields.map(_.dbSelectString).
                                                       mkString(", ")+
                                                       " FROM "+dbTableName+" ",false,  bl), bl, conn)
      DB.prepareStatement(query, conn) {
        st =>
        setStatementFields(st, bl, 1)
        DB.exec(st) {
          rs =>
          val mi = buildMapper(rs)
          if (rs.next) Full(createInstance(dbId, rs, mi._1, mi._2))
          else Empty
        }

      }
    }
  }

  override def afterSchemifier {
    if (crudSnippets_?) {
      LiftRules.snippets.append(crudSnippets)
    }
  }

  /**
   * Override this definition in your model to enable CRUD snippets
   * for that model. Set to false by default.
   *
   * Remember to override editSnippetSetup and viewSnippetSetup as well,
   * as the defaults are broken.
   *
   * @return false
   */
  def crudSnippets_? = false

  /**
   * Defines the default CRUD snippets. Override if you want to change
   * the names of the snippets. Defaults are "add", "edit", and "view".
   *
   * (No, there's no D in CRUD.)
   */
  def crudSnippets: LiftRules.SnippetPF = {
    val Name = _dbTableName

    NamedPF("crud "+Name) {
      case Name :: "add"  :: _ => addSnippet
      case Name :: "edit" :: _ => editSnippet
      case Name :: "view" :: _ => viewSnippet
    }
  }

  /**
   * Default snippet for modification. Used by the default add and edit snippets.
   */
  def modSnippet(xhtml: NodeSeq, obj: A, cleanup: (A => Unit)): NodeSeq = {
    val name = _dbTableName

    def callback() {
      cleanup(obj)
    }

    xbind(name, xhtml)(obj.fieldPF orElse obj.fieldMapperPF(_.toForm.openOr(Text(""))) orElse {
        case "submit" => label => SHtml.submit(label.text, callback _)
      })
  }

  /**
   * Default add snippet. Override to change behavior of the add snippet.
   */
  def addSnippet(xhtml: NodeSeq): NodeSeq = {
    modSnippet(xhtml, addSnippetSetup, addSnippetCallback _)
  }

  /**
   * Default edit snippet. Override to change behavior of the edit snippet.
   */
  def editSnippet(xhtml: NodeSeq): NodeSeq = {
    modSnippet(xhtml, editSnippetSetup, editSnippetCallback _)
  }

  /**
   * Default view snippet. Override to change behavior of the view snippet.
   */
  def viewSnippet(xhtml: NodeSeq): NodeSeq = {
    val Name = _dbTableName
    val obj: A = viewSnippetSetup

    xbind(Name, xhtml)(obj.fieldPF orElse obj.fieldMapperPF(_.asHtml))
  }

  /**
   * Lame attempt at automatically getting an object from the HTTP parameters.
   * BROKEN! DO NOT USE! Only here so that existing sub-classes KeyedMetaMapper
   * don't have to implement new methods when I commit the CRUD snippets code.
   */
  def objFromIndexedParam: Box[A] = {
    val found = for (
      req <- S.request.toList;
      (param, value :: _) <- req.params;
      fh <- mappedFieldList if fh.field.dbIndexed_? == true && fh.name.equals(param)
    ) yield find(value)

    found.filter(obj => obj match {
        case Full(obj) => true
        case _         => false
      }) match {
      case obj :: _ => obj
      case _        => Empty
    }
  }

  /**
   * Default setup behavior for the add snippet. Creates a new mapped object.
   *
   * @return new mapped object
   */
  def addSnippetSetup: A = {
    this.create
  }

  /**
   * Default setup behavior for the edit snippet. BROKEN! MUST OVERRIDE IF
   * USING CRUD SNIPPETS!
   *
   * @return a mapped object of this metamapper's type
   */
  def editSnippetSetup: A = {
    objFromIndexedParam.open_!
  }
  /**
   * Default setup behavior for the view snippet. BROKEN! MUST OVERRIDE IF
   * USING CRUD SNIPPETS!
   *
   * @return a mapped object of this metamapper's type
   */
  def viewSnippetSetup: A = {
    objFromIndexedParam.open_!
  }
  /**
   * Default callback behavior of the edit snippet. Called when the user
   * presses submit. Saves the passed in object.
   *
   * @param obj mapped object of this metamapper's type
   */
  def editSnippetCallback(obj: A) { obj.save }
  /**
   * Default callback behavior of the add snippet. Called when the user
   * presses submit. Saves the passed in object.
   *
   * @param obj mapped object of this metamapper's type
   */
  def addSnippetCallback(obj: A) { obj.save }
}

case class FieldHolder[T](name: String, method: Method, field: MappedField[_, T])

class KeyObfuscator {
  var to: Map[String, Map[Any, String]] = Map.empty
  var from: Map[String, Map[String, Any]] = Map.empty

  def obscure[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](theType:
                                                                   KeyedMetaMapper[KeyType, MetaType], key: KeyType): String = synchronized {
    val local: Map[Any, String] = to.getOrElse(theType.dbTableName, Map.empty)
    local.get(key) match {
      case Some(s) => s
      case _ => val ret = "r"+randomString(15)

        val l2: Map[Any, String] = local + ( (key -> ret) )
        to = to + ( (theType.dbTableName -> l2) )

        val lf: Map[String, Any] = from.getOrElse(theType.dbTableName, Map.empty) + ( (ret -> key))
        // lf(ret) = key
        from = from + ( (theType.dbTableName -> lf) )

        ret
    }
  }

  def obscure[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](what: KeyedMapper[KeyType, MetaType]): String =
  {
    obscure(what.getSingleton, what.primaryKeyField.is)
  }

  def apply[KeyType, MetaType <: KeyedMapper[KeyType, MetaType], Q <% KeyType](theType:
                                                                               KeyedMetaMapper[KeyType, MetaType], key: Q): String = {
    val k: KeyType = key
    obscure(theType, k)
  }

  def apply[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](what: KeyedMapper[KeyType, MetaType]): String =
  {
    obscure(what)
  }

  def recover[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](theType:
                                                                   KeyedMetaMapper[KeyType, MetaType], id: String): Box[KeyType] = synchronized {
    Box(from.get(theType.dbTableName)).flatMap(h => Box(h.get(id)).map(_.asInstanceOf[KeyType]))
  }
}

case class IHaveValidatedThisSQL(who: String, date: String)

trait SelectableField {
  def dbSelectString: String
}