<?php
/**
 *  ╔═══════════════════════════════════════════════════╗
 *  ║                                                   ║
 *  ║     ██╗  ██╗   █████╗    ██████╗                  ║
 *  ║     ██║  ██║  ██╔══██╗  ██╔═══██╗                 ║
 *  ║     ███████║  ███████║  ██║   ██║                 ║
 *  ║     ██╔══██║  ██╔══██║  ██║   ██║                 ║
 *  ║     ██║  ██║  ██║  ██║  ╚██████╔╝   SNS           ║
 *  ║                                                   ║    
 *  ║                                                   ║    
 *  ║     © 2023 HaoSNS™ All Rights Reserved            ║
 *  ║     官方网站: https://www.haosns.com                *
 *  ║     本代码由赣州乐易网络科技有限公司®提供             *
 *  ║                                                    *
 *  ║   未经授权禁止复制、传播或用于其他商业目的            *
 *  ║                                                   ║
 *  ╚═══════════════════════════════════════════════════╝
 */

declare(strict_types=1);

namespace app\common\service;

class QQWry {
    private $fp;
    private $firstIndex;
    private $lastIndex;
    private $totalIndex;

    public function __construct($filename = 'qqwry.dat') {
        $this->fp = fopen($filename, 'rb');
        if ($this->fp === false) {
            throw new \Exception("无法打开QQWry.dat文件");
        }
        
        $this->firstIndex = $this->readLong(0);
        $this->lastIndex = $this->readLong(4);
        $this->totalIndex = ($this->lastIndex - $this->firstIndex) / 7 + 1;
    }

    public function __destruct() {
        if ($this->fp) {
            fclose($this->fp);
        }
    }

    public function getLocation($ip) {
        if (!$this->isValidIP($ip)) {
            return ['country' => '无效IP', 'area' => ''];
        }
        
        $ipNum = $this->ip2long($ip);
        $index = $this->findIndex($ipNum);
        
        if ($index === false) {
            return ['country' => '未知', 'area' => ''];
        }
        
        return $this->readLocation($index);
    }

    private function findIndex($ipNum) {
        $low = 0;
        $high = $this->totalIndex - 1;
        
        while ($low <= $high) {
            $mid = floor(($low + $high) / 2);
            $pos = intval($this->firstIndex + $mid * 7);
            
            $startIp = $this->readLong($pos);
            $pos += 4;
            $endIpOffset = $this->read3Byte($pos);
            $endIp = $this->readLong($endIpOffset);
            
            if ($ipNum < $startIp) {
                $high = $mid - 1;
            } elseif ($ipNum > $endIp) {
                $low = $mid + 1;
            } else {
                return $endIpOffset + 4;
            }
        }
        
        return false;
    }

    private function readLocation($offset) {
        $mode = $this->readByte($offset);
        
        if ($mode == 1) { // 重定向模式1
            $offset = $this->read3Byte($offset + 1);
            $mode = $this->readByte($offset);
        }
        
        $country = '';
        if ($mode == 2) { // 重定向模式2
            $countryOffset = $this->read3Byte($offset + 1);
            $country = $this->readString($countryOffset);
            $offset += 4;
        } else { // 普通字符串
            $country = $this->readString($offset);
            $offset = $offset + strlen($country) + 1;
        }
        
        $area = '';
        $mode = $this->readByte($offset);
        if ($mode == 2) { // 重定向模式2
            $areaOffset = $this->read3Byte($offset + 1);
            $area = $this->readString($areaOffset);
        } else { // 普通字符串
            $area = $this->readString($offset);
        }
        
        return [
            'country' => $this->convertEncoding($country),
            'area' => $this->convertEncoding($area)
        ];
    }

    private function readString($offset) {
        $str = '';
        while (true) {
            $byte = $this->readByte($offset++);
            if ($byte === 0) {
                break;
            }
            $str .= chr($byte);
        }
        return $str;
    }

    private function readByte($offset) {
        fseek($this->fp, $offset, SEEK_SET);
        $byte = unpack('C', fread($this->fp, 1));
        return $byte[1];
    }

    private function readLong($offset) {
        fseek($this->fp, $offset, SEEK_SET);
        $long = unpack('V', fread($this->fp, 4));
        return $long[1];
    }

    private function read3Byte($offset) {
        fseek($this->fp, $offset, SEEK_SET);
        $data = unpack('V', fread($this->fp, 3) . chr(0));
        return $data[1];
    }

    private function ip2long($ip) {
        $long = ip2long($ip);
        return $long < 0 ? $long + 4294967296 : $long;
    }

    private function isValidIP($ip) {
        return filter_var($ip, FILTER_VALIDATE_IP);
    }

    private function convertEncoding($str) {
        // 将GBK编码转换为UTF-8
        if (function_exists('mb_convert_encoding')) {
            return mb_convert_encoding($str, 'UTF-8', 'GBK');
        } elseif (function_exists('iconv')) {
            return iconv('GBK', 'UTF-8', $str);
        }
        return $str;
    }
}



// try {
//     // 初始化，指定QQWry.dat文件路径
//     $qqwry = new QQWry('qqwry.dat');
//     // 获取客户端IP
//     $ip = $_SERVER['REMOTE_ADDR'];
//     // 查询IP地理位置
//     $location = $qqwry->getLocation($ip);
//     // 输出结果
//     echo "IP: " . $ip . "<br>";
//     echo "国家/地区: " . $location['country'] . "<br>";
//     echo "区域/运营商: " . $location['area'] . "<br>";
//     // 完整结果查看
//     print_r($location);
    
// } catch (Exception $e) {
//     echo "发生错误: " . $e->getMessage();
// }