<?php
if (!defined('ABSPATH')) {
    exit;
}

//2FA Core - MoeSec.com - Oct 2025
class MoeSec_QRCode {
    private $size = 250;
    private $margin = 10;
    
    /**
     * Generate QR code as data URL
     */
    public function generate_svg($data, $size = 250) {
        
        $encoded_data = urlencode($data);
        $qr_url = "https://api.qrserver.com/v1/create-qr-code/?size={$size}x{$size}&data={$encoded_data}";
        
        // Return an img tag with the QR code
        return sprintf(
            '<img src="%s" alt="QR Code" width="%d" height="%d" style="display: block;" />',
            esc_url($qr_url),
            $size,
            $size
        );
    }
    
    private function encode_qr($data) {
        $data_length = strlen($data);
        $version = $this->get_version_for_length($data_length);
        
        if ($version === false) {
            return false;
        }
        
        $size = 17 + (4 * $version);
        
        $matrix = array_fill(0, $size, array_fill(0, $size, 0));
        
        $this->add_finder_pattern($matrix, 0, 0);
        $this->add_finder_pattern($matrix, $size - 7, 0);
        $this->add_finder_pattern($matrix, 0, $size - 7);
        
        $this->add_separators($matrix, $size);
        
        $this->add_timing_patterns($matrix, $size);
        
        $matrix[4 * $version + 9][8] = 1;
        
        $encoded_data = $this->encode_data($data, $version);
        
        $this->place_data($matrix, $encoded_data, $size);
        
        $this->apply_mask($matrix, $size, 0);
        
        $this->add_format_info($matrix, $size, 0);
        
        return $matrix;
    }
    
    private function get_version_for_length($length) {
        $capacities = array(
            1 => 14, 2 => 26, 3 => 42, 4 => 62, 5 => 84,
            6 => 106, 7 => 122, 8 => 152, 9 => 180, 10 => 213
        );
        
        foreach ($capacities as $version => $capacity) {
            if ($length <= $capacity) {
                return $version;
            }
        }
        
        return 10; 
    }
    
    
    private function add_finder_pattern(&$matrix, $row, $col) {
        $pattern = array(
            array(1,1,1,1,1,1,1),
            array(1,0,0,0,0,0,1),
            array(1,0,1,1,1,0,1),
            array(1,0,1,1,1,0,1),
            array(1,0,1,1,1,0,1),
            array(1,0,0,0,0,0,1),
            array(1,1,1,1,1,1,1)
        );
        
        for ($i = 0; $i < 7; $i++) {
            for ($j = 0; $j < 7; $j++) {
                if (isset($matrix[$row + $i][$col + $j])) {
                    $matrix[$row + $i][$col + $j] = $pattern[$i][$j];
                }
            }
        }
    }
    
    private function add_separators(&$matrix, $size) {
        for ($i = 0; $i < 8; $i++) {
            $matrix[7][$i] = 0;
            $matrix[$i][7] = 0;
        }
        
        for ($i = 0; $i < 8; $i++) {
            $matrix[7][$size - 8 + $i] = 0;
            $matrix[$i][$size - 8] = 0;
        }
        
        for ($i = 0; $i < 8; $i++) {
            $matrix[$size - 8][$i] = 0;
            $matrix[$size - 8 + $i][7] = 0;
        }
    }
    
    private function add_timing_patterns(&$matrix, $size) {
        for ($i = 8; $i < $size - 8; $i++) {
            $matrix[$i][6] = ($i % 2 === 0) ? 1 : 0;
            $matrix[6][$i] = ($i % 2 === 0) ? 1 : 0;
        }
    }
    
    private function encode_data($data, $version) {
        $mode = '0100'; 
        $length = strlen($data);
        $char_count_bits = 8; 
        
        $char_count = str_pad(decbin($length), $char_count_bits, '0', STR_PAD_LEFT);
        
        $data_bits = '';
        for ($i = 0; $i < $length; $i++) {
            $data_bits .= str_pad(decbin(ord($data[$i])), 8, '0', STR_PAD_LEFT);
        }
        
        $encoded = $mode . $char_count . $data_bits;
        
        $data_capacity = $this->get_data_capacity($version);
        $terminator_length = min(4, $data_capacity - strlen($encoded));
        $encoded .= str_repeat('0', $terminator_length);
        
        while (strlen($encoded) % 8 !== 0) {
            $encoded .= '0';
        }
        
        $pad_bytes = array('11101100', '00010001');
        $pad_index = 0;
        while (strlen($encoded) < $data_capacity) {
            $encoded .= $pad_bytes[$pad_index % 2];
            $pad_index++;
        }
        
        $encoded_with_ec = $this->add_error_correction($encoded, $version);
        
        return $encoded_with_ec;
    }
    private function get_data_capacity($version) {
        $capacities = array(
            1 => 152, 2 => 272, 3 => 440, 4 => 640, 5 => 864,
            6 => 1088, 7 => 1248, 8 => 1552, 9 => 1856, 10 => 2192
        );
        return $capacities[$version];
    }
    
