<?php

namespace Acms\Services\StaticExport\Compiler;

use Acms\Services\StaticExport\Contracts\Resolver;
use ACMS_RAM;

class SourceResolver extends Resolver
{
    /**
     * @var string
     */
    protected $destinationOffsetDir;

    /**
     * @var string
     */
    protected $destinationPath;

    /**
     * @var string
     */
    protected $destinationDomain;

    /**
     * @var string
     */
    protected $destinationBlogCode;

    /**
     * @var int
     */
    protected $offset;

    /**
     * @param string $html
     * @param string $document_root
     * @param string $offset_dir
     * @param string $domain
     * @param string $blog_code
     *
     * @return string
     */
    public function resolve($html, $document_root, $offset_dir, $domain, $blog_code)
    {
        $this->destinationOffsetDir = $offset_dir;
        $this->destinationPath = $document_root . $offset_dir;
        $this->destinationDomain = $domain;
        $this->destinationBlogCode = $blog_code;

        $blogPath = ACMS_RAM::blogDomain(BID) ?? '';
        if (DIR_OFFSET) {
            $blogPath .= '/' . DIR_OFFSET;
        }
        $blogPath = preg_quote($blogPath, '@');

        $regex = $this->getRegex();
        $this->offset = 0;
        while (preg_match($regex, $html, $match, PREG_OFFSET_CAPTURE, $this->offset)) {
            // 置き換え対象文字列の$html全体からみたときのオフセット文字数を取得
            $this->offset = $match[0][1] + strlen($match[0][0]);

            // マッチ箇所を1文字列チャンクあたり，5回まで検出する
            // マッチポイントが検出されたらbreakして，$mptはつぎのwhileループに持ち越す
            for ($mpt = 1; $mpt <= 6; $mpt++) {
                if (!empty($match[$mpt][0])) {
                    break;
                }
            }
            $path = trim($match[$mpt][0], '\'"'); // @phpstan-ignore-line
            // ポートはホスト直後に現れるため、$blogPath（ドメイン + DIR_OFFSET）と
            // ポートを同一の正規表現でまとめてマッチさせるのは不可能。
            // 先に WHATWG URL でポートを取り除いてから自ブログ URL を相対化する。
            $path = $this->removePortFromUrl($path);
            $path = preg_replace('@(https?)?://' . $blogPath . '/?@', '/', $path);
            $this->replacer($path, $html, $match, $mpt); // @phpstan-ignore-line
        }
        $regex = '@<\s*(?:img|input|script|frame|iframe)(?:"[^"]*"|\'[^\']*\'|[^\'">])*data-src\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>@';
        $this->offset = 0;
        while (preg_match($regex, $html, $match, PREG_OFFSET_CAPTURE, $this->offset)) {
            $this->offset = $match[0][1] + strlen($match[0][0]);
            $path = trim($match[1][0], '\'"');
            $path = $this->removePortFromUrl($path);
            $path = preg_replace('@(https?)?://' . $blogPath . '/?@', '/', $path);
            $this->replacer($path, $html, $match, 1);
        }
        return $html;
    }

    /**
     * @return string
     */
    protected function getRegex()
    {
        $extension = '(?:acms)';
        if (defined('REWRITE_PATH_EXTENSION')) {
            $extension = '(?:acms|' . REWRITE_PATH_EXTENSION . ')';
        }
        $regex = '@' .
            // src属性をもつHTML要素
            '<\s*(?:img|input|script|frame|iframe)(?:"[^"]*"|\'[^\']*\'|[^\'">])*[\s]+src\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>|' .
            // link要素（href属性）
            '<\s*link(?:"[^"]*"|\'[^\']*\'|[^\'">])*href\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>|' .
            // object, applet要素（arcvhie属性）
            '<\s*(?:object|applet)(?:"[^"]*"|\'[^\']*\'|[^\'">])*archives\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>|' .
            // background属性
            '<\s*\w+(?:"[^"]*"|\'[^\']*\'|[^\'">])*background\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>|' .
            // css url属性
            '\s*url\s*\(("[^"]+"|\'[^\']+\'|[^\'"\)]+)\)|' .
            // a要素
            '<\s*a(?:"[^"]*"|\'[^\']*\'|[^\'">])*href\s*=\s*("[^"]+\.' . $extension . '"|\'[^\']+\.' . $extension . '\'|[^\'"\s>]\.' . $extension . '+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>' .
            '@i';

        return $regex;
    }

