<?php

use PhpOffice\PhpSpreadsheet\IOFactory;

/**
 * AutomaticImportTransform.
 *
 * @author     Petr Knap <knap@wpj.cz>
 *
 * @category   Import
 */
class AutomaticImportTransform
{
    private $pathToFile;
    private $typeOfFile;
    private $rootElementName = 'xml';
    private $headElementName = 'head';
    private $itemElementName = 'item';
    private $colNames = [];
    private $encoding;

    /**
     * Zkouška existence souboru na disku.
     *
     * @param string $iPathToFile cesta k souboru na pevném disku
     *
     * @return bool
     */
    private static function TestFile($iPathToFile)
    {
        return file_exists($iPathToFile);
    }

    /**
     * Konstruktor třídy.
     *
     * @param string      $iPathToFile cesta k souboru na pevném disku
     * @param string|null $iTypeOfFile typ souboru (null = automatická detekce)
     */
    public function __construct($iPathToFile, $iTypeOfFile = null)
    {
        if (self::TestFile($iPathToFile)) {
            $this->pathToFile = $iPathToFile;
            if ($iTypeOfFile === null) {
                $this->typeOfFile = IOFactory::identify($this->pathToFile);
            } else {
                $this->typeOfFile = $iTypeOfFile;
            }
        }
    }

    /**
     * Setter pro $rootElementName.
     *
     * @param string $iName název XML root tagu
     *
     * @return bool
     */
    public function setRootElementName($iName)
    {
        if (is_string($iName) && !preg_match('/[^A-Za-z0-9]/', $iName)) {
            $this->rootElementName = $iName;

            return true;
        }

        return false;
    }

    /**
     * Setter pro $headElementName.
     *
     * @param string $iName název XML head tagu
     *
     * @return bool
     */
    public function setHeadElementName($iName)
    {
        if (is_string($iName) && !preg_match('/[^A-Za-z0-9]/', $iName)) {
            $this->headElementName = $iName;

            return true;
        }

        return false;
    }

    /**
     * Setter pro $itemElementName.
     *
     * @param string $iName název XML item tagu
     *
     * @return bool
     */
    public function setItemElementName($iName)
    {
        if (is_string($iName) && !preg_match('/[^A-Za-z0-9]/', $iName)) {
            $this->itemElementName = $iName;

            return true;
        }

        return false;
    }

    /**
     * Setter pro $colNames.
     *
     * @param array(string) $iNames Pole stringů obsahující názvy XML subitem tagy. Pole je ve tvaru $index => $nazev, např. array(0 => 'id', 1 => 'name',...).
     *
     * @return bool
     */
    public function setColNames($iNames)
    {
        if (is_array($iNames)) {
            foreach ($iNames as $name) {
                if (!is_string($name) || preg_match('/[^A-Za-z0-9_]/', $name)) {
                    return false;
                }
            }
            $this->colNames = $iNames;

            return true;
        }

        return false;
    }

    public function setEncoding($encoding)
    {
        if (empty($encoding)) {
            return;
        }

        switch ($this->typeOfFile) {
            case 'HTML':
            case 'Csv':
                ob_start(); // start output buffering

                readfile($this->pathToFile);

                // save buffer in a file
                $buffer = ob_get_clean();
                @ob_end_clean();
                $buffer = iconv($encoding, 'UTF-8', $buffer);
                if ($buffer !== false) {
                    $this->pathToFile = "{$this->pathToFile}.utf8";
                    file_put_contents($this->pathToFile, $buffer);
                }
                break;

            case 'dbf':
                $this->encoding = $encoding;
                break;
        }
    }

    /**
     * Metoda pro načtení binárního tabulkového souboru (XLS, XLSX, ODS, atd.) do 3D pole.
     *
     * @return array|null
     */
    public function LoadBinfileAsArray()
    {
        if (!self::TestFile($this->pathToFile)) {
            return null;
        }
        $output = [];
        $objReader = IOFactory::createReader($this->typeOfFile);
        $objReader->setLoadAllSheets();
        $objPHPExcel = $objReader->load($this->pathToFile);
        $loadedSheetNames = $objPHPExcel->getSheetNames();
        foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) {
            $objPHPExcel->setActiveSheetIndex($sheetIndex);
            $output[$loadedSheetName] = $objPHPExcel->getActiveSheet()->rangeToArray($objPHPExcel->getActiveSheet()->calculateWorksheetDataDimension(), null, true, true, true);
        }

