<?php
  if (!defined('MEMCACHED_HOST')) {
    define('MEMCACHED_HOST', '127.0.0.1');
  } // if
  
  if (!defined('MEMCACHED_PORT')) {
    define('MEMCACHED_PORT', 11211);
  } // if
  
  if (!defined('MEMCACHED_SERVER')) {
    $domain = parse_url(ROOT_URL);
    define('MEMCACHED_SERVER', $domain['host']);
  } // if

  /**
   * Memcached cache backend
   *
   * This backend saves cache data using memcached platform
   * 
   * @package angie.library.cache
   * @subpackage backend
   */
  class MemcachedCacheBackend extends CacheBackend {
    /**
     * Memcache class
     *
     * @var Memcache
     */
    var $memcache = null;
           
    /**
     * key that describes location of list of caches
     *
     * @var string
     */
    var $stored_caches_key = '_stored_caches';
    
    /**
     * List of existing caches
     *
     * @var array
     */
    var $stored_caches = array();
    
    /**
     * Storage server name
     *
     * @var string
     */
    var $storage_server = null;
    
    /**
     * Internal cache - why not bypass memcache server if we already have that value in memory
     *
     * @var array
     */
    var $data = array();
    
    /**
     * To use Memcached or Memcache extension
     *
     * @var boolean
     */
    var $use_memcached = false;
  
    /**
     * Constructor
     *
     * @param array $params
     * @return MemcachedCacheBackend
     */
    function __construct($params = null) {
      parent::__construct($params);
      
      $this->use_memcached = extension_loaded('memcached');
      
      if ($this->use_memcached) {
        $this->memcache = new Memcached();
        $connect = $this->memcache->addServer(MEMCACHED_HOST, MEMCACHED_PORT);
        if (!$connect) {
          new Error(lang('Could not connect to MEMCACHE server at :server::port. Error: :error', array('server' => MEMCACHED_HOST, 'port' => MEMCACHED_PORT, 'error' => $this->memcache->getResultMessage())));
        } // if
        $this->memcache->setOption(Memcached::OPT_NO_BLOCK, true);
      } else {
        $this->memcache = new Memcache();
        $connect = $this->memcache->pconnect(MEMCACHED_HOST, MEMCACHED_PORT);
        if (!$connect) {
          new Error(lang('Could not connect to MEMCACHE server at :server::port', array('server' => MEMCACHED_HOST, 'port' => MEMCACHED_PORT)));
        } // if
      } // if
      
      $this->storage_server = MEMCACHED_SERVER;      
      $this->stored_caches = $this->get($this->stored_caches_key);      
    } // __construct
    
    /**
     * Get value for a given variable from cache and return it
     *
     * @param string $name
     * @return mixed
     */
    function get($name) {
      if (isset($this->data[$name])) {
        return $this->data[$name];
      } else {
        
        if ($this->use_memcached) {
          // use memcached extension
          $result = $this->memcache->getByKey($this->storage_server, $name);
          if ($result === false) {
            if ($this->memcache->getResultCode() != Memcached::RES_NOTFOUND) {
              throw new Error(lang('Could not retrieve cache \':cache_name\'. Error: :error_message', array('cache_name' => $name, 'error_message' => $this->memcache->getResultMessage())));
              return false;            
            } // if
          } //if
        } else {
          // use memcache extension
          $result = $this->memcache->get($this->prepareCacheName($name));
        } // if
        
        if (!in_array($name, $this->stored_caches)) {
          $this->stored_caches[] = $name;
          $this->saveCachesList();
        } // if
        
        $this->data[$name] = $result;
        return $result;
      } // if
    } // get
    
    /**
     * Set value for a given variable
     * 
     * @param string $name
     * @param mixed $value
     * @return null
     */
    function set($name, $value) {
      $existing_value = $this->get($name);
      
      if ($existing_value != $value) {
        if ($this->use_memcached) {
          // use memcached extension
          $result = $this->memcache->setByKey($this->storage_server, $name, $value, $this->lifetime);  
          if ($result === false) {
            throw new Error(lang('Could not set cache \':cache_name\'. Error: :error_message', array('cache_name' => $name, 'error_message' => $this->memcache->getResultMessage())));
            return false;
          } // if
        } else {
          // use memcache extension
          $result = $this->memcache->set($this->prepareCacheName($name), $value, null, $this->lifetime);
          if (!$result) {
            throw new Error(lang('Could not set cache \':cache_name\'', array('cache_name' => $name)));
            return false;            
          }
        } // if
        
        if (!in_array($name, $this->stored_caches)) {
          $this->stored_caches[] = $name;
          $this->saveCachesList();
        } // if
        
        $this->data[$name] = $value;
      } // if
      return true;
    } // set
    
    /**
     * Remove variable from cache
     *
     * @param string $name
     * @return null
     */
    function remove($name) {
      if ($this->use_memcached) {
        // use memcached extension
        $result = $this->memcache->deleteByKey($this->storage_server, $name);
        if ($result === false && ($this->memcache->getResultCode() != Memcached::RES_NOTFOUND)) {
          throw new Error('Could not remove cache: ' . $this->memcache->getResultMessage());
          return false;
        } // if
      } else {
        // use memcache extension
        $result = $this->memcache->delete($this->prepareCacheName($name));
      } // if
      
      if (in_array($name, $this->stored_caches)) {
        $key = array_search($name, $this->stored_caches);
        unset($this->stored_caches[$key]);
        $this->saveCachesList();
      } // if
      
      // remove cache from internal cache
      if (isset($this->data[$name])) {
        unset($this->data[$name]);
      } // if
      
      return true;
    } // remove
    
    /**
     * Remove config options by pattern
     *
     * @param string $pattern
     * @return null
     */
    function removeByPattern($pattern) {
      $reg_expression = $this->preparePattern($pattern);
      
      $this->stored_caches = array_values($this->stored_caches);
      if (is_foreachable($this->stored_caches)) {
        foreach ($this->stored_caches as $key => $cache_name) {
          if(preg_match($reg_expression, $cache_name)) {            
            unset($this->stored_caches[$key]);
            unset($this->data[$cache_name]);
            
            if ($this->use_memcached) {
              // use memcached extension
              $result = $this->memcache->deleteByKey($this->storage_server, $cache_name);
              if ($result === false && ($this->memcache->getResponseCode() != Memcached::RES_NOTFOUND)) {
                throw new Error('Could not remove cache: ' . $this->memcache->getResultMessage());
                return false;
              } // if
            } else {
              // use memcache extension
              $result = $this->memcache->delete($this->prepareCacheName($name));
            }
          } // if
        } // foreach
        $this->saveCachesList();
      } // if
      return true;
    } // removeByPattern
    
    /**
     * Save data to persistant storage
     *
     * @param void
     * @return null
     */
    function save() {
      return true;
    } // save
    
    /**
     * Clear data from cache - drop everything
     *
     * @param void
     * @return null
     */
    function clear() {
      if ($this->use_memcached) {
        // ue memcached extension
        $result = $this->memcache->flush();
        if ($result === false) {
          throw new Error('Could not clear cache: ' . $this->memcache->getResultMessage());
          return false;
        } // if
      } else {
        // use memcache extension
        $result = $this->memcache->flush();
      } // if
      
      unset($this->stored_caches);
      $this->saveCachesList();
      
      unset($this->data);
    } // clear
    
    /**
     * Save list of caches
     * 
     * @param void
     * @return null
     */
    function saveCachesList() {
      if ($this->use_memcached) {
        // use memcached extension
        $this->memcache->setByKey($this->storage_server, $this->stored_caches_key, $this->stored_caches);
      } else {
        // use memcache extension
        $this->memcache->set($this->prepareCacheName($this->stored_caches_key), $this->stored_caches, null, $this->lifetime);
      } // if
    } // saveCachesList
    
    /**
     * Prepare cache name
     *
     * @param string $name
     * @return string
     */
    function prepareCacheName($name) {
      return $this->use_memcached ? $name : $this->storage_server.'_'.$name;
    } // prepareCacheName
  
  } // FileCacheBackend

?>