class BootStrap
{

    def sessionFactory // used by isDirty().
    def grailsApplication // used by isDirty().
    def createDataService

    def init = { servletContext ->

        /** Add isDirty() to domainClasses.
        * @todo remove after upgrading to Grails-1.3 >.
        * See: http://grails.1312388.n4.nabble.com/beforeUpdate-Compare-with-previous-set-value-isDirty-not-working-in-Events-td1695661.html
        */
        addDirtyCheckMethods()

        /** Environment specific settings.
        * Note that (circa grails-1.2.2) if running "test test-app integration:" from "grails interactive"
        * the correct environment is applied for DataSource and Config,
        * but for BootStrap (and others) the development environment is applied.
        */
        environments {
            development {
                log.info "Starting DEVELOPMENT bootstrap."
                createDataService.ensureSystemAndAdminAccess()
                createDataService.createBaseData()
                createDataService.createDemoData()
                createDataService.startSearchableIndex()
            }
            test {
                log.info "Starting TEST bootstrap."
                createDataService.ensureSystemAndAdminAccess()
                createDataService.createBaseData()
                createDataService.startSearchableIndex(false)
            }
            production {
                log.info "Starting PRODUCTION bootstrap."
                createDataService.ensureSystemAndAdminAccess()
                createDataService.createBaseData()
                createDataService.startSearchableIndex()
            }
        }

    } // init

    def destroy = {
    }

   private void addDirtyCheckMethods() {
      for (dc in grailsApplication.domainClasses) {
         addDirtyCheckMethods dc.clazz.metaClass
      }
   }

    private void addDirtyCheckMethods(metaClass) {
      metaClass.isDirty = { ->
         def session = sessionFactory.currentSession
         def entry = findEntityEntry(delegate, session)
         if (!entry) {
            return false
         }

         Object[] values = entry.persister.getPropertyValues(delegate, session.entityMode)
         def dirtyProperties = entry.persister.findDirty(values, entry.loadedState, delegate, session)
         return dirtyProperties != null
      }

      metaClass.isDirty = { String fieldName ->
         def session = sessionFactory.currentSession
         def entry = findEntityEntry(delegate, session)
         if (!entry) {
            return false
         }

         Object[] values = entry.persister.getPropertyValues(delegate, session.entityMode)
         int[] dirtyProperties = entry.persister.findDirty(values, entry.loadedState, delegate, session)
         int fieldIndex = entry.persister.propertyNames.findIndexOf { fieldName == it }
         return fieldIndex in dirtyProperties
      }

      metaClass.getDirtyPropertyNames = { ->
         def session = sessionFactory.currentSession
         def entry = findEntityEntry(delegate, session)
         if (!entry) {
            return []
         }

         Object[] values = entry.persister.getPropertyValues(delegate, session.entityMode)
         int[] dirtyProperties = entry.persister.findDirty(values, entry.loadedState, delegate, session)
         def names = []
         for (index in dirtyProperties) {
            names << entry.persister.propertyNames[index]
         }
         names
      }

      metaClass.getPersistentValue = { String fieldName ->
         def session = sessionFactory.currentSession
         def entry = findEntityEntry(delegate, session, false)
         if (!entry) {
            return null
         }

         int fieldIndex = entry.persister.propertyNames.findIndexOf { fieldName == it }
         return fieldIndex == -1 ? null : entry.loadedState[fieldIndex]
      }
   }

   private findEntityEntry(instance, session, boolean forDirtyCheck = true) {
      def entry = session.persistenceContext.getEntry(instance)
      if (!entry) {
         return null
      }

      if (forDirtyCheck && !entry.requiresDirtyCheck(instance) && entry.loadedState) {
         return null
      }

      entry
   }

} // end class
