<?php

namespace App\Libraries\Adityampc;

use Exception;
use ZipArchive;

class DocxToHtml
{
    private $html = "";
    protected $srcFile = "";
    private $randomNameFile = true;
    protected $tempFol = "";
    private $word_xml = "";
    private $word_xml_relational = "";
    private $assetPath = "";
    private $isPublic = true;
    private $allowedTag = [
        "<a:blip>",  "<w:p>", "<o:OLEObject>", "<w:tbl>", "<w:tr>", "<w:tc>", "<p>",
    ];
    private $relation_file = [];
    private $xmlToHtml = [
        "xml" => ["/<w:p(.*?)>/", "/<\/w:p(.*?)>/"], "html" => ["<p>", "</p>"]
    ];
    private $supported_file = ['jpg', 'jpeg', 'png', 'mp3', 'bin'];
    /**
     * @param string|null $src file docx path source
     * @param string|null $temp temporary directory path
     * @param string|array|null $assetPath asset directory path ['image'=>'path/to/image','audio'=>'path/to/audio'];
     */
    public function __construct($src = null,  $temp = null, $assetPath = null)
    {
        if ($src) $this->setSrc($src);
        if ($temp) $this->setTemp($temp);
        if ($assetPath) $this->setAssetPath($assetPath);
    }
    public function __destruct()
    {
        try {
            $this->rrmdir($this->tempFol);
            unlink($this->srcFile);
        } catch (\Throwable $th) {
        }
    }
    /**
     * @param bool $setPublic auto rewrite to public path assets
     */
    public function setPublic(bool $setPublic = true)
    {
        $this->isPublic = $setPublic;
        return $this;
    }
    /**
     * @param bool $randomNameFile random name assets
     */
    public function setRandomName(bool $randomNameFile = true)
    {
        $this->randomNameFile = $randomNameFile;
        return $this;
    }

    /**
     * @param string $src file docx path source
     */
    public function setSrc($src)
    {
        if (!file_exists($src)) throw new Exception("File not found");
        if (!is_file($src)) throw new Exception("File is not file");
        $this->srcFile = $src;
        return $this;
    }
    /**
     * @param string|array $assetPath asset directory path ['image'=>'path/to/image','audio'=>'path/to/audio'];
     */
    public function setAssetPath($assetPath)
    {
        if (is_array($assetPath)) {
            if (isset($assetPath['image'])) {
                if (!file_exists($assetPath['image'])) {
                    @mkdir($assetPath['image']);
                }
                if (!is_dir($assetPath['image'])) throw new Exception("Asset Image Path folder is not folder");
            }
            if (isset($assetPath['audio'])) {
                if (!file_exists($assetPath['audio'])) {
                    @mkdir($assetPath['audio']);
                }
                if (!is_dir($assetPath['audio'])) throw new Exception("Asset Audio Path folder is not folder");
            }
        } else {
            if (!file_exists($assetPath)) {
                @mkdir($assetPath);
            }
            if (!is_dir($assetPath)) throw new Exception("Asset Path folder is not folder");
        }
        $this->assetPath = $assetPath;
        return $this;
    }
    /**
     * @param string $temp temporary directory path
     */
    public function setTemp($temp)
    {
        if (!file_exists($temp)) {
            @mkdir($temp, 0777, true);
        }
        if (!is_dir($temp)) throw new Exception("Temporary folder is not folder");
        $this->tempFol = $temp;
        $this->word_xml = $this->tempFol . "/word/document.xml";
        $this->word_xml_relational = $this->tempFol . "/word/_rels/document.xml.rels";
        return $this;
    }
    /**
     * @param string|array $tag set allowed tag xml
     */
    public function allowedTag($tag)
    {
        if (is_array($tag)) {
            $this->allowedTag = $tag;
        } else {
            $this->allowedTag = [$tag];
        }
        return $this;
    }

