<?php

/**
 * archive path file list object
 *
 * Standard: PSR-2
 *
 * @link http://www.php-fig.org/psr/psr-2
 *
 * @package   name
 * @copyright (c) 2019, Snapcreek LLC
 * @license   https://opensource.org/licenses/GPL-3.0 GNU Public License
 */

defined('ABSPATH') || defined('DUPXABSPATH') || exit;

use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapJson;

class DUP_PRO_Archive_File_List
{
    protected $path   = null;
    protected $handle = null;
    protected $cache  = null;

    public function __construct($path)
    {
        if (empty($path)) {
            throw new Exception('path can\'t be empty');
        }

        $this->path = SnapIO::safePath($path);
    }

    public function __destruct()
    {
        $this->close();
    }

    public function getPath()
    {
        return $this->path;
    }

    public function open($truncate = false)
    {
        if (is_null($this->handle)) {
            if (($this->handle = fopen($this->path, 'a+')) === false) {
                DUP_PRO_Log::trace('Can\'t open ' . $this->path);
                $this->handle = null;
                return false;
            }
        }
        if ($truncate) {
            $this->emptyFile();
        }
        return true;
    }

    public function emptyFile()
    {
        if (!$this->open(false)) {
            return false;
        }
        if (($res = ftruncate($this->handle, 0)) === false) {
            DUP_PRO_Log::trace('Can\'t truncate file ' . $this->path);
            return false;
        }
        return true;
    }

    public function close()
    {
        if (!is_null($this->handle)) {
            if (($res = @fclose($this->handle)) === false) {
                DUP_PRO_Log::trace('Can\'t close ' . $this->path);
                return false;
            }
            $this->handle = null;
        }
        return true;
    }

    public function addEntry($path, $size, $nodes)
    {

        if (is_null($this->handle)) { // check to generate less overhead
            if (!$this->open()) {
                return false;
            }
        }
        $entry = array('p' => $path, 's' => $size, 'n' => $nodes);
        fwrite($this->handle, SnapJson::jsonEncode($entry) . "\n");
    }

    /**
     *
     * @param bool $pathOnly if true return only payth
     *
     * @return boolean|array|string return false if is end of filer.
     */
    public function getEntry($pathOnly = false)
    {
        if (is_null($this->handle)) { // check to generate less overhead
            if (!$this->open()) {
                return false;
            }
        }

        if (($json = fgets($this->handle, 4196)) === false) {
            // end of file return false
            return false;
        }

        $result = json_decode($json, true);
        if ($pathOnly) {
            return $result['p'];
        } else {
            return $result;
        }
    }

    protected function cleanCache()
    {
        $this->cache = null;
        return true;
    }

    protected function loadCache($refreshCache = false)
    {
        if ($refreshCache || is_null($this->cache)) {
            if (!$this->open()) {
                return false;
            }
            $this->cache = array();
            if (@fseek($this->handle, 0) === -1) {
                DUP_PRO_Log::trace('Can\'t seek at 0 pos for file ' . $this->path);
                $this->cleanCache();
                return false;
            }
            while (($entry = $this->getEntry()) !== false) {
                $this->cache[$entry['p']] = $entry;
            }
            if (!feof($this->handle)) {
                DUP_PRO_Log::trace('Error: unexpected fgets() fail', false);
            }
        }
        return true;
    }

    public function getEntryFromPath($path, $refreshCache = false)
    {
        if (!$this->loadCache($refreshCache)) {
            return false;
        }

        if (array_key_exists($path, $this->cache)) {
            return $this->cache[$path];
        } else {
            return false;
        }
    }

    public function getEntriesFormPath($path, $refreshCache = false)
    {
        if (!$this->loadCache($refreshCache)) {
            return false;
        }

        if (array_key_exists($path, $this->cache)) {
            $result = array();
            foreach ($this->cache as $current => $entry) {
                if (preg_match('/^' . preg_quote($path, '/') . '\/[^\/]+$/', $current)) {
                    $result[] = $entry;
                }
            }
            return $result;
        } else {
            return false;
        }
    }

    public function getArrayPaths($pathPrefix = '')
    {
        if (!$this->open()) {
            return false;
        }

        $result = array();
        if (@fseek($this->handle, 0) === -1) {
            DUP_PRO_Log::trace('Can\'t seek at 0 pos for file ' . $this->path);
            return false;
        }
        $safePrefix = SnapIO::safePathUntrailingslashit($pathPrefix);
        while (($path       = $this->getEntry(true)) !== false) {
            $result[] = $safePrefix . '/' . $path;
        }
        if (!feof($this->handle)) {
            DUP_PRO_Log::trace('Error: unexpected fgets() fail', false);
        }
        return $result;
    }
}
