<?php

namespace Neag\MainBundle\Cache;

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;

class Cache
{
    // ???????????? ????? ?????
    const KEY_MAXLENGTH = 255;
    
    /**
     * ?????? Filesystem
     *
     * @var       object
     * @access    protected
     */
    protected $fs;   
    
    /**
     * ????
     *
     * @var       string | array | null
     * @access    protected
     */
    protected $token = null;
    
    /**
     * ????? ????? ???? ? ?.
     *
     * @var       integer
     * @access    protected
     */
    protected $lifetime = 3600;
    
    /**
     * ?????????????? ???????????? ??????
     *
     * @var       boolean
     * @access    protected
     */
    protected $serialization = false;

    /**
     * ?????????????? ????????? ??????? ????????? ???? ??? ????????? ??????
     *
     * @var       boolean
     * @access    protected
     */
    protected $prolongation = false;
    
    /**
     * ??????????? ??????? ???????? ??????
     *
     * @var       float
     * @access    protected
     */
    protected $tidyProbability = 0.01;

    /**
     * ???????????? ????
     *
     * @var       boolean
     * @access    protected
     */
    protected $invalidToken = false;
    
    /**
     * ?????? CacheManager
     *
     * @var       object
     * @access    protected
     */
    protected $cacheManager;
    
    /**
     * ??????????? ??????
     *
     * @access      public
     * @param       object xbkModule
     * @param       integer | string | array | object
     * @param       array
     */
    public function __construct ($token = null, $options = Array())
    {
        $this->fs = new Filesystem();
        
        if (!is_null($token)) {
            $this->setToken($token);
        }

        if (isset($options['lifetime'])) {
            $this->setLifetime($options['lifetime']);
        }

        if (isset($options['serialization'])) {
            $this->setSerialization($options['serialization']);
        }

        if (isset($options['prolongation'])) {
            $this->setProlongation($options['prolongation']);
        }

        if (isset($options['tidy_probability'])) {
            $this->setTidyProbability($options['tidy_probability']);
        }
        
        $this->cacheManager = new \Neag\MainBundle\Cache\CacheManager;

        $this->tryTidy();
    }
    
