<?php
class DumpFile
{
    public function __construct($score_id)
    {
        $this->score_id = $score_id;
        $this->dump_lines = null;
    }


    public function get_filename($dir, $ext)
    {
        $dirname = sprintf("%s/%d", $dir, floor($this->score_id / 1000) * 1000);
        return sprintf("%s/%d.%s.gz", $dirname, $this->score_id, $ext);
    }


    public function show($dir, $ext, $content_type)
    {
        $filename = $this->get_filename($dir, $ext);

        if (!file_exists($filename)) {
            http_response_code(404);
            return;
        }

        $contents = file_get_contents($filename);
        $etag = md5($contents);

        if ($etag === filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH')) {
            http_response_code(304);
            return;
        }

        $content_encoding = self::get_content_encoding();
        header("Etag: ".$etag);
        header("Content-Type: ".$content_type);

        if ($content_encoding !== null) {
            header("Content-Encoding: ".$content_encoding);
            echo $contents;
        } else {
            echo gzdecode($contents);
        }
    }


    public function save($dir, $ext, $contents)
    {
        if ($contents === false) {
            return;
        }

        umask(2); // Group書き込み権限許可

        $filename = $this->get_filename($dir, $ext);
        $dirname = dirname($filename);

        if (!file_exists($dirname)) {
            mkdir($dirname, 02775, true);
        }

        $zp = gzopen($filename, "w9");

        foreach ($contents as $line) {
            gzwrite($zp, $line);
            gzwrite($zp, "\n");
        }

        gzclose($zp);
    }


    public function exists($dir, $ext)
    {
        $dirname = sprintf("%s/%d", $dir, floor($this->score_id / 1000) * 1000);
        $filename = sprintf("%s/%d.%s.gz", $dirname, $this->score_id, $ext);

        return file_exists($filename);
    }


    /**
     * キャラクタダンプを配列に読み込む
     * 読み込んだ配列はメンバ変数dump_linesに格納
     *
     * @return boolean 読み込みに成功した場合TRUE、失敗した場合FALSE
     */
    private function readlines()
    {
        if ($this->dump_lines !== null) {
            return true;
        }

        $lines = gzfile($this->get_filename('dumps', 'txt'));
        if ($lines !== false) {
            $this->dump_lines = $lines;
            return true;
        }

        return false;
    }


    /**
     * キャラクタダンプの死ぬ直前のメッセージもしくは勝利メッセージを取得する
     *
     * @return array 死ぬ直前のメッセージもしくは勝利メッセージを1行1要素にした文字列の配列
     */
    public function get_last_message()
    {
        if ($this->readlines() === false) {
            return [];
        }

        $in_message = false;
        $result = [];
        foreach ($this->dump_lines as $line) {
            if (preg_match('/^\s*\[(.*)\]\s*$/u', $line, $matches)) {
                if ($matches[1] == '*勝利*メッセージ' ||
                    $matches[1] == '死ぬ直前のメッセージ') {
                    $in_message = true;
                } elseif ($in_message) {
                    break;
                }
            }

            if ($in_message) {
                $result[] = rtrim($line, "\n");
            }
        }

        return $result;
    }


    /**
     * キャラクタダンプから死因の詳細を得る
     *
     * @return string 死因の詳細を表す文字列
     */
    public function get_death_reason_detail()
    {
        if ($this->readlines() === false) {
            return false;
        }

        $death_reason_lines = array_slice($this->dump_lines, 30, 3);
        $death_reason = implode("", array_map('trim', $death_reason_lines));

        if (preg_match("/^…あなたは、?(.+)。/u", $death_reason, $matches)) {
            return $matches[1];
        } else {
            return false;
        }
    }


    /**
     * キャラクタダンプから死んだ場所を得る
     *
     * @return string|FALSE 死んだ場所を表す文字列。
     *                死んだ場所が得られなかった場合FALSE。
     */
    public function get_dead_place()
    {
        $death_reason_detail = $this->get_death_reason_detail();
        if ($death_reason_detail === false) {
            return false;
        }
        $places = implode("|", [
            "階", "荒野", "地上", "街",
            "辺境の地", "モリバント", "アングウィル", "テルモラ", "ズル",
            "クエスト「.+」", "クエスト",
        ]);

        if (preg_match(
            "/^(.*(?:{$places})\s*)で.+$/u",
            $death_reason_detail,
            $matches
        )) {
            return $matches[1];
        } else {
            return false;
        }
    }


    private static function browser_accept_encodings()
    {
        $accept_encoding = filter_input(INPUT_SERVER, 'HTTP_ACCEPT_ENCODING');
        if ($accept_encoding == null) {
            return [];
        }

        return array_map('trim', explode(",", $accept_encoding));
    }


    private static function get_content_encoding()
    {
        $supported_gzip_encodings = array_intersect(
            self::browser_accept_encodings(),
            ['gzip', 'x-gzip']
        );

        return array_shift($supported_gzip_encodings);
    }
}
