<?php

namespace dokuwiki\plugin\config\core;

use dokuwiki\plugin\config\core\Setting\Setting;
use dokuwiki\Logger;

/**
 * Writes the settings to the correct local file
 */
class Writer
{
    /** @var string header info */
    protected $header = 'Dokuwiki\'s Main Configuration File - Local Settings';

    /** @var string the file where the config will be saved to */
    protected $savefile;

    /**
     * Writer constructor.
     */
    public function __construct()
    {
        global $config_cascade;
        $this->savefile = end($config_cascade['main']['local']);
    }

    /**
     * Save the given settings
     *
     * @param Setting[] $settings
     * @throws \Exception
     */
    public function save($settings)
    {
        global $conf;
        if ($this->isLocked()) throw new \Exception('no save');

        // backup current file (remove any existing backup)
        if (file_exists($this->savefile)) {
            if (file_exists($this->savefile . '.bak.php')) @unlink($this->savefile . '.bak.php');
            if (!io_rename($this->savefile, $this->savefile . '.bak.php')) throw new \Exception('no backup');
        }

        if (!$fh = @fopen($this->savefile, 'wb')) {
            io_rename($this->savefile . '.bak.php', $this->savefile); // problem opening, restore the backup
            throw new \Exception('no save');
        }

        $out = '';
        foreach ($settings as $setting) {
            if ($setting->shouldBeSaved()) {
                $out .= $setting->out('conf', 'php');
            }
        }

        if ($out === '') {
            throw new \Exception('empty config');
        }
        $out = $this->getHeader() . $out;

        fwrite($fh, $out);
        fclose($fh);
        if ($conf['fperm']) chmod($this->savefile, $conf['fperm']);
        $this->opcacheUpdate($this->savefile);
    }

    /**
     * Update last modified time stamp of the config file
     *
     * Will invalidate all DokuWiki caches
     *
     * @throws \Exception when the config isn't writable
     */
    public function touch()
    {
        if ($this->isLocked()) throw new \Exception('no save');
        @touch($this->savefile);
        $this->opcacheUpdate($this->savefile);
    }

    /**
     * Invalidate the opcache of the given file (if possible)
     *
     * @todo this should probably be moved to core
     * @param string $file
     */
    protected function opcacheUpdate($file)
    {
        if (!function_exists('opcache_invalidate')) return;
        set_error_handler(function ($errNo, $errMsg) {
            Logger::debug('Unable to invalidate opcache: ' . $errMsg);
        });
        opcache_invalidate($file);
        restore_error_handler();
    }

    /**
     * Configuration is considered locked if there is no local settings filename
     * or the directory its in is not writable or the file exists and is not writable
     *
     * @return bool true: locked, false: writable
     */
    public function isLocked()
    {
        if (!$this->savefile) return true;
        if (!is_writable(dirname($this->savefile))) return true;
        if (file_exists($this->savefile) && !is_writable($this->savefile)) return true;
        return false;
    }

    /**
     * Returns the PHP intro header for the config file
     *
     * @return string
     */
    protected function getHeader()
    {
        return implode(
            "\n",
            [
                '<?php',
                '/*',
                ' * ' . $this->header,
                ' * Auto-generated by config plugin',
                ' * Run for user: ' . ($_SERVER['REMOTE_USER'] ?? 'Unknown'),
                ' * Date: ' . date('r'),
                ' */',
                '',
                ''
            ]
        );
    }
}