<?php

if (!defined('ABSPATH')) { exit; }

class AIEO_DMM_XLSX_Writer {

    
    const STYLE_DEFAULT  = 0;
    const STYLE_HEADER   = 1;
    const STYLE_CURRENCY = 2;
    const STYLE_INTEGER  = 3;
    const STYLE_NUMBER   = 4;

    private $sheet_name;
    private $rows_xml = '';
    private $row_index = 0;
    private $shared_strings = array();   
    private $shared_strings_count = 0;

    public function __construct($sheet_name = 'Sheet1') {
        
        $sheet_name = preg_replace('#[/\\\\\\?\\*\\[\\]:]#', ' ', (string) $sheet_name);
        $this->sheet_name = mb_substr($sheet_name, 0, 31);
    }

    public function add_header_row(array $cells) {
        $this->row_index++;
        $this->rows_xml .= '<row r="' . $this->row_index . '">';
        foreach ($cells as $i => $val) {
            $this->rows_xml .= $this->cell_xml($i, $this->row_index, (string) $val, 'string', self::STYLE_HEADER);
        }
        $this->rows_xml .= '</row>';
    }

    
    public function add_row(array $cells, array $formats = array()) {
        $this->row_index++;
        $this->rows_xml .= '<row r="' . $this->row_index . '">';
        foreach ($cells as $i => $val) {
            $fmt = isset($formats[$i]) ? $formats[$i] : 'string';
            $style = self::STYLE_DEFAULT;
            if ($fmt === 'currency') { $style = self::STYLE_CURRENCY; }
            elseif ($fmt === 'integer') { $style = self::STYLE_INTEGER; }
            elseif ($fmt === 'number') { $style = self::STYLE_NUMBER; }
            $this->rows_xml .= $this->cell_xml($i, $this->row_index, $val, $fmt, $style);
        }
        $this->rows_xml .= '</row>';
    }

    
    public function stream_download($filename) {
        $tmp = wp_tempnam('aieo-xlsx-');
        $zip = new ZipArchive();
        $zip->open($tmp, ZipArchive::OVERWRITE);
        $zip->addFromString('[Content_Types].xml',         $this->content_types_xml());
        $zip->addFromString('_rels/.rels',                 $this->root_rels_xml());
        $zip->addFromString('xl/_rels/workbook.xml.rels',  $this->workbook_rels_xml());
        $zip->addFromString('xl/workbook.xml',             $this->workbook_xml());
        $zip->addFromString('xl/styles.xml',               $this->styles_xml());
        $zip->addFromString('xl/sharedStrings.xml',        $this->shared_strings_xml());
        $zip->addFromString('xl/worksheets/sheet1.xml',    $this->sheet_xml());
        $zip->close();

        nocache_headers();
        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        header('Content-Disposition: attachment; filename="' . $this->safe_filename($filename) . '"');
        header('Content-Length: ' . filesize($tmp));
        readfile($tmp);
        @wp_delete_file($tmp);
        exit;
    }

    

    private function safe_filename($name) {
        $name = preg_replace('#[^A-Za-z0-9._-]+#', '_', (string) $name);
        if ($name === '' || $name === false) { $name = 'export.xlsx'; }
        if (!preg_match('/\.xlsx$/i', $name)) { $name .= '.xlsx'; }
        return $name;
    }

    private function shared_string_index($value) {
        if (!isset($this->shared_strings[$value])) {
            $this->shared_strings[$value] = $this->shared_strings_count++;
        }
        return $this->shared_strings[$value];
    }

    private function cell_xml($col_index, $row_index, $value, $fmt, $style_index) {
        $col = $this->col_letters($col_index);
        $ref = $col . $row_index;
        $style_attr = $style_index > 0 ? ' s="' . $style_index . '"' : '';

        if ($fmt === 'number' || $fmt === 'currency' || $fmt === 'integer') {
            
            if ($value === '' || $value === null) {
                return '<c r="' . $ref . '"' . $style_attr . '/>';
            }
            $num = is_numeric($value) ? (float) $value : 0;
            
            if ($fmt === 'integer') {
                $str = (string) (int) round($num);
            } else {
                $str = rtrim(rtrim(sprintf('%.4f', $num), '0'), '.');
                if ($str === '' || $str === '-') { $str = '0'; }
            }
            return '<c r="' . $ref . '"' . $style_attr . '><v>' . $str . '</v></c>';
        }

        
        $idx = $this->shared_string_index((string) $value);
        return '<c r="' . $ref . '" t="s"' . $style_attr . '><v>' . $idx . '</v></c>';
    }

    private function col_letters($zero_based) {
        $n = (int) $zero_based;
        $s = '';
        do {
            $s = chr(65 + ($n % 26)) . $s;
            $n = intdiv($n, 26) - 1;
        } while ($n >= 0);
        return $s;
    }

    private function content_types_xml() {
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
            . '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
            . '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
            . '<Default Extension="xml" ContentType="application/xml"/>'
            . '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
            . '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
            . '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'
            . '<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>'
            . '</Types>';
    }

    private function root_rels_xml() {
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
            . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
            . '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
            . '</Relationships>';
    }

    private function workbook_rels_xml() {
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
            . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
            . '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
            . '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'
            . '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>'
            . '</Relationships>';
    }

    private function workbook_xml() {
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
            . '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"'
            . ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
            . '<sheets><sheet name="' . $this->xml_attr($this->sheet_name) . '" sheetId="1" r:id="rId1"/></sheets>'
            . '</workbook>';
    }

    private function styles_xml() {
        
        
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
            . '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
            . '<numFmts count="3">'
            . '<numFmt numFmtId="164" formatCode="&quot;&#8364;&quot;#,##0.00;[Red]&quot;-&#8364;&quot;#,##0.00"/>'
            . '<numFmt numFmtId="165" formatCode="#,##0"/>'
            . '<numFmt numFmtId="166" formatCode="#,##0.00"/>'
            . '</numFmts>'
            . '<fonts count="2">'
            . '<font><sz val="11"/><name val="Calibri"/></font>'
            . '<font><b/><sz val="11"/><name val="Calibri"/></font>'
            . '</fonts>'
            . '<fills count="3">'
            . '<fill><patternFill patternType="none"/></fill>'
            . '<fill><patternFill patternType="gray125"/></fill>'
            . '<fill><patternFill patternType="solid"><fgColor rgb="FFE5E7EB"/><bgColor indexed="64"/></patternFill></fill>'
            . '</fills>'
            . '<borders count="1"><border/></borders>'
            . '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>'
            . '<cellXfs count="5">'
            . '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>'
            . '<xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1"/>'
            . '<xf numFmtId="164" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>'
            . '<xf numFmtId="165" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>'
            . '<xf numFmtId="166" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>'
            . '</cellXfs>'
            . '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>'
            . '</styleSheet>';
    }

    private function shared_strings_xml() {
        $count = $this->shared_strings_count;
        $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
             . '<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"'
             . ' count="' . $count . '" uniqueCount="' . $count . '">';
        foreach ($this->shared_strings as $s => $_idx) {
            $xml .= '<si><t xml:space="preserve">' . $this->xml_text($s) . '</t></si>';
        }
        $xml .= '</sst>';
        return $xml;
    }

    private function sheet_xml() {
        return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
            . '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
            . '<sheetData>' . $this->rows_xml . '</sheetData>'
            . '</worksheet>';
    }

    private function xml_text($s) {
        
        $s = preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/', '', (string) $s);
        return htmlspecialchars((string) $s, ENT_XML1 | ENT_QUOTES, 'UTF-8');
    }

    private function xml_attr($s) {
        return $this->xml_text($s);
    }
}
