<?php

if (!defined('ABSPATH')) {
    exit;
}

class BS2FA_CBOR {
    public static function decode($data) {
        $offset = 0;
        return self::decode_item($data, $offset);
    }

    protected static function decode_item($data, &$offset) {
        $initial = ord($data[$offset++]);
        $major   = $initial >> 5;
        $additional = $initial & 0x1F;

        switch ($major) {
            case 0:
                return self::read_length($data, $offset, $additional);
            case 1:
                return -1 - self::read_length($data, $offset, $additional);
            case 2:
                $length = self::read_length($data, $offset, $additional);
                $value  = substr($data, $offset, $length);
                $offset += $length;
                return $value;
            case 3:
                $length = self::read_length($data, $offset, $additional);
                $value  = substr($data, $offset, $length);
                $offset += $length;
                return $value;
            case 4:
                $length = self::read_length($data, $offset, $additional);
                $array  = [];
                for ($i = 0; $i < $length; $i++) {
                    $array[] = self::decode_item($data, $offset);
                }
                return $array;
            case 5:
                $length = self::read_length($data, $offset, $additional);
                $map = [];
                for ($i = 0; $i < $length; $i++) {
                    $key = self::decode_item($data, $offset);
                    $value = self::decode_item($data, $offset);
                    $map[$key] = $value;
                }
                return $map;
            case 6:
                self::read_length($data, $offset, $additional);
                return self::decode_item($data, $offset);
            case 7:
                if ($additional < 20) {
                    return $additional;
                }
                if ($additional === 20) {
                    return false;
                }
                if ($additional === 21) {
                    return true;
                }
                if ($additional === 22 || $additional === 23) {
                    return null;
                }
                if ($additional === 24) {
                    $value = ord($data[$offset]);
                    $offset += 1;
                    return $value;
                }
                if ($additional === 25) {
                    $offset += 2;
                    return null;
                }
                if ($additional === 26) {
                    $value = unpack('G', substr($data, $offset, 4))[1];
                    $offset += 4;
                    return $value;
                }
                if ($additional === 27) {
                    $parts = unpack('N2', substr($data, $offset, 8));
                    $offset += 8;
                    return ($parts[1] * 4294967296) + $parts[2];
                }
        }

        throw new RuntimeException('Unsupported CBOR structure');
    }

    protected static function read_length($data, &$offset, $additional) {
        if ($additional < 24) {
            return $additional;
        }

        if ($additional === 24) {
            return ord($data[$offset++]);
        }

        if ($additional === 25) {
            $value = unpack('n', substr($data, $offset, 2))[1];
            $offset += 2;
            return $value;
        }

        if ($additional === 26) {
            $value = unpack('N', substr($data, $offset, 4))[1];
            $offset += 4;
            return $value;
        }

        if ($additional === 27) {
            $parts = unpack('N2', substr($data, $offset, 8));
            $offset += 8;
            return ($parts[1] * 4294967296) + $parts[2];
        }

        throw new RuntimeException('Indefinite lengths are not supported');
    }
}