    /**
     * パスの書き換え
     *
     * @param string $path 置き換え対象パス
     * @param string $html 全体html
     * @param array $match
     * @param int $mpt
     */
    protected function replacer($path, &$html, $match, $mpt)
    {
        $path = $this->removePortFromUrl($path);
        $path = trim($path);
        $_path = explode('?', $path);
        $_path = $_path[0];

        // 何らかのスキーマ http:// 等から始まっていたら次のマッチポイントへ
        if (is_int(strpos($path, '://')) || substr($path, 0, 2) === '//') {
            return;
        }

        // / をなくしたときに，何も残らなければ ただのルートパス指定とみなして次のマッチポイントへ
        if (!str_replace('/', '', $_path)) {
            return;
        }

        if ('/' == substr($path, 0, 1)) {
            $path = substr($path, 1);
            $_path = explode('?', $path);
            $_path = $_path[0];

            if ($this->acmsJsReplace($path, $_path, $html, $match, $mpt)) {
                return;
            }
            if ($this->matchingPath($path, $_path, $html, $match, $mpt)) {
                return;
            }
        }
    }

    /**
     * acms.jsのクエリのドメイン書き換え処理
     *
     * @param string $path
     * @param string $format_path
     * @param string $html
     * @param array $match
     * @param int $mpt
     * @return boolean
     */
    protected function acmsJsReplace($path, $format_path, &$html, $match, $mpt)
    {
        if ($format_path === 'acms.js') {
            $offset = strlen($match[$mpt][0]);
            $path = preg_replace('/domains=([^&]+)/', 'domains=' . $this->destinationDomain, $path);
            if ($this->destinationOffsetDir) {
                $path = preg_replace('/scriptRoot=([^&]+)/', 'scriptRoot=/' . $this->destinationOffsetDir, $path);
                if (strpos($path, 'offset=') === false) {
                    $path .= '&offset=' . $this->destinationOffsetDir;
                } else {
                    $path = preg_replace('/offset=([^&]+)/', 'offset=' . $this->destinationOffsetDir, $path);
                }
            }
            $html = substr_replace($html, '"/' . $this->destinationOffsetDir . $path . '"', $match[$mpt][1], $offset);
            $this->offset -= ($offset - strlen($path));
            return true;
        }
        return false;
    }

    /**
     * パスの書き換え
     *
     * @param string $path
     * @param string $format_path
     * @param string $html
     * @param array $match
     * @param int $mpt
     * @return boolean
     */
    protected function matchingPath($path, $format_path, &$html, $match, $mpt)
    {
        $_path = explode('?', $path);
        $query = isset($_path[1]) ? '?' . $_path[1] : '';
        $split_path = preg_split('@/@', $format_path, -1, PREG_SPLIT_NO_EMPTY);

        $rewrite = false;
        while (true) {
            $tmp = join('/', $split_path);
            if ($tmp === '') {
                break; // ここで while を抜ける（＝rewriteしない）
            }
            // 書き出し先にあったら
            foreach (['',  $this->destinationBlogCode] as $bcd) {
                if (is_readable($this->destinationPath . $bcd . urldecode($tmp))) {
                    $offset = strlen($match[$mpt][0]);
                    if ($mpt === 5) {
                        $html = substr_replace($html, '/' . $this->destinationOffsetDir . $bcd . $tmp . $query . '', $match[$mpt][1], $offset);
                    } else {
                        $html = substr_replace($html, '"/' . $this->destinationOffsetDir . $bcd . $tmp . $query . '"', $match[$mpt][1], $offset);
                    }
                    $this->offset -= ($offset - strlen($tmp . $query));
                    $rewrite = true;
                    break 2;
                }
            }
            if (!(array_shift($split_path))) {
                break;
            }
        }
        if (!$rewrite) {
            // ToDo: 書き換え失敗のエラーハンドリングをする
        }
        return true;
    }
}
