<?php
/**
 * HTML Compressor Class
 * 全站HTML代码压缩算法
 * 
 * @package Nenghui Energy Theme
 * @since 1.0.0
 */

// 防止直接访问
if (!defined('ABSPATH')) {
    exit;
}

class NenghuiHTMLCompressor {
    
    /**
     * 压缩配置选项
     */
    private $options = array(
        'remove_comments' => true,
        'remove_whitespace' => true,
        'compress_css' => true,
        'compress_js' => true,
        'preserve_line_breaks' => false,
        'preserve_pre_content' => true,
        'preserve_textarea_content' => true,
        'preserve_script_content' => true,
        'preserve_style_content' => true
    );
    
    /**
     * 需要保护的标签内容
     */
    private $preserve_tags = array('pre', 'textarea', 'script', 'style');
    
    /**
     * 保护的内容存储
     */
    private $preserved_content = array();
    
    /**
     * 构造函数
     */
    public function __construct($options = array()) {
        $this->options = array_merge($this->options, $options);
    }
    
    /**
     * 主要压缩方法
     * 
     * @param string $html HTML内容
     * @return string 压缩后的HTML
     */
    public function compress($html) {
        if (empty($html)) {
            return $html;
        }
        
        // 重置保护内容
        $this->preserved_content = array();
        
        // 1. 保护需要保留原始格式的内容
        $html = $this->preserveContent($html);
        
        // 2. 移除HTML注释
        if ($this->options['remove_comments']) {
            $html = $this->removeComments($html);
        }
        
        // 3. 压缩空白字符
        if ($this->options['remove_whitespace']) {
            $html = $this->compressWhitespace($html);
        }
        
        // 4. 恢复保护的内容
        $html = $this->restoreContent($html);
        
        // 5. 压缩内联CSS和JS
        if ($this->options['compress_css'] || $this->options['compress_js']) {
            $html = $this->compressInlineCode($html);
        }
        
        return trim($html);
    }
    
    /**
     * 保护需要保留原始格式的内容
     * 
     * @param string $html
     * @return string
     */
    private function preserveContent($html) {
        $index = 0;
        
        // 保护 <pre> 标签内容
        if ($this->options['preserve_pre_content']) {
            $html = preg_replace_callback(
                '/<pre\b[^>]*>.*?<\/pre>/is',
                function($matches) use (&$index) {
                    $placeholder = "<!--NENGHUI_PRESERVE_PRE_{$index}-->";
                    $this->preserved_content[$placeholder] = $matches[0];
                    $index++;
                    return $placeholder;
                },
                $html
            );
        }
        
        // 保护 <textarea> 标签内容
        if ($this->options['preserve_textarea_content']) {
            $html = preg_replace_callback(
                '/<textarea\b[^>]*>.*?<\/textarea>/is',
                function($matches) use (&$index) {
                    $placeholder = "<!--NENGHUI_PRESERVE_TEXTAREA_{$index}-->";
                    $this->preserved_content[$placeholder] = $matches[0];
                    $index++;
                    return $placeholder;
                },
                $html
            );
        }
        
        // 保护 <script> 标签内容
        if ($this->options['preserve_script_content']) {
            $html = preg_replace_callback(
                '/<script\b[^>]*>.*?<\/script>/is',
                function($matches) use (&$index) {
                    $placeholder = "<!--NENGHUI_PRESERVE_SCRIPT_{$index}-->";
                    $this->preserved_content[$placeholder] = $matches[0];
                    $index++;
                    return $placeholder;
                },
                $html
            );
        }
        
        // 保护 <style> 标签内容
        if ($this->options['preserve_style_content']) {
            $html = preg_replace_callback(
                '/<style\b[^>]*>.*?<\/style>/is',
                function($matches) use (&$index) {
                    $placeholder = "<!--NENGHUI_PRESERVE_STYLE_{$index}-->";
                    $this->preserved_content[$placeholder] = $matches[0];
                    $index++;
                    return $placeholder;
                },
                $html
            );
        }
        
        return $html;
    }
    
    /**
     * 移除HTML注释
     * 
     * @param string $html
     * @return string
     */
    private function removeComments($html) {
        // 移除HTML注释，但保留条件注释
        $html = preg_replace('/<!--(?!\s*(?:\[if [^\]]+]|<!|>))(?:(?!-->).)*-->/s', '', $html);
        
        return $html;
    }
    
