<?php

namespace iDServer\Internal\ASN1;

/**
 * @internal
 */
final class Signature
{
    private const TYPE_SEQUENCE = "\x30";
    private const TYPE_INT = "\x02";

    /**
     * @var ASN1Integer
     */
    private $r;
    /**
     * @var ASN1Integer
     */
    private $s;


    /**
     * @param string $binarySignature
     * @return void
     * @throws ASN1ParserException
     */
    public static function fromBinary(string $binarySignature): self
    {
        $signature = new self();

        if ($binarySignature[0] !== self::TYPE_SEQUENCE) {
            throw new ASN1ParserException(
                "Signature must be an ASN.1 sequence. "
                . "The first byte must be but 0x"
                . bin2hex(self::TYPE_SEQUENCE)
                . ", but it is 0x" . bin2hex($binarySignature[0])
            );
        }

        $index = 1;

        $sequenceDataLength = ord($binarySignature[$index++]);
        if ($sequenceDataLength == 0x81) {
            $index++;
        }

        $rIntType = $binarySignature[$index++];
        if ($rIntType !== self::TYPE_INT) {
            throw new ASN1ParserException(
                "The type of R must be integer (0x"
                . bin2hex(self::TYPE_INT)
                . "), but it is 0x"
                . bin2hex($rIntType)
            );
        }

        $rLength = ord($binarySignature[$index++]);
        $r = substr($binarySignature, $index, $rLength);

        $sIntType = $binarySignature[$index + $rLength];

        if ($sIntType !== self::TYPE_INT) {
            throw new ASN1ParserException(
                "The type of S must be integer (0x"
                . bin2hex(self::TYPE_INT)
                . "), but it is 0x"
                . bin2hex($sIntType)
            );
        }

        // $sLength = $binarySignature[$index + $rLength + 1];
        $s = substr($binarySignature, $index + $rLength + 2);

        $signature->r = new ASN1Integer($r);
        $signature->s = new ASN1Integer($s);

        return $signature;
    }

    /**
     * Private constructor to prevent instantiating it without the parser
     */
    private function __construct()
    {

    }

    /**
     * @return ASN1Integer
     */
    public function getR(): ASN1Integer
    {
        return $this->r;
    }

    /**
     * @return ASN1Integer
     */
    public function getS(): ASN1Integer
    {
        return $this->s;
    }

    /**
     * @return string
     */
    public function toXMLSignatureBinary(): string
    {
        $br = $this->r->toBinary();
        $bs = $this->s->toBinary();

        $lr = strlen($br);
        $ls = strlen($bs);
        if ($lr != $ls) {
            if ($lr > $ls) {
                $bs = "\x00" . $bs;
            } else {
                $br = "\x00" . $br;
            }
        }

        return $br . $bs;
    }
}