    /**
     * @param string|array $tag add more allowed tag xml
     */
    public function addAllowedTag($tag)
    {
        if (is_array($tag)) {
            $this->allowedTag = [...$this->allowedTag, ...$tag];
        } else {
            $this->allowedTag[] = $tag;
        }
        return $this;
    }
    /**
     * @param $parse true for parsed html to data array
     */
    public function run($parse = true)
    {
        $this->extract();
        $this->filterFile();
        $this->filterHtml();
        $this->html = $this->BlipToImage($this->html);
        $this->html = $this->OleToMp3($this->html);
        if ($this->isPublic) $this->html =  str_replace(FCPATH, "../../../", $this->html);
        $this->convertTable();
        return $parse ? $this->parseSoal($this->html) : $this->html;
    }
    static function parseSoal($str)
    {
        $alias = [1 => "A", "B", "C", "D", "E"];
        $data = [];
        $str = preg_replace("/Q(\d+)&gt;:/", "<p>Q&gt;:", $str);
        $text = explode("<p>Q&gt;:", $str);
        foreach ($text as $k => $question) {
            $question  = trim($question);
            if (!$question || $question === " " || $question == "<p></p>") continue;
            $question = "<p>$question";
            $keys = [];
            $pt = 1;
            $pf = 0;
            $ispgtf = false;
            $d = ["question" => '', "responds" => [], "shortentry" => null, "couples" => [], "type" => 2, 'true_default_point' => $pt, 'false_default_point' => $pf];
            if (preg_match('/<p>PT&gt;:(?<pt>(.*?))<\/p>/', $question, $matches)) {
                $d['true_default_point'] = trim($matches["pt"]);
                $question = str_replace($matches[0], "", $question);
            }
            if (preg_match('/<p>PF&gt;:(?<pf>(.*?))<\/p>/', $question, $matches)) {
                $d['false_default_point'] = trim($matches["pf"]);
                $question = str_replace($matches[0], "", $question);
            }
            if (preg_match('/<p>SE&gt;:(?<shortentry>(.*?))<\/p>/', $question, $matches)) {
                $d['type'] = 5;
                $d['shortentry'] = strip_tags($matches["shortentry"]);
                $question = str_replace($matches[0], "", $question);
            }
            if (preg_match('/<p>K&gt;:(?<keys>(.*?))<\/p>/', $question, $matches)) {
                $keys = explode(",", trim($matches["keys"]));
                $question = str_replace($matches[0], "", $question);
                if (isset($keys[0])) $ispgtf  = preg_match('/(.*)-(\d)/', $keys[0], $matches);
            }
            $responds = explode("<p>A&gt;:", $question);
            if (count($responds) == 1) $responds = explode("A&gt;:", $question);
            $couples = explode("<p>B&gt;:", $question);
            if (count($couples) == 1) $couples = explode("B&gt;:", $question);
            $coupledKeys = [];
            if (count($couples) > 1) {
                $d['type'] = 4;
                foreach ($couples as $sk => $c) {
                    if ($sk > 5) continue;
                    if (!$d['question'])  $d['question'] = implode("<p>A&gt;:", $responds);
                    $d['question'] = str_replace("<p>B&gt;:" . $c, "", $d['question']);
                    if ($sk == 0) continue;
                    $c = explode("<p>A&gt;:", $c);
                    $c = $c[0];
                    $cid = uniqid();
                    $d['couples'][] = [
                        'id' => $cid,
                        'body' => $c,
                    ];
                    foreach ($keys as $kk => $kv) {
                        if (strlen($kv) != 3) continue;
                        $skv = explode('-', $kv);
                        if ($skv[0] == $alias[$sk]) {
                            $coupledKeys[$skv[1]] = $cid;
                            break;
                        }
                    }
                }
            }
            $rk = 0;
            foreach ($responds as $sk => $r) {
                if ($sk > 5) continue;
                $d['question'] = str_replace("<p>A&gt;:" . $r, "", $d['question']);
                if ($sk == 0) {
                    if (!$d['question']) $d['question'] = $r;
                    continue;
                }
                if ($ispgtf) {
                    foreach ($keys as $kkk => $vvv) {
                        $kvv = explode('-', $vvv);
                        if ($alias[$sk] == $kvv[0]) {
                            $ik = $kvv[1];
                            break;
                        }
                    }
                } else $ik = in_array($alias[$sk], $keys);
                $d['responds'][] = [
                    "soal_couple_id" => isset($coupledKeys[$alias[$sk]]) ? $coupledKeys[$alias[$sk]] : null,
                    "body" => "<p>$r",
                    "is_key" => $ik
                ];
                if ($ik) $rk++;
                if ($rk == 1 && $d['type'] != 4) $d['type'] = 1;
                if ($rk > 1 && $d['type'] != 4) $d['type'] = 3;
                if ($ispgtf) $d['type'] = 6;
            }
            $data[] = $d;
        }
        return $data;
    }


