<?php

namespace EdwinFound\Utils\Storage;


class LocalStorage
{
    static $HASH_SHARD_COUNT = 16;

    private $path;

    public function __construct($path)
    {
        $path = rtrim($path, '/') . '/';
        $this->path = $path;
        @mkdir($this->path, 0755, true);
    }

    public static function build($path)
    {
        return new LocalStorage($path);
    }

    private function wrapPath($path, $prefix = '', $check = true)
    {
        if ($check && !preg_match('/^[a-zA-Z0-9_]+$/', $path)) {
            throw new \Exception('LocalStorage key must match [a-zA-Z0-9]+');
        }
        return $this->path . $prefix . $path;
    }

    public function set($key, $value)
    {
        $path = $this->wrapPath($key) . '.json';
        file_put_contents($path, json_encode($value));
        return $value;
    }

    public function setnx($key, $value)
    {
        if ($this->exists($key)) {
            return;
        }
        $this->set($key, $value);
        return $value;
    }

    public function getOrSetFromResponse($key, $callback)
    {
        if ($this->exists($key)) {
            return $this->get($key);
        }
        $ret = $callback();
        if ($ret['code']) {
            throw new \Exception('getOrSetFromResponse error : ' . $ret['msg']);
        }
        $this->set($key, $ret['data']);
        return $ret['data'];
    }

    public function del($key)
    {
        $path = $this->wrapPath($key) . '.json';
        if (file_exists($path)) {
            @unlink($path);
        }
    }

    public function get($key)
    {
        $path = $this->wrapPath($key) . '.json';
        if (file_exists($path)) {
            return @json_decode(file_get_contents($path), true);
        }
        return null;
    }

    public function exists($key)
    {
        $path = $this->wrapPath($key) . '.json';
        return file_exists($path);
    }

    public function keys($keyPattern)
    {
        $pathBase = $this->wrapPath('', '', false);
        $path = $this->wrapPath($keyPattern, '', false) . '.json';
        $paths = glob($path);
        return array_map(function ($p) use ($pathBase) {
            return substr(substr($p, strlen($pathBase)), 0, -5);
        }, $paths);
    }

    private function hashPath($key, $hash = null)
    {
        $path = $this->wrapPath($key, 'h.');
        if (null === $hash) {
            $hash = (crc32($key) % self::$HASH_SHARD_COUNT);
        }
        $path = $path . '.' . $hash . '.json';
        return $path;
    }

    public function hset($key, $field, $value)
    {
        $path = $this->hashPath($key);
        $map = [];
        if (file_exists($path)) {
            $map = @json_decode(file_get_contents($path), true);
        }
        $map[$field] = $value;
        file_put_contents($path, json_encode($map));
    }

    public function hget($key, $field)
    {
        $path = $this->hashPath($key);
        if (file_exists($path)) {
            $map = @json_decode(file_get_contents($path), true);
            return array_key_exists($field, $map) ? $map[$field] : null;
        }
        return null;
    }

    public function hgetall($key)
    {
        $path = $this->hashPath($key, '*');
        $paths = glob($path);
        $all = [];
        foreach ($paths as $path) {
            if (file_exists($path)) {
                $map = @json_decode(file_get_contents($path), true);
                foreach ($map as $k => $v) {
                    $all[$k] = $v;
                }
            }
        }
        return $all;
    }

    public function hdel($key, $field)
    {
        $path = $this->hashPath($key);
        if (file_exists($path)) {
            $map = @json_decode(file_get_contents($path), true);
            if (array_key_exists($field, $map)) {
                unset($map[$field]);
            }
            file_put_contents($path, json_encode($map));
        }
    }

    public function hexists($key, $field)
    {
        $path = $this->hashPath($key);
        if (file_exists($path)) {
            $map = @json_decode(file_get_contents($path), true);
            return array_key_exists($field, $map);
        }
        return false;
    }

}