        return $output;
    }

    /**
     * Metoda pro načtení CSV tabulkového souboru do 2D pole.
     *
     * @param string|null $iItemSeparator oddělovač položek v řádku
     * @param string|null $iLineSeparator oddělovač řádků
     *
     * @return array|null
     */
    public function LoadCsvfileAsArray($iItemSeparator = null, $iLineSeparator = null)
    {
        if (!self::TestFile($this->pathToFile)) {
            return null;
        }
        $output = [];
        if ($iItemSeparator === null) {
            $ItemSeparator = ';';
        } else {
            $ItemSeparator = $iItemSeparator;
        }
        if ($iLineSeparator === null) {
            $LineSeparator = "\n";
        } else {
            $LineSeparator = $iLineSeparator;
        }

        $handle = fopen($this->pathToFile, 'r');
        while (($row = fgetcsv($handle, null, $ItemSeparator, '"')) != false) {
            $output[] = $row;
        }
        fclose($handle);

        return $output;
    }

    /**
     * Metoda pro převod tabulkového souboru na XML.
     *
     * Základní XML struktura je následující:
     * <xml>
     *    <head>
     *       <col>...</col>
     *       ...
     *       <col>...</col>
     *    </head>
     *    <item>
     *       <col>...</col>
     *       ...
     *       <col>...</col>
     *    </item>
     *    ...
     *    <item>...</item>
     * </xml>
     * Tuto strukturu lze modifikovat pomocí metod setRootElementName, setHeadElementName, setItemElementName a setColNames.
     *
     * @param int         $iFirstDataLine            index prvního datového řádku (číslováno od 1)
     * @param string|null $iSheetName_iItemSeparator název sešitu nebo znak oddělující položky na řádku
     * @param array       $params                    další parametry s případnou konfigurací pro načtení souboru
     *
     * @return string|null
     */
    public function GetXml($iHeaderLine = 0, $iFirstDataLine = 1, $iSheetName_iItemSeparator = null, array $params = [])
    {
        if (!self::TestFile($this->pathToFile)) {
            return null;
        }
        $output = new SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><{$this->rootElementName}/>");
        switch ($this->typeOfFile) {
            case 'Xls':
            case 'Xml':
            case 'Xlsx':
            case 'Ods':
                $data = self::LoadBinfileAsArray();
                if ($iSheetName_iItemSeparator === null) {
                    $values = array_values($data);
                    $data = array_shift($values);
                } else {
                    $data = $data[$iSheetName_iItemSeparator];
                }
                break;

            case 'json':
                $data = json_decode(file_get_contents($this->pathToFile), true);
                break;

            case 'dbf':
                $data = $this->LoadDBFDatabase();
                break;

            default: // CSV
                $data = self::LoadCsvfileAsArray($iSheetName_iItemSeparator);
                $iFirstDataLine--;
                $iHeaderLine--;
                break;
        }

        if ($data === null) {
            return null;
        }

        // vložení hlavičky
        if ($iHeaderLine > 0 || ($params['forceHeaderLine'] ?? null) !== null) {
            $i = 0;
            $heads = $data[$params['forceHeaderLine'] ?? $iHeaderLine];
            $head = $output->addChild($this->headElementName);
            if (count($heads) == 1 && strpos($heads[0], 'columns:') === 0) { // Úprava pro WinShop
                $heads = explode(
                    ';',
                    trim(
                        str_replace('columns:', '', $heads[0])
                    )
                );
            }
            foreach ($heads as $name) {
                if (empty($this->colNames[$i])) {
                    $this->colNames[$i] = empty($name) ? 'col' : createScriptURL_Text($name);
                }
                $head->addChild($this->colNames[$i], $name);
                $i++;
            }
        }

        // odtržení úvodní části
        for ($i = 1; $i < $iFirstDataLine; $i++) {
            array_shift($data);
        }

        // sestavení datové části
        foreach ($data as $A => $B) {
            $item = $output->addChild($this->itemElementName);
            $this->addItemChildren($item, $B);
        }

        return $output;
    }

    public function addItemChildren($parent, $data)
    {
        $i = 0;
        foreach ($data as $key => $item) {
            if (empty($this->colNames[$i])) {
                $this->colNames[$i] = 'col';
            }

            $colName = $this->colNames[$i];
            if ($this->typeOfFile == 'json') {
                $colName = $key;
                if (is_numeric($key)) {
                    $colName = $this->itemElementName;
                }
            }

            if (is_array($item)) {
                $newParent = @$parent->addChild($colName);
                $this->addItemChildren($newParent, $item);
            } else {
                @$parent->addChild($colName, strtr($item, ['&' => '&amp;', '>' => '&gt;', '<' => '&lt;']));
            }

            $i++;
        }
    }

    /**
     * Metoda pro převod XML souboru za využití XSL.
     *
     * @param DomDocument $xsl     xSL šablona
     * @param DomDocument $xml_doc XML dokument
     *
     * @return DomDocument|void|null
     */
    public static function TransformXml($xsl, $xml_doc)
    {
        $xp = new XSLTProcessor();
        $xp->registerPHPFunctions(['xsl_replace', 'count_same']);
        $xp->importStylesheet($xsl);

        if ($output = $xp->transformToDoc($xml_doc)) {
            return $output;
        } else {
            trigger_error('XSL transformation failed.', E_USER_ERROR);
        }
    }

    public static function LoadXMLFromFile($fileName)
    {
        if (!self::TestFile($fileName)) {
            trigger_error('Cannot load XML file {$fileName}.', E_USER_ERROR);

            return null;
        }

        $xml = new DOMDocument();
        @$xml->load($fileName);

        return $xml;
    }

    public function LoadDBFDatabase()
    {
        $table = new XBase\Table($this->pathToFile);

        $columns = [];

        foreach ($table->getColumns() as $name => $column) {
            $columns[] = preg_replace('/[^[:print:]\p{L}]/u', '', $name);
        }

        if (count($columns) <= 0) {
            throw new Exception('Empty DBF file');
        }
        $this->setColNames($columns);

        $data = [];
        while ($record = $table->nextRecord()/* && $i++ < 1000 */) {
            $row = [];
            foreach ($table->getColumns() as $name => $column) {
                if ($this->encoding && $column->getType() == XBase\Record::DBFFIELD_TYPE_CHAR) {
                    $row[] = preg_replace('/[^[:print:]\p{L}]/u', '', iconv($this->encoding, 'UTF-8', $record->getString($name) ?? ''));
                } else {
                    $row[] = $record->getString($name);
                }
            }
            $data[] = $row;
        }

        return $data;
    }
}