    private function filterFile()
    {
        $xml = simplexml_load_file($this->word_xml_relational);
        foreach ($xml as $k => $v) {
            $ext = strtolower(pathinfo($v['Target'], PATHINFO_EXTENSION));
            if (in_array($ext, $this->supported_file)) {
                $id = xml_attribute($v, 'Id');
                $target = xml_attribute($v, 'Target');
                $f = "{$this->tempFol}/word/$target";
                $this->relation_file[$id] = $ext == "bin" ? $this->moveAudio($f) : $this->moveImage($f);
            }
        }
    }
    private function moveImage($file)
    {
        if (!file_exists($file)) return false;
        $name = $this->randomNameFile ? uniqid() . "." . strtolower(pathinfo($file, PATHINFO_EXTENSION)) : basename($file);
        $new_file = rtrim(($this->assetPath['image'] ?? $this->assetPath), "/") . "/" . $name;
        if (!rename($file, $new_file)) return false;
        return $new_file;
    }
    private function moveAudio($file)
    {
        if (!file_exists($file)) return false;
        $name = $this->randomNameFile ? uniqid() . "." . strtolower(pathinfo($file, PATHINFO_EXTENSION)) : basename($file);
        $new_file = rtrim(($this->assetPath['audio'] ?? $this->assetPath), "/") . "/" . str_replace(".bin", ".mp3", $name);
        if (!rename($file, $new_file)) return false;
        return $new_file;
    }
    private function filterHtml()
    {
        $this->html = file_get_contents($this->word_xml);
        $this->html = preg_replace("/<wp:posOffset>(.*?)<\/wp:posOffset>/", '<p></p>', $this->html);
        $this->html = preg_replace("/<w:t>([^)]+)\)(.*?)<\/w:t>/", '<p>${1})${2}</p>', $this->html);
        $this->html = str_replace(["</w:t><w:t>"], ["</p><p>"], $this->html);
        $this->html = strip_tags($this->html, $this->allowedTag);
        $this->html = str_replace(["<w:t>", "</w:t>"], ["<p>", "</p>"], $this->html);
        $this->html = preg_replace($this->xmlToHtml['xml'], $this->xmlToHtml['html'], $this->html);
    }
    private function extract()
    {
        $za = new ZipArchive();
        $za->open($this->srcFile);
        $za->extractTo($this->tempFol);
    }

    private function convertTable()
    {
        $style = "border: 1px solid black;border-collapse: collapse;";
        $rpl = [
            "xml" => [
                "/<w:tbl(.*?)>/", "/<w:tr(.*?)>/", "/<w:tc(.*?)>/",
                "/<\/w:tbl(.*?)>/", "/<\/w:tr(.*?)>/", "/<\/w:tc(.*?)>/",
            ],
            "html" => [
                "<table style=\"$style\">", "<tr style=\"$style\">", "<td style=\"$style\">",
                "</table>", "</tr>", "</td>",
            ]
        ];
        $this->html = preg_replace($rpl['xml'], $rpl['html'], $this->html);
    }

    private function BlipToImage($str, $file = null)
    {
        foreach (($file ?? $this->relation_file) as $k => $f) {
            $rplc_str = [
                '/<a:blip r:embed="' . $k . '" cstate="print"\/>/',
                '/<a:blip r:embed="' . $k . '"><\/a:blip>/',
                '/<a:blip r:embed="' . $k . '"\/>/'
            ];
            $str = preg_replace($rplc_str, htmlIMAGE($f), $str);
        }
        return $str;
    }

    private function OleToMp3($str, $file = null)
    {
        $file = ($file ?? $this->relation_file);
        if (preg_match_all('/<o:OLEObject(.*?)\/>/', $str, $matches)) {
            foreach ($matches[0] as $k => $v) {
                if (preg_match('/r:id="(.*?)"/', $v, $matches)) {
                    if (isset($file[$matches[1]])) {
                        $str = str_replace($v, htmlMP3($file[$matches[1]]), $str);
                    }
                }
            }
        }
        return $str;
    }

    protected function rrmdir($src)
    {
        $dir = opendir($src);
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                $full = $src . '/' . $file;
                if (is_dir($full)) {
                    $this->rrmdir($full);
                } else {
                    unlink($full);
                }
            }
        }
        closedir($dir);
        rmdir($src);
        return true;
    }
}
