<?php
namespace Api\PhpUtils\Common;

use Api\PhpUtils\Log\FileLog;

class IP
{
    const IPV4 = 1;
    const IPV6 = 2;

    private static $file = NULL;
    private static $fileSize = 0;
    private static $nodeCount = 0;
    private static $nodeOffset = 0;
    private static $meta = [];
    private static $v4offset = 0;
    private static $v6offsetCache = [];
    private static $database = ROOT_PATH . '/data/ipip_china_cn.ipdb';

    /**
     * 根据ip获取地理位置
     *
     * @param string $ip
     * @return array 
     */
    public static function find($ip)
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, [ FILTER_FLAG_IPV4, FILTER_FLAG_IPV6 ])) {
            try {
                return self::findv4v6($ip);
            }catch (\Exception $e) {
                FileLog::waring('ipip exception', $e->getMessage());
                return [];
            }
        }
        return [];
    }

    private static function init()
    {
        if (is_readable(self::$database) === FALSE)
        {
            throw new \Exception("The IP Database file" . self::$database . "does not exist or is not readable.");
        }
        self::$file = @fopen(self::$database, 'rb');
        if (self::$file === FALSE)
        {
            throw new \Exception("IP Database File opening " . self::$database . ".");
        }
        self::$fileSize = @filesize(self::$database);
        if (self::$fileSize === FALSE)
        {
            throw new \Exception("Error determining the size of " . self::$database . ".");
        }

        $metaLength = unpack('N', fread(self::$file, 4))[1];
        $text = fread(self::$file, $metaLength);

        self::$meta = json_decode($text, 1);

        if (isset(self::$meta['fields']) === FALSE || isset(self::$meta['languages']) === FALSE)
        {
            throw new \Exception('IP Database metadata error.');
        }

        $fileSize = 4 + $metaLength + self::$meta['total_size'];
        if ($fileSize != self::$fileSize)
        {
            throw  new \Exception('IP Database size error.');
        }

        self::$nodeCount = self::$meta['node_count'];
        self::$nodeOffset = 4 + $metaLength;
    }

    /**
     * @param $ip
     * @param string $language
     * @return array
     * @throws Exception
     */
    private static function findv4v6($ip, $language = 'CN')
    {
        if (self::$file === NULL)
        {
            self::init();
        }
        if (is_resource(self::$file) === FALSE)
        {
            throw new \Exception('IPIP DB closed.');
        }
        if (isset(self::$meta['languages'][$language]) === FALSE)
        {
            throw new \Exception("language : {$language} not support.");
        }
        if (strpos($ip, '.') !== FALSE && !self::supportV4())
        {
            throw new \Exception("The Database not support IPv4 address.");
        }
        elseif (strpos($ip, ':') !== FALSE && !self::supportV6())
        {
            throw new \Exception("The Database not support IPv6 address.");
        }

        try
        {
            $node = self::findNode($ip);

            if ($node > 0)
            {
                $data = self::resolve($node);
                $values = explode("\t", $data);
                $result = array_slice($values, self::$meta['languages'][$language], count(self::$meta['fields']));
                if(is_array($result) && !in_array($result[0], ['', '局域网', '本机地址']))
                {
                    return [
                        'country_name' => $result[0],
                        'region_name' => $result[1],
                        'city_name' => $result[2],
                        'owner_domain' => $result[3],
                        'isp_domain' => $result[4],
                        'latitude' => $result[5],
                        'longitude' => $result[6],
                        'timezone' => $result[7],
                        'utc_offset' => $result[8],
                        'china_admin_code' => $result[9],
                        'idd_code' => $result[10],
                        'country_code' => $result[11],
                        'continent_code' => $result[12],
                    ];
                }
            }
        } catch (\Exception $e)
        {
            return [];
        }

        return [];
    }

    /**
     * @param $node
     * @return mixed
     * @throws Exception
     */
    private static function resolve($node)
    {
        $resolved = $node - self::$nodeCount + self::$nodeCount * 8;
        if ($resolved >= self::$fileSize)
        {
            return NULL;
        }

        $bytes = self::read(self::$file, $resolved, 2);
        $size = unpack('N', str_pad($bytes, 4, "\x00", STR_PAD_LEFT))[1];

        $resolved += 2;

        return self::read(self::$file, $resolved, $size);
    }

    private function close()
    {
        if (is_resource(self::$file) === TRUE)
        {
            fclose(self::$file);
        }
    }

    /**
     * @param $stream
     * @param $offset
     * @param $length
     * @return bool|string
     * @throws Exception
     */
    private static function read($stream, $offset, $length)
    {
        if ($length > 0)
        {
            if (fseek($stream, $offset + self::$nodeOffset) === 0)
            {
                $value = fread($stream, $length);
                if (strlen($value) === $length)
                {
                    return $value;
                }
            }
            throw new \Exception("The Database file read bad data.");
        }
        return '';
    }

    private static function supportV6()
    {
        return (self::$meta['ip_version'] & self::IPV6) === self::IPV6;
    }

    private static function supportV4()
    {
        return (self::$meta['ip_version'] & self::IPV4) === self::IPV4;
    }

    /**
     * @param $ip
     * @return int
     * @throws Exception
     */
    private static function findNode($ip)
    {
        $binary = inet_pton($ip);
        $bitCount = strlen($binary) * 8; // 32 | 128
        $key = substr($binary, 0, 2);
        $node = 0;
        $index = 0;
        if ($bitCount === 32)
        {
            if (self::$v4offset === 0)
            {
                for ($i = 0; $i < 96 && $node < self::$nodeCount; $i++)
                {
                    if ($i >= 80)
                    {
                        $idx = 1;
                    }
                    else
                    {
                        $idx = 0;
                    }
                    $node = self::readNode($node, $idx);
                    if ($node > self::$nodeCount)
                    {
                        return 0;
                    }
                }
                self::$v4offset = $node;
            }
            else
            {
                $node = self::$v4offset;
            }
        }
        else
        {
            if (isset(self::$v6offsetCache[$key]))
            {
                $index = 16;
                $node = self::$v6offsetCache[$key];
            }
        }

        for ($i = $index; $i < $bitCount; $i++)
        {
            if ($node >= self::$nodeCount)
            {
                break;
            }

            $node = self::readNode($node, 1 & ((0xFF & ord($binary[$i >> 3])) >> 7 - ($i % 8)));

            if ($i == 15)
            {
                self::$v6offsetCache[$key] = $node;
            }
        }

        if ($node === self::$nodeCount)
        {
            return 0;
        }
        elseif ($node > self::$nodeCount)
        {
            return $node;
        }

        throw new \Exception("find node failed.");
    }

    /**
     * @param $node
     * @param $index
     * @return mixed
     * @throws Exception
     */
    private static function readNode($node, $index)
    {
        return unpack('N', self::read(self::$file, $node * 8 + $index * 4, 4))[1];
    }

    public function __destruct()
    {
        self::close();
        self::$file = NULL;
    }

    /**
     * 是否国内ip
     *
     * @param string $ip
     * @return bool
     */
    public static function inChina($ip = '')
    {
        // 测试环境支持ip参数
        if (empty($ip) && !empty($_GET['test_ip']) && defined('DEVELOPMENT_VERSION_ONLY'))
        {
            $ip = $_GET['test_ip'];
        }

        $ip = empty($ip) ? self::ip() : $ip;
        if (empty($ip) || self::is_intranet($ip))
        {
            return true;
        }
        $location = self::find($ip);
        if (isset($location[0]) && $location[0] == '中国')
        {
            return true;
        }
        return false;
    }

    /**
     * 获取ip地址
     *
     * @return void
     */
    public static function ip()
    {
        if (isset($_SERVER['IPV6_REMOTE_ADDR']) && !empty($_SERVER['IPV6_REMOTE_ADDR']) && trim($_SERVER['IPV6_REMOTE_ADDR']) !== '-')  // 如果没有ipv6地址， 运维会传一个占位符“-”
        {
            $ips = $_SERVER['IPV6_REMOTE_ADDR'];
        }
        else if (!empty($_SERVER['HTTP_CDN_SRC_IP']))
        {
            $ips = $_SERVER['HTTP_CDN_SRC_IP'];
        }
        else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
        {
            $ips = $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        else if (!empty($_SERVER['HTTP_CLIENT_IP']))
        {
            $ips = $_SERVER['HTTP_CLIENT_IP'];
        }
        else
        {
            $ips = $_SERVER['REMOTE_ADDR'];
        }

        $ip = explode(',', $ips);
        return $ip[0];
    }

    /**
     * 获取客户端ip
     *
     * @return string
     */
    public static function remote_ip()
    {
        $ips = self::user_ips();

        return array_pop($ips);
    }

    public static function user_ips()
    {
        $ips = '';
        if (isset($_SERVER['IPV6_REMOTE_ADDR']) && !empty($_SERVER['IPV6_REMOTE_ADDR']) && trim($_SERVER['IPV6_REMOTE_ADDR']) !== '-')  // 如果没有ipv6地址， 运维会传一个占位符“-”
        {
            $ips .= $_SERVER['IPV6_REMOTE_ADDR'];
        }
        if (!empty($_SERVER['HTTP_CDN_SRC_IP']))
        {
            $ips .= ',' . $_SERVER['HTTP_CDN_SRC_IP'];
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
        {
            $ips .= ',' . $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        if (!empty($_SERVER['HTTP_X_REAL_IP']))
        {
            $ips .= ',' . $_SERVER['HTTP_X_REAL_IP'];
        }

        $ips .= ',' . $_SERVER['REMOTE_ADDR'];

        $internet_ips = array();

        $ip = explode(',', $ips);
        $ip = array_map(function($item){return trim($item);}, $ip);

        foreach ($ip as $one)
        {
            if (!self::is_intranet($one) && $one !== '')
            {
                $internet_ips[] = $one;
            }
        }

        return $internet_ips;
    }

    /**
     * 是否是内网地址
     *
     * @param string $ip
     * @return bool
     */
    public static function is_intranet($ip)
    {
        $long = ip2long(trim($ip));

        return $long == 2130706433 || //false, 127.0.0.1
            ($long >= 167772160 && $long <= 184549375) ||    //10.0.0.0 ~ 10.255.255.255
            ($long >= 2886729728 && $long <= 2887778303) ||  //172.15.0.0 ~ 172.31.255.255
            ($long >= 3232235520 && $long <= 3232301055);    //192.168.0.0 ~ 192.168.255.255
    }
}
