public function decode($input)
    {
        // Don't use UCS-2.
        $output = [];
        $inputLength = strlen($input);
        $i = 0;
        $n = self::INITIAL_N;
        $bias = self::INITIAL_BIAS;

        // Handle the basic code points: let `basic` be the number of input code
        // points before the last delimiter, or `0` if there is none, then copy
        // the first basic code points to the output.

        $basic = strrpos($input, self::DELIMITER);
        if ($basic === false ) {
            $basic = 0;
        }

        for ($j = 0; $j < $basic; ++$j) {
            // if it's not a basic code point
            if ( ord($input[$j]) >= 0x80) {
                $this->error('not-basic');
            }
            $output[] = ord($input[$j]);
        }

        // Main decoding loop: start just after the last delimiter if any basic code
        // points were copied; start at the beginning otherwise.

        for ( $index = $basic > 0 ? $basic + 1 : 0; $index < $inputLength; /* no final expression */) {

            // `index` is the index of the next character to be consumed.
            // Decode a generalized variable-$length integer into `delta`,
            // which gets added to `i`. The overflow checking is easier
            // if we increase `i` as we go, then subtract off its starting
            // value at the end to obtain `delta`.
            $oldi = $i;
            for ( $w = 1, $k = self::BASE; /* no condition */; $k += self::BASE) {

                if ($index >= $inputLength) {
                    $this->error('invalid-input');
                }

                $digit = $this->basicToDigit(ord($input[$index++]));

                if ($digit >= self::BASE || $digit > floor((self::MAX_INT - $i) / $w)) {
                    $this->error('overflow');
                }

                $i += $digit * $w;
                $t = $k <= $bias ? self::T_MIN : ($k >= $bias + self::T_MAX ? self::T_MAX : $k - $bias);

                if ($digit < $t) {
                    break;
                }

                $baseMinusT = self::BASE - $t;
                if ($w > floor(self::MAX_INT / $baseMinusT)) {
                    $this->error('overflow');
                }

                $w *= $baseMinusT;

            }

            $out = count($output) + 1;
            $bias = $this->adapt($i - $oldi, $out, $oldi == 0);

            // `i` was supposed to wrap around from `out` to `0`,
            // incrementing `n` each time, so we'll fix that now:
            if (floor($i / $out) > self::MAX_INT - $n) {
                $this->error('overflow');
            }

            $n += floor($i / $out);
            $i %= $out;

            // Insert `n` at position `i` of the output.
            array_splice($output, $i++, 0, $n);
        }

        return mb_convert_encoding($this->ucs2Encode($output), "UTF-8", "UTF-16");
    }