<?php

namespace Api\PhpServices\Session;

/**
 * Session base class
 */
abstract class Session
{
    protected $sessionId = '';
    protected $sessionName = 'JSESSIONID';
    protected $started = false;
    protected $handler = null;
    protected $appid = '';
    protected static $instance = null;

    protected function __construct()
    {
    }

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new static();
        }
        return self::$instance;
    }

    /**
     * session started, or not
     * @return boolean
     */
    public function started()
    {
        $this->started = false;
        if (session_status() === PHP_SESSION_ACTIVE) {
            $this->started = true;
        }
        return $this->started;
    }

    public function getAppid()
    {
        return $this->appid;
    }

    public function setAppid($appid)
    {
        $this->appid = $appid;
        return $this;
    }

    /**
     * get session id
     * @return string
     */
    public function getSessionId()
    {
        return $this->sessionId;
    }

    /**
     * set session id
     * @param string $sessionId
     * @return boolean
     */
    public function setSessionId($sessionId = null)
    {
        if ($sessionId) {
            if (!$this->checkSessionId($sessionId)) {
                throw new \Exception("session id [{$sessionId}] invalid");
            }
            $this->sessionId = $sessionId;
        }

        if (!empty($this->sessionId)) {
            return session_id($this->sessionId);
        }
        return false;
    }

    /**
     * get session name
     * @return string
     */
    public function getSessionName()
    {
        return $this->sessionName;
    }

    /**
     * set session name
     * @param string $sessionName
     * @return boolean
     */
    public function setSessionName($sessionName = null)
    {
        if ($sessionName) {
            $this->sessionName = $sessionName;
        } else {
            $sessionName = $this->sessionName;
        }

        return session_name($sessionName);
    }

    /**
     * get session handler
     * @return SessionHandlerInterface
     */
    public function getHandler()
    {
        return $this->handler;
    }

    /**
     * set session handler
     * @param object $handler
     * @param boolean $registerShutdown
     * @return boolean
     */
    public function setHandler($handler, $registerShutdown = true)
    {
        $this->handler = $handler;
        return session_set_save_handler($handler, $registerShutdown);
    }

    /**
     * session start
     * @param array $options
     * @return boolean
     */
    public function start($options = [])
    {
        if (session_start($options) === false) {
            return false;
        }
        $this->started();
        return true;
    }

    /**
     * get session expire (second)
     * @param string $userid
     * @return integer
     */
    public function getExpire($userid = null)
    {
        if (empty($userid) || !in_array($userid, [
            1211920133,
            1215304799,
        ])) {
            return 2592000;
        }
        return 3600;
    }

    /**
     * flush session to database
     * @return boolean
     */
    public function write()
    {
        return $this->handler ? $this->handler->write(
            $this->sessionId,
            serialize($_SESSION)
        ) : false;
    }

    /**
     * destroy session
     * @return boolean
     */
    public function destroy()
    {
        $this->started = false;
        return session_destroy();
    }

    /**
     * close session
     * @return boolean
     */
    public function close()
    {
        if (!$this->started()) {
            $this->write();
            $this->closeHandler();
            $this->started = false;
            return true;
        }
        session_write_close();
        $this->started = false;
        return true;
    }

    /**
     * close session handler
     * @return boolean
     */
    protected function closeHandler()
    {
        return $this->handler ? $this->handler->close() : false;
    }

    /**
     * set cookie
     * @param integer $expire       expire time (s)
     * @param boolean $replace      replace previous similar cookie
     * @return boolean
     */
    public function setCookie($expire = 0, $replace = true)
    {
        if ($replace) {
            $cookieKeyword = $this->getSessionName()
                . '=' . $this->getSessionId() . ';';
            $this->removeCookie($cookieKeyword);
        }

        if ($expire != 0) {
            $expire += time();
        }

        setcookie(
            $this->getSessionName(),
            $this->getSessionId(),
        $expire, '/', '', false, true);

        $_COOKIE[$this->getSessionName()] = $this->getSessionId();

        return true;
    }

    /**
     * generate valid session id
     * @param string $appid
     * @param integer $userid
     * @param boolean $isLogin                 是否登录
     * @return array
     */
    public function validSessionIds($appid, $userid, $isLogin = false)
    {
        $guest = $isLogin ? 'nonguest' : 'guest';
        
        // 使用codis hash tag保证同一个文章的正文及信息流缓存落入同一台codis server
        $tag = '{'.strtolower(str_replace(array('+','/','='), array(''), base64_encode($userid))).'}';
        $sessionIds = [];
        for ($i = 0; $i < 10; $i++) {
            $sessionId = $tag . md5($i . $appid . $userid . $guest);
            $sessionIds[] = $sessionId . substr(sha1($sessionId), -2);
        }
        return $sessionIds;
    }

    /**
     * check session id
     * @return boolean
     */
    public function checkSessionId($sessionId = null)
    {
        if(!$sessionId) {
            return false;
        }
        $verifyCode = substr($sessionId, -2);
        $encodeStr = substr($sessionId, 0, -2);
        if (substr(sha1($encodeStr), -2) !== $verifyCode) {
            return false;
        }
        return true;
    }

    /**
     * remove cookie
     * @param string $keyword
     * @return boolean
     */
    protected function removeCookie($keyword = null)
    {
        if (is_null($keyword)) {
            header_remove('Set-Cookie');
            return true;
        }
        $keyword = " {$keyword}";
        $otherCookies = [];
        foreach (headers_list() as $header) {
            if (stripos($header, 'Set-Cookie:') !== 0) {
                continue;
            }
            if (strpos($header, $keyword, 11) !== 11) {
                $otherCookies[] = $header;
            }
        }

        header_remove('Set-Cookie');
        $otherCookies = array_unique($otherCookies);
        foreach ($otherCookies as $cookie) {
            header($cookie, false);
        }
        return true;
    }
}