    /**
     * ????????? ???? ?? ?????
     *
     * @access      public
     * @param       integer | string | array | object
     * @return      boolean
     */
    public function isValidToken ($token)
    {
        if (is_string($token)) {
            if (strlen($token) > self::KEY_MAXLENGTH) {
                return false;
            } else {
                return true;
            }
        } elseif (is_array($token)) {
            if (count($token) > 1) {
                $dir = array_shift($token);
                if (strlen($dir) > self::KEY_MAXLENGTH) {
                    return false;
                }
                return $this->isValidToken($token);
            } elseif (count($token) == 1) {
                return $this->isValidToken($token[0]);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
    
    /**
     * ????????? ?????
     *
     * @access      public
     * @param       string
     * @return      $this
     */
    public function setToken ($token)
    {
        /*
        if (is_array($token) || is_object($token)) {
            $token = serialize($token);
        }
        */
        $arr = explode('/', $token);
        if (count($arr) > 1) {
            // Если папка
            if ($this->isValidToken($arr)) {
                $this->invalidToken = false;
                $this->token = $arr;
            } else {
                $this->invalidToken = true;
            }
        } else {
            // Если файл
            if ($this->isValidToken($token)) {
                $this->invalidToken = false;
                $this->token = $token;
            } else {
                $this->invalidToken = true;
            }
        }

        return $this;
    }

    /**
     * ?????????? ????
     *
     * @access      public
     * @return      integer | string | array | object
     */
    public function getToken ($original = true)
    {
        if ($original) {
            if (is_array($this->token)) {
                return implode('/', $this->token);
            }
        }
        return $this->token;
    }
    
    /**
     * ????????? ??????? ????? ????
     *
     * @access      public
     * @param        integer
     * @return      $this
     */
    public function setLifetime ($lifetime)
    {
        if (is_numeric($lifetime)) {
            $this->lifetime = $lifetime;
        }

        return $this;
    }

    /**
     * ?????????? ??????? ????? ????
     *
     * @access      public
     * @return        integer
     */
    public function getLifetime ()
    {
        return $this->lifetime;
    }
    
    /**
     * ????????? ??????????? ?????? ???? ?? ?????????? ??????????
     *
     * @access      public
     * @param        float
     * @return      $this
     */
    public function setTidyProbability ($tidyProbability)
    {
        if (
        is_float($tidyProbability) ?
        ($tidyProbability >= 0 && $tidyProbability <= 1) :
        false
        ) {
            $this->tidyProbability = $tidyProbability;
        }

        return $this;
    }

    /**
     * ?????????? ??????????? ?????? ???? ?? ?????????? ??????????
     *
     * @access      public
     * @return        float
     */
    public function getTidyProbability ()
    {
        return $this->tidyProbability;
    }
    
    /**
     * ????????? ????? ????????????
     *
     * @access      public
     * @param        boolean
     * @return      $this
     */
    public function setSerialization ($serialization)
    {
        if (is_bool($serialization)) {
            $this->serialization = $serialization;
        }

        return $this;
    }

    /**
     * ?????????? ???? ????????????
     *
     * @access      public
     * @return        boolean
     */
    public function getSerialization ()
    {
        return $this->serialization;
    }

    /**
     * ????????? ????? ?????????
     *
     * @access      public
     * @param        boolean
     * @return      $this
     */
    public function setProlongation ($prolongation)
    {
        if (is_bool($prolongation)) {
            $this->prolongation = $prolongation;
        }

        return $this;
    }

    /**
     * ?????????? ???? ?????????
     *
     * @access      public
     * @return        boolean
     */
    public function getProlongation ()
    {
        return $this->prolongation;
    }
    
    /**
     * ???????? ?????????? ?????????? ?? ????
     *
     * @access      public
     */
    public function tryTidy ()
    {
        if (!is_null($this->tidyProbability)) {
            if (rand(0, 10000) <= $this->tidyProbability*10000) {
                // ?????? ??????
                $this->cacheManager->tidy();
            }
        }
    }
    
    /**
     * ???????? ???? ?? ?????
     *
     * @access      public
     */
    public function getFilePath ($path = null)
    {
        if (empty($path)) {
            $token = $this->getToken(false);
            if (is_array($token)) {
                $path = implode('/', $token);
            } else {
                $path = $token;
            }
        }
        return \Neag\MainBundle\Cache\CacheManager::DIR.$path;
    }       
    
    /**
     * ?????????? ????? ???????? ????
     *
     * @access     public
     * @param      integer | null
     */
    public function getTimeCreate ()
    {
        if (empty($this->Record)) {
            try {
                $this->load();
            } catch (Exception $e) {
                return null;
            }
        }
        return strtotime($this->Record->create_time);
    }

    /**
     * ?????????? ????? ????????? ????
     *
     * @access     public
     * @param      integer | null
     */
    public function getTimeExpire ()
    {
        if (empty($this->Record)) {
            try {
                $this->load();
            } catch (Exception $e) {
                return null;
            }
        }
        return strtotime($this->Record->expire_time);
    }
    
    /**
     * ????????? ????? ???? (??????? / ?? ???????)
     *
     * @access     public
     * @param      mixed
     * @return     boolean
     */
    public function checkTime()
    {
        $filepath = $this->getFilePath();        
        $time = @filemtime($filepath);
        if (empty($time)) {
            return false;
        }
        if ($time < (time() - $this->getLifetime())) {
            // ????? ????
            return false;            
        } else {
            return true;
        }
    }   
    
    /**
     * ?????? ?????????? ? ???
     *
     * @access     public
     * @param      mixed
     * @return     boolean
     */
    public function save ($data)
    {                                                       
        if ($this->invalidToken) {
            return false;
        }                

        if ($this->serialization) {
            $data = serialize($data);
        }                           
                
        //$fs->dumpFile($filepath, $data);
        //echo $filepath;
        $fs = $this->fs;
        $token = $this->getToken(false);
        //$this->trace('chp 1');
        //$this->trace($token);
        if (is_array($token)) {
            //$this->trace('chp 2');
            //throw Exception('chp 2');
            $subparent_path = '';
            for ($i=0; isset($token[$i]); $i++) {                
                if (isset($token[$i+1])) {
                    // Если не последний элемент - папка
                    $path = \Neag\MainBundle\Cache\CacheManager::DIR.$subparent_path.$token[$i];
                    if (!$fs->exists($path)) {
                        $fs->mkdir($path, 0777);
                    } else {
                        if (!is_dir($path)) {
                            $fs->remove($path);
                            $fs->mkdir($path, 0777);
                        }
                    }                  
                    $subparent_path .= $token[$i].'/';                    
                }
            }
        }                     
        $filepath = $this->getFilePath();
        //throw new \Exception($filepath);
        $fs->remove($filepath);
        file_put_contents($filepath, $data);
        /*        
        $fp = fopen($filepath, 'w');
        fwrite($fp, $data);  
        fclose($fp); 
        */
    }

    /**
     * ????????? ?????????? ?? ????
     *
     * @access     public
     * @param      mixed | false
     */
    public function load ()
    {
        if ($this->invalidToken) {
            return false;
        }
        
        $fs = $this->fs;
               
        $filepath = $this->getFilePath();
                
        if (!$this->checkTime()) {
            $fs->remove($filepath);
            return false;           
        }
        
        $data = @file_get_contents($filepath);

        if ($this->serialization) {
            $data = unserialize($data);
        }

        if ($this->prolongation) {
            $fs->touch($filepath);
        }

        return $data;
    }

    /**
     * ????????? ??????? ?????????? ? ???
     *
     * @access     public
     * @param      boolean
     */
    public function exists ()
    {
        if ($this->invalidToken) {
            return false;
        }        
        
        $fs = $this->fs;
        $filepath = $this->getFilePath();
        
        if (!$this->checkTime()) {             
            $fs->remove($filepath);
            return false;           
        }

        return $fs->exists($filepath); 
    }

    /**
     * ??????? ???
     *
     * @access     public
     * @return     boolean
     */
    public function clear ()
    {
        if ($this->exists())    {
            /*
            $fs = $this->fs;
            $filepath = $this->getFilePath();
            $fs->remove($filepath);
            */
            $token = $this->getToken(false);
            if (is_array($token)) {
                $path = implode('/', $token);
            } else {
                $path = $token;
            }
            $this->cacheManager->clear($path);
            return true;
        }
        return false;
    }                                          

}