<?php

namespace loyalsoft;

/**
 * 激活码数据结构
 */
class ActiveCode {

    /**
     * 平台编号
     * @var int
     */
    public $plat;

    /**
     * 礼包编号
     * @var int
     */
    public $package;

    /**
     * 编号
     * @var int
     */
    public $number;

}

/**
 * 在定制的Base32(去掉了 0 和 o, 1 和 l. 使用剩余的 24个小写字母和8个数字)基础上使用
 * 密码本算法打乱字母规律, 并对number进行了散列, 增加猜解难度.
 * @author gwang 2016.2
 */
class CipheredBase32 {

    const codeLength = 10;                                                      # 兑换码的长度固定为10

    /**
     * 密码本
     * @return string
     */
    public static function charMap() {
        return GameConstants::GetCipherString();                                # 密码本
    }

    /**
     * 解码
     * @param string $codeString
     * @return \ActiveCode
     * @throws \Exception
     */
    public static function Decode($codeString) {
        if (self::codeLength != strlen($codeString)) {                          # 校验兑换码字符串长度
            return "activecode length wrong!";
        } else {
            $bytesof5bits = array();                                            # 将字符串转为索引数组:=[0..32]{10}
            $cipher = self::charMap();
            for ($i = 0; $i < self::codeLength; $i++) {
                $c = $codeString[$i];                                           # 取字符
                $b = strpos($cipher, $c);                                       # 按照密码本还原真实字符的索引
                if ($b < 32 && $b > -1) {                                       # 索引范围校验
                    $bytesof5bits[] = $b;
                } else {
                    // CLogUtil::console("b ($b)is not a valid index ");
                }
            }
            $by8bits = self::fetch5BitByte($bytesof5bits);                      # 将索引数组转为字节数组:=[0..256]{5}
            $activeCode = new ActiveCode();                                     # 创建对象
            $activeCode->plat = $by8bits[1];                                    # 第一个字节: 平台
            $activeCode->package = $by8bits[3];                                 # 第二个字节: 礼包编号
            $i = 0;                                                             # 临时变量
            $i += ($by8bits[0] ^ $by8bits[3] ^ $by8bits[1]);                    # 剩余字节为兑换码的编号, 低8位
            $i += ( $by8bits[4] ^ $by8bits[3] ^ $by8bits[1] ) << 8;             # 高8位
            $i += ($by8bits[2] ^ $by8bits[3] ^ $by8bits[1] ) << 16;             # 第17-24位
            $i += ($by8bits[5] ^ $by8bits[3] ^ $by8bits[1] ) << 24;             # 第25-32位
            $i = self::uint32val($i);                                           # 提取编号(32位环境,大int值会转为float进行运算)
            $i = 4294967295 - $i;                                               # 补码才是真正的编号
            $i /= 85889;                                                        # 编号再除以一个特殊值才还原为最终编号
            if (intval($i) - $i == 0) {                                         # 确认是合法整数
                $activeCode->number = intval($i);
            }
            return $activeCode;
        }
    }

    /**
     * php本身没有unint类型, 网上找的,具体代码看不懂--gwang
     * @param type $var
     * @return type
     */
    static function uint32val($var) {
        if (is_string($var)) {                                                  # 字符串
            if (PHP_INT_MAX > 2147483647) {                                     # 大于32位
                $var = intval($var);
            } else {                                                            # 利用float来避免数值较大时损失高位信息
                $var = floatval($var);
            }
        }
        if (!is_int($var)) {
            $var = intval($var);                                                # 再次强转int
        }
        if ((0 > $var) || ($var > 4294967295)) {
            $var &= 4294967295;                                                 # uint32最大值
            if (0 > $var) {
                $var = sprintf('%u', $var);                                     # 转为uint(string)
            }
        }
        return $var;
    }

    /**
     * 转换 10个 5bit Bytes 到 6个 8bit Bytes
     * @param array $data 长度不小于10的字节数组
     * @return array
     */
    static function fetch5BitByte($data) {
        $ret5 = array();
        $ret5[0] = ( $data[0] + intval(( $data[1] & 0x7) << 5));                # 5 + 3 = 8
        $ret5[1] = ( intval(($data[1] & 0x18 ) >> 3)                            # 2 + 5 + 1 = 8
                + intval($data[2] << 2) + intval(($data[3] & 0x1) << 7));
        $ret5[2] = ( intval(($data[3] & 0x1E) >> 1)                             # 4 + 4 = 8
                + intval(($data[4] & 0xf) << 4));
        $ret5[3] = ( intval(($data[4] & 0x10) >> 4)                             # 1 + 5 + 2 = 8
                + intval($data[5] << 1) + intval(($data[6] & 0x3) << 6));
        $ret5[4] = ( intval(($data[6] & 0x1C) >> 2) + intval($data[7] << 3));   # 3 + 5 = 8
        $ret5[5] = ( intval(($data[8]) + intval($data[9] << 5)));               # 5 + 3 = 8
        return $ret5;
    }

}