/**
 * Replace all occurrences of the search string with the replacement string.
 *
 * @param mixed $search  The value being searched for, otherwise known as the needle. An array may be used to designate multiple needles.
 * @param mixed $replace The replacement value that replaces found search values. An array may be used to designate multiple replacements.
 * @param mixed $subject the string, DOM element or array being searched and replaced on, otherwise known as the haystack
 * @param int   $count   if passed, this will be set to the number of replacements performed
 *
 * @return mixed this function returns a string with the replaced values
 */
function xsl_replace($search, $replace, $subject, &$count = null)
{
    if (is_array($subject)) {
        $subject = $subject[0];
    }
    if (!is_string($subject)) {
        $subject = $subject->textContent;
    }

    return str_replace($search, $replace, $subject, $count);
}

/**
 * @param DOMElement $node
 */
function count_same($node, $attr)
{
    if (is_array($node)) {
        $node = $node[0];
    }
    var_dump($node->firstChild->textContent);
    exit;
    $next = $node;
    $value = $node->getAttribute($attr);
    var_dump([$value, $attr]);
    $count = 1;
    while ($next && $next->getAttribute($attr) == $value) {
        var_dump([$count, $next->getAttribute($attr)]);
        $count++;
        $next = $next->nextSibling;
    }
    exit(var_dump($count));
}
