<?php
namespace Base\Tool;

class Base62
{
    const ENCODES = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/';

    private static function getCharArray($string)
    {
        $list = [];
        $length = strlen($string);
        for ($i = 0; $i < $length; $i++) {
            $list[] = $string[$i];
        }
        return $list;
    }

    private static function getIndexChars($string) {
        $length = strlen($string);
        $chars = [];
        for($i = 0; $i < 256; $i++) {
            $chars[] = 0;
        }
        for($i = 0; $i < $length; $i++) {
            $chars[$string[$i]] = $i ;
        }
        return $chars;
    }

    public static function getSpecialChar($char, $default = null)
    {
        if ($default == null) {
            $default = $char;
        }
        return $char == 'i' ? "ia" : ($char == '+' ? "ib" : ($char == '/' ? "ic" : $default));
    }

    public static function encode($string)
    {
        $encodes = self::getCharArray(self::ENCODES);
        $bytes = self::stringToBytes($string);
        $pos = 0;
        $val = 0;
        $result = [];
        for ($i = 0; $i < count($bytes); $i++) {
            $val = ($val << 8) | ($bytes[$i] & 0xFF);
            $pos += 8;
            while ($pos > 5) {
                $char = $encodes[$val >> ($pos -= 6)];
                $encodeChar = self::getSpecialChar($char);
                $result[] = $encodeChar;
                $val &= ((1 << $pos) - 1);
            }
        }
        if ($pos > 0) {
            $char = $encodes[$val << (6 - $pos)];
            $result[] = self::getSpecialChar($char);
        }
        return implode('', $result);
    }

    public static function decode($string)
    {
        $decodes = self::getIndexChars(self::ENCODES);
        $chars = self::getCharArray($string);
        $pos = 0;
        $val = 0;
        $bytes = [];
        for ($i = 0; $i < count($chars); $i++) {
            $char = $chars[$i];
            if ($char == 'i') {
                $char = $chars[++$i];
                $char = 
                    $char == 'a' ? 'i' :
                    ($char == 'b' ? '+' :
                    ($char == 'c' ? '/' : $chars[--$i]));
            }
            $val = ($val << 6) | $decodes[$char];
            $pos += 6;
            while ($pos > 7) {
                $bytes[] = $val >> ($pos -= 8);
                $val &= ((1 << $pos) - 1);
            }
        }
        return self::bytesToString($bytes);
    }

    private static function bytesToString($bytes) {
        $string = '';
        foreach($bytes as $byte) {
            $string .= chr($byte);
        }
        return $string;
    }

    private static function stringToBytes($string) {
        $length = strlen($string);
        $bytes = array();
        for($i = 0; $i < $length; $i++) {
            if(ord($string[$i]) >= 128){
                $byte = ord($string[$i]) - 256;
            }else{
                $byte = ord($string[$i]);
            }
            $bytes[] =  $byte ;
        }
        return $bytes;
    }
}