LogConfig = (require '../log/LogConfig.coffee').LogConfig
Log = (require '../log/Log.coffee').Log
Store = (require './Store.coffee').Store
Transaction = (require './Transaction.coffee').Transaction
IOUtil = (require '../util/IOUtil.coffee').IOUtil
EventEmitter = (require 'events').EventEmitter
PhysicalLayerError =
  (require '../errors/PhysicalLayerError.coffee').PhysicalLayerError
ExodusError = (require '../errors/ExodusError.coffee').ExodusError

# This class provides common access to data. Every Store is placed in its own
# subdir and has its own log.
class Environment extends EventEmitter
  # @private
  ec: undefined
  # @private
  openStores: undefined
  # @private
  config: undefined
  # @private
  dir: undefined
  # @private
  txns: undefined

  # Constructor.
  #
  # @param config log configuration
  # @param ec environment configuration
  @create$LogConfig$EnvironmentConfig: (config, ec, o) ->
    if !o? then o = new Environment
    o.config = config
    o.dir = config.getDir()
    o.ec = ec
    o.openStores = new Array()
    o.txns = new Array()
    return o

  # Constructor.
  #
  # @param dir
  # @param ec environment configuration
  @create$String$EnvironmentConfig: (dir, ec, o) ->
    if !o? then o = new Environment
    config = LogConfig.create()
    config.setDir dir
    o = Environment.create$LogConfig$EnvironmentConfig config, ec, o
    return o

  getLocation: () ->
    return @dir

  getLog$String: (name) ->
    @config.setDir @dir + name + '/'
    @config.setFileSize(@ec.getLogFileSize())
    @config.setLockTimeout(@ec.getLogLockTimeout())
    @config.setCachePageSize(@ec.getLogCachePageSize())
    @config.setCacheOpenFilesCount(@ec.getLogCacheOpenFilesCount())
    @config.setDurableWrite(@ec.getLogDurableWrite())
    @config.setSharedCache(@ec.isLogCacheShared())
    @config.setNonBlockingCache(@ec.isLogCacheNonBlocking())
    @config.setWriteBufferSize(@ec.getLogCacheWriteBufferSize())
    log = Log.create$LogConfig @config
    return log

  openStore$String$StoreConfiguration$emit: (name, config) ->
    if @txns.length == 0
      txn = @beginTransaction()
    else
      txn = @txns[@txns.length - 1]
    return @openStore$String$StoreConfiguration$TransactionDescriptor$emit name,
            config, txn

  openStore$String$StoreConfiguration$TransactionDescriptor$emit: (name, config, txn) ->
    Store.EMITTER.once 'create', (store) =>
      txn.addStore$Store store
      @openStores.push store
      @emit 'openStore', store
    Store.create$Environment$String$int$StoreConfiguration this,
            name, 1, config
    return undefined

  # @private
  getAndCheckAutoCommit$Transaction: (txn) ->
    res = new Object()
    if txn?
      res.txn = txn
      res.commit = false
    else
      res.txn = @beginTransaction()
      res.commit = true
    return res

  abortTransaction$Transaction$emit: (txn) ->
    @txns.pop()
    @emit 'abortTransaction'

  commitTransaction$Transaction$emit: (txn) ->
    removeStores = () =>
      if txn.removedStores.length != 0
        dir = @config.getDir()
        if dir[dir.length - 1] != '/' then dir += '/'
        for i in [0..txn.removedStores.length - 1]
          path = dir + txn.removedStores[i]
          IOUtil.removeDirectory$String path
    finish = () =>
      removeStores()
      @txns.pop()
      @emit 'commitTransaction'
    n = 0
    if txn.getStores().length != 0
      for i in [0..txn.getStores().length - 1]
        txn.getStores()[i].on 'close', () =>
          n += 1
          if n == txn.getStores().length
            finish()
        txn.getStores()[i].close$emit()
    else
      finish()

  beginTransaction: () ->
    txn = Transaction.create$Environment$Boolean this, false
    if txn == null
      throw new PhysicalLayerError "This exception should never be thrown"
    @txns.push txn
    return txn

  # Exclusiveness is unundestandable feature.
  beginExclusiveTransaction: () ->
    txn = Transaction.create$Environment$Boolean this, true
    if txn == null
      throw new PhysicalLayerError "This exception should never be thrown"
    @txns.push txn
    return txn

  getCurrentTransaction: () ->
    if @txns.length != 0
      return @txns[@txns.length - 1]
    else
      return undefined

  # Remove all stores.
  clear: () ->
    dir = @config.getDir()
    IOUtil.removeDirectory$String dir
    IOUtil.makeDirectory$String dir
    @openStores = new Object()

  # Close all stores.
  close$emit: () ->
    if @txns.length != 0
      throw new ExodusError "Finish all transactions before closing database environment"
    n = 0
    for i in [0..@openStores.length - 1]
      @openStores[i].on 'close', () ->
        n += 1
        if n == @openStores.length
          @openStores = new Object()
          @emit 'close'
      @openStores[i].close$emit()

  # TODO File backup(String backupRootName, BlobVault blobVault) throws IOException

  truncateStore$String$Transaction$emit: (storeName, transaction) ->
    res = @getAndCheckAutoCommit$Transaction transaction
    txn = res.txn
    k = -1
    for i in [0..@openStores.length - 1]
      if @openStores[i].getName() == storeName
        k = i
    if k != -1
      Store.EMITTER.once 'create', (store) =>
        txn.addStore$Store store
        @openStores.splice k, 1
        @openStores.push store
        if res.commit
          txn.once 'commit', () ->
            @emit 'truncateStore'
          txn.commit$emit()
        else
          @emit 'truncateStore'
      Store.create$Environment$String$int$StoreConfiguration this,
              storeName, 1, @openStores[k].getConfig()
    else
      @emit 'truncateStore'

  removeStore$String$Transaction$emit: (storeName, transaction) ->
    res = @getAndCheckAutoCommit$Transaction transaction
    txn = res.txn
    txn.removeStore$String storeName
    if res.commit
      txn.once 'commit', () ->
        @emit 'removeStore'
      txn.commit$emit()
    else
      @emit 'removeStore'

  getAllStoreNames: () ->
    names = new Array()
    for i in [0..@openStores.length - 1]
      names.push @openStores[i].getName()
    return names

  # Get size of all the environment.
  #
  # @return size of env in bytes.
  getDiskUsage: () ->
    dir = @config.getDir()
    return IOUtil.getDirectorySize$String dir


exports.Environment = Environment