    private function add_error_correction($data, $version) {
        $ec_length = array(
            1 => 10, 2 => 16, 3 => 26, 4 => 36, 5 => 48,
            6 => 60, 7 => 66, 8 => 86, 9 => 100, 10 => 122
        );
        
        $ec_bits = $ec_length[$version] * 8;
        $ec_data = str_repeat('0', $ec_bits);
        
        return $data . $ec_data;
    }
    
    private function place_data(&$matrix, $data, $size) {
        $data_length = strlen($data);
        $data_index = 0;
        
        for ($col = $size - 1; $col > 0; $col -= 2) {
            if ($col === 6) $col--; // Skip timing column
            
            for ($row = $size - 1; $row >= 0; $row--) {
                for ($c = 0; $c < 2; $c++) {
                    $current_col = $col - $c;
                    
                    if ($this->is_function_module($matrix, $row, $current_col, $size)) {
                        continue;
                    }
                    
                    if ($data_index < $data_length) {
                        $matrix[$row][$current_col] = ($data[$data_index] === '1') ? 1 : 0;
                        $data_index++;
                    } else {
                        $matrix[$row][$current_col] = 0;
                    }
                }
            }
            
            $col -= 2;
            if ($col === 6) $col--;
            
            for ($row = 0; $row < $size; $row++) {
                for ($c = 0; $c < 2; $c++) {
                    $current_col = $col - $c;
                    
                    if ($col < 0) break 3;
                    
                    if ($this->is_function_module($matrix, $row, $current_col, $size)) {
                        continue;
                    }
                    
                    if ($data_index < $data_length) {
                        $matrix[$row][$current_col] = ($data[$data_index] === '1') ? 1 : 0;
                        $data_index++;
                    } else {
                        $matrix[$row][$current_col] = 0;
                    }
                }
            }
        }
    }
    
    private function is_function_module($matrix, $row, $col, $size) {
        if (($row < 9 && $col < 9) ||
            ($row < 9 && $col >= $size - 8) ||
            ($row >= $size - 8 && $col < 9)) {
            return true;
        }
        
        if ($row === 6 || $col === 6) {
            return true;
        }
        
        return false;
    }
    
    private function apply_mask(&$matrix, $size, $mask_pattern) {
        for ($row = 0; $row < $size; $row++) {
            for ($col = 0; $col < $size; $col++) {
                if ($this->is_function_module($matrix, $row, $col, $size)) {
                    continue;
                }
                
                if ($this->get_mask($row, $col, $mask_pattern)) {
                    $matrix[$row][$col] = $matrix[$row][$col] ? 0 : 1;
                }
            }
        }
    }
    
    private function get_mask($row, $col, $pattern) {
        switch ($pattern) {
            case 0: return (($row + $col) % 2 === 0);
            case 1: return ($row % 2 === 0);
            case 2: return ($col % 3 === 0);
            case 3: return (($row + $col) % 3 === 0);
            case 4: return ((floor($row / 2) + floor($col / 3)) % 2 === 0);
            case 5: return ((($row * $col) % 2) + (($row * $col) % 3) === 0);
            case 6: return (((($row * $col) % 2) + (($row * $col) % 3)) % 2 === 0);
            case 7: return (((($row + $col) % 2) + (($row * $col) % 3)) % 2 === 0);
            default: return false;
        }
    }
    
    private function add_format_info(&$matrix, $size, $mask_pattern) {
        $format_bits = '01' . str_pad(decbin($mask_pattern), 3, '0', STR_PAD_LEFT);
        
        $format_string = $this->generate_format_string($format_bits);
        
        for ($i = 0; $i < 15; $i++) {
            $bit = ($format_string[$i] === '1') ? 1 : 0;
            
            // Top-left
            if ($i < 6) {
                $matrix[8][$i] = $bit;
            } elseif ($i < 8) {
                $matrix[8][$i + 1] = $bit;
            } elseif ($i === 8) {
                $matrix[7][8] = $bit;
            } else {
                $matrix[14 - $i][8] = $bit;
            }
            
            if ($i < 8) {
                $matrix[$size - 1 - $i][8] = $bit;
            } else {
                $matrix[8][$size - 15 + $i] = $bit;
            }
        }
    }
    
    private function generate_format_string($format_bits) {
        $format_poly = '10100110111'; 
        
        $padded = $format_bits . str_repeat('0', 10);
        
        $result = $format_bits . '0000000000';
        
        $mask = '101010000010010';
        $final = '';
        for ($i = 0; $i < 15; $i++) {
            $final .= ($result[$i] === $mask[$i]) ? '0' : '1';
        }
        
        return $final;
    }
}
