<?php

namespace Buckhill\Support;

class Base32
{
    protected static $map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';

    /**
     * @param string|null $encoded
     * @return string
     */
    public static function decode(string $encoded = null): string
    {
        if(empty($encoded))
        {
            return '';
        }

        $temp = '';

        $arr = str_split($encoded);

        foreach($arr as $str)
        {
            $char = strpos(static::$map, $str);

            // Return immediately, invalid base32 char
            if(false === $char)
            {
                return '';
            }

            if($char !== 32)
            {
                $temp .= sprintf("%05b", $char);
            }
        }

        while(strlen($temp) %8 !== 0)
        {
            $temp = substr($temp, 0, strlen($temp) - 1);
        }

        $bin_arr = static::chunk($temp, 8);

        $result = array_map(function($bin)
        {
            $bin = str_pad($bin, 8, 0, STR_PAD_RIGHT);

            return chr(bindec($bin));

        }, $bin_arr);

        return implode('', $result);
    }

    /**
     * @param string $payload
     * @return string
     */
    public static function encode(string $payload): string
    {
        // Empty payload - empty return
        if(strlen($payload) === 0) return '';

        $bin_arr = static::toBinaryArray($payload);

        $result = array_map(function($char)
        {
            return static::encodeSingle($char);

        }, $bin_arr);

        return implode('', $result);
    }

    /**
     * @param string|null $bin
     * @return string
     */
    protected static function encodeSingle(string $bin = null): string
    {
        $char = 32;

        if (!is_null($bin))
        {
            // Pad the binary strings
            $bin = str_pad($bin, 5, 0, STR_PAD_RIGHT);

            $char = bindec($bin);
        }

        return static::$map[$char];
    }


    /**
     * @param string $string
     * @return array
     */
    protected static function toBinaryArray(string $string): array
    {
        $bin_str = static::toBinaryString($string);

        $bin_arr = static::chunk($bin_str, 5);

        // Pad array to be divisible by 8
        while(sizeof($bin_arr) % 8 !== 0)
        {
            $bin_arr[] = null;
        }

        return $bin_arr;
    }

    /**
     * @param string $string
     * @return string
     */
    protected static function toBinaryString(string $string): string
    {
        $split = str_split($string);

        $result = array_map(function($char)
        {
            return sprintf("%08b", ord($char));

        }, $split);

        return implode('', $result);
    }

    /**
     * @param string $string
     * @param int $bits
     * @return array
     */
    protected static function chunk(string $string, $bits = 5): array
    {
        $bin_str = chunk_split($string, $bits, ' ');

        if(substr($bin_str, (strlen($bin_str)) - 1) == ' ')
        {
            $bin_str = substr($bin_str, 0, strlen($bin_str)-1);
        }

        return explode(' ', $bin_str);
    }
}