<?php

declare(strict_types=1);

namespace Atdt\Import;

/**
 * PETSCII to Unicode, best effort (SPEC-IMPORT §14.1). PETSCII is not a
 * superset of ASCII: in the shifted (text) set, $41-$5A are LOWERCASE
 * letters and $C1-$DA are uppercase; color and cursor controls litter
 * $05-$9F. This table decodes the shifted set, which is what message
 * text on Commodore boards was written in; graphics characters map to
 * their closest CP437-friendly shape or are dropped as controls.
 *
 * mb_convert_encoding knows nothing of any of this, which is why the
 * table ships (the C64 archive case is the one this feature began with).
 */
final class Petscii
{
    /** Decode PETSCII bytes to a Unicode string. Unknown bytes -> null char list. */
    public static function toUnicode(string $bytes): string
    {
        $out = '';
        $n = strlen($bytes);
        for ($i = 0; $i < $n; $i++) {
            $b = ord($bytes[$i]);
            if ($b === 0x0D || $b === 0x8D) {           // RETURN / shifted RETURN
                $out .= "\n";
                continue;
            }
            if ($b === 0x09) {
                $out .= "\t";
                continue;
            }
            if ($b === 0xA0) {                          // shifted space
                $out .= ' ';
                continue;
            }
            if ($b < 0x20 || ($b >= 0x80 && $b <= 0x9F)) {
                continue;                                // colors, cursor moves, controls
            }
            if ($b >= 0x20 && $b <= 0x40) {              // punctuation and digits: as ASCII
                $out .= chr($b);
                continue;
            }
            if ($b >= 0x41 && $b <= 0x5A) {              // shifted set: lowercase
                $out .= chr($b + 0x20);
                continue;
            }
            if ($b >= 0x5B && $b <= 0x5F) {              // [ £ ] ↑ ←
                $out .= match ($b) {
                    0x5B => '[', 0x5C => chr(0xC2) . chr(0xA3), 0x5D => ']',
                    0x5E => '^', default => '_',
                };
                continue;
            }
            if ($b >= 0xC1 && $b <= 0xDA) {              // shifted set: uppercase
                $out .= chr($b - 0x80);
                continue;
            }
            if ($b >= 0x61 && $b <= 0x7A) {              // some exports carry ASCII lowercase
                $out .= chr($b);
                continue;
            }
            $out .= match ($b) {                         // common graphics worth keeping
                0x60, 0xC0 => "\u{2500}",                // horizontal bar
                0x7D, 0xDD => "\u{2502}",                // vertical bar
                0x7B, 0xDB => "\u{253C}",                // cross
                0xA6, 0xDE => "\u{2592}",                // checkerboard shade
                0x7E, 0xDF => "\u{03C0}",                // pi
                default => '?',
            };
        }
        return $out;
    }
}