    /**
     * 压缩空白字符
     * 
     * @param string $html
     * @return string
     */
    private function compressWhitespace($html) {
        // 移除标签之间的空白字符
        $html = preg_replace('/>\s+</', '><', $html);
        
        // 压缩多个空白字符为单个空格
        $html = preg_replace('/\s+/', ' ', $html);
        
        // 移除标签开始和结束处的空白
        $html = preg_replace('/\s*(<[^>]+>)\s*/', '$1', $html);
        
        // 移除行首行尾空白
        $html = preg_replace('/^\s+|\s+$/m', '', $html);
        
        if (!$this->options['preserve_line_breaks']) {
            // 移除换行符
            $html = str_replace(array("\r\n", "\r", "\n"), '', $html);
        }
        
        return $html;
    }
    
    /**
     * 恢复保护的内容
     * 
     * @param string $html
     * @return string
     */
    private function restoreContent($html) {
        foreach ($this->preserved_content as $placeholder => $content) {
            $html = str_replace($placeholder, $content, $html);
        }
        
        return $html;
    }
    
    /**
     * 压缩内联CSS和JavaScript代码
     * 
     * @param string $html
     * @return string
     */
    private function compressInlineCode($html) {
        // 压缩内联CSS
        if ($this->options['compress_css']) {
            $html = preg_replace_callback(
                '/<style\b[^>]*>(.*?)<\/style>/is',
                array($this, 'compressCSS'),
                $html
            );
            
            // 压缩style属性中的CSS
            $html = preg_replace_callback(
                '/style\s*=\s*["\']([^"\']*)["\']/',
                function($matches) {
                    return 'style="' . $this->minifyCSS($matches[1]) . '"';
                },
                $html
            );
        }
        
        // 压缩内联JavaScript
        if ($this->options['compress_js']) {
            $html = preg_replace_callback(
                '/<script\b[^>]*>(.*?)<\/script>/is',
                array($this, 'compressJS'),
                $html
            );
        }
        
        return $html;
    }
    
    /**
     * 压缩CSS代码
     * 
     * @param array $matches
     * @return string
     */
    private function compressCSS($matches) {
        $css = $matches[1];
        $css = $this->minifyCSS($css);
        return '<style' . (isset($matches[0]) ? substr($matches[0], 6, strpos($matches[0], '>') - 6) : '') . '>' . $css . '</style>';
    }
    
    /**
     * CSS压缩核心方法
     * 
     * @param string $css
     * @return string
     */
    private function minifyCSS($css) {
        // 移除注释
        $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
        
        // 移除多余的空白字符
        $css = preg_replace('/\s+/', ' ', $css);
        
        // 移除不必要的空格
        $css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
        
        // 移除最后一个分号
        $css = preg_replace('/;}/', '}', $css);
        
        // 移除空的CSS规则
        $css = preg_replace('/[^{}]+{\s*}/', '', $css);
        
        return trim($css);
    }
    
    /**
     * 压缩JavaScript代码
     * 
     * @param array $matches
     * @return string
     */
    private function compressJS($matches) {
        $js = $matches[1];
        
        // 检查是否有src属性，如果有则不压缩内容
        if (preg_match('/src\s*=/', $matches[0])) {
            return $matches[0];
        }
        
        $js = $this->minifyJS($js);
        return '<script' . (isset($matches[0]) ? substr($matches[0], 7, strpos($matches[0], '>') - 7) : '') . '>' . $js . '</script>';
    }
    
    /**
     * JavaScript压缩核心方法
     * 
     * @param string $js
     * @return string
     */
    private function minifyJS($js) {
        // 移除单行注释
        $js = preg_replace('/\/\/.*$/m', '', $js);
        
        // 移除多行注释
        $js = preg_replace('/\/\*[\s\S]*?\*\//', '', $js);
        
        // 移除多余的空白字符
        $js = preg_replace('/\s+/', ' ', $js);
        
        // 移除不必要的空格
        $js = preg_replace('/\s*([{}:;,=()[\]<>!&|+\-*\/])\s*/', '$1', $js);
        
        return trim($js);
    }
    
    /**
     * 获取压缩统计信息
     * 
     * @param string $original
     * @param string $compressed
     * @return array
     */
    public function getCompressionStats($original, $compressed) {
        $original_size = strlen($original);
        $compressed_size = strlen($compressed);
        $saved_bytes = $original_size - $compressed_size;
        $compression_ratio = $original_size > 0 ? ($saved_bytes / $original_size) * 100 : 0;
        
        return array(
            'original_size' => $original_size,
            'compressed_size' => $compressed_size,
            'saved_bytes' => $saved_bytes,
            'compression_ratio' => round($compression_ratio, 2)
        );
    }
    
    /**
     * 设置压缩选项
     * 
     * @param array $options
     */
    public function setOptions($options) {
        $this->options = array_merge($this->options, $options);
    }
    
    /**
     * 获取当前压缩选项
     * 
     * @return array
     */
    public function getOptions() {
        return $this->options;
    }
}