'use strict'

var expireOpts = new Map()
var defaultTimeout = 2000
var responseKeys = [ 'header', 'body' ]

module.exports = function(routes, opts) {
  if (opts.debug) console.info('cache options:', routes, opts.debug)

  var store = new Store(opts)
  expireOpts.set('test2', 1000)
  store.set('test2', '12413531', 1000).then(function() {
    store.has('test2').then(function(a) {
      console.log(a)
    })
  })

  setTimeout(function() {
    store.has('test2').then(function(a) {
      console.log(a)
    })
  }, 2500)

  return function *(next) {
    try {
      // check if no-cache is provided
      if (this.request.header['cache-control'] == 'no-cache') {
        return yield next
      }

      // check if HTTP methods other than GET are sent
      if (this.request.method != 'GET') {
        return yield next
      }

      let _url = this.request.url

      // return cached response
      if (store.has(_url)) {
        // let item = store.get(_url).get('data')
        let item = store.get(_url)

        if (opts.debug) console.info('returning from cache', _url)

        for (let key of item.keys()) {
          if (key == 'header') {
            let value = item.get(key)

            for (let hkey in value) {
              this.set(hkey, value[hkey])
            }

            continue
          }

          this[key] = item.get(key)
        }

        return
      }

      // check if route is permitted to be cached
      for (let route in routes) {
        if (this.request.path.indexOf(route) != -1) {
          let routeExpire = routes[route]

          if (routeExpire == false) {
            return yield next
          }

          if (isNaN(routeExpire) && (typeof routeExpire != Boolean)) {
            if (opts.debug) console.warn('invalid cache setting:', routeExpire)
            return yield next
          }

          // override default timeout
          expireOpts.set(this.request.path, routeExpire)
        }
      }

      // call next middleware and cache response on return
      yield next

      let _response = new Map()

      for (let key in this.response) {
        if (responseKeys.indexOf(key) != -1)
          _response.set(key, this.response[key])
      }

      /*
      let entry = new Map()
      entry.set('data', _response)
      entry.set('timeout', setTimeout(function() {
        if (opts.debug) console.info('deleting from cache', _url)

        // delete caching entry from cache on expiration
        if (cache.has(_url)) cache.delete(_url, entry)
      }, function() {
        if (expireOpts.has(_url)) return expireOpts.get(_url)
        return defaultTimeout
      }()))
      */

      if (opts.debug) console.info('caching', _url)

      // set new caching entry
      store.set(_url, _response)
    }
    catch (error) {
      if (opts.debug) console.error(error)
      this.throw(error)
    }
  }
}

function Store(opts) {
  let type = opts.external ? opts.external.type : 'memory'
  let Driver = drivers[type]

  this.store = new Driver(opts.external || null)
}

Store.prototype.get = function(key) {
  // returns a new Promise
  return this.store.get(key)
}

Store.prototype.has = function(key) {
  // returns a new Promise
  return this.store.has(key)
}

Store.prototype.set = function(key, value, timeout) {
  // returns a new Promise
  return this.store.set(key, value, timeout)
}

function Memory() {
  let cache = new Map()
  let routeExpire = new Map()

  this.get = function(key) {
    return Promise.resolve(cache.get(key))
  }

  this.has = function(key) {
    return Promise.resolve(cache.has(key))
  }

  this.set = function(key, value, timeout) {
    routeExpire.set(key, setTimeout(function() {
      if (opts.debug) console.info('deleting from cache', key)

      // delete caching entry from cache on expiration
      if (cache.has(key)) cache.delete(key, entry)
    }, function() {
      if (expireOpts.has(key)) return expireOpts.get(key)
      return defaultTimeout
    }()))

    return Promise.resolve(cache.set(key, value))
  }
}

function Redis(opts) {
  let conn = new (require('ioredis'))(opts.port || '127.0.0.1', opts.host || 6379)

  this.get = function(key) {
    return conn.get(key)
  }

  this.has = function(key) {
    return conn.exists(key).then(function(check) {
      if (check > 0) return Promise.resolve(true)
      else return Promise.resolve(false)
    })
  }

  this.set = function(key, value, timeout) {
    return conn.set(key, value).then(function() {
      return conn.expire(key, function() {
        if (expireOpts.has(key)) return expireOpts.get(key) / 1000
        return defaultTimeout
      }())
    })
  }
}

var drivers = {
  'redis': Redis,
  'memory': Memory
}

@@ -246,280 +246,8 @@
 pts)
-%0A  expireOpts.set('test2', 1000)%0A  store.set('test2', '12413531', 1000).then(function() %7B%0A    store.has('test2').then(function(a) %7B%0A      console.log(a)%0A    %7D)%0A  %7D)%0A%0A  setTimeout(function() %7B%0A    store.has('test2').then(function(a) %7B%0A      console.log(a)%0A    %7D)%0A  %7D, 2500)
 %0A%0A  
@@ -629,27 +629,60 @@
     
-if (store.has(_url)
+let exists = yield store.has(_url)%0A%0A      if (exists
 ) %7B%0A
@@ -688,19 +688,16 @@
 %0A       
- //
  let ite
@@ -691,32 +691,38 @@
       let item =
+ yield
  store.get(_url)
@@ -725,20 +725,8 @@
 url)
-.get('data')
 %0A   
@@ -734,36 +734,32 @@
     
-let 
 item = 
-store.get(_url
+JSON.parse(item
 )%0A
-%0A
     
@@ -804,32 +804,40 @@
 rning from cache
+ for url
 ', _url)%0A%0A      
@@ -855,22 +855,15 @@
 key 
-of
+in
  item
-.keys()
 ) %7B%0A
@@ -923,25 +923,21 @@
 e = item
-.get(key)
+%5Bkey%5D
 %0A%0A      
@@ -1085,25 +1085,21 @@
 %5D = item
-.get(key)
+%5Bkey%5D
 %0A       
@@ -1795,27 +1795,30 @@
 ponse = new 
-Map
+Object
 ()%0A%0A      fo
@@ -1912,25 +1912,23 @@
 response
-.set(key,
+%5Bkey%5D =
  this.re
@@ -1934,25 +1934,24 @@
 esponse%5Bkey%5D
-)
 %0A      %7D%0A%0A  
@@ -4182,22 +4182,38 @@
 et(key, 
+JSON.stringify(
 value)
+)
 .then(fu

switch to a pure Object store instead of Map because of redis values stored as strings