<?php

namespace Buckhill\U2F\Enroll;


use Buckhill\U2F\U2FHelper;

class RequestParser
{
    protected $data;
    protected $offset = 1;
    protected $pub_key;
    protected $pem_pub_key;
    protected $key_handle;
    protected $pem_cert;
    protected $raw_cert;

    public function __construct(array $data)
    {
        static::validate($data);

        $this->data = $data;
    }


    /**
     * @return mixed
     */
    public function getPublicKey()
    {
        return $this->pub_key;
    }


    /**
     * @return mixed
     */
    public function getPEMPublicKey()
    {
        return $this->pem_pub_key;
    }


    /**
     * @return mixed
     */
    public function getKeyHandle()
    {
        return $this->key_handle;
    }


    /**
     * @return mixed
     */
    public function getPEMEncodedCertificate()
    {
        return $this->pem_cert;
    }


    /**
     * @return mixed
     */
    public function getRawCertificate()
    {
        return $this->raw_cert;
    }


    /**
     * @return $this
     */
    public function parse()
    {
        return $this
            ->parsePublicKey()
            ->parseKeyHandle()
            ->parseCertificate()
        ;
    }


    /**
     * @return $this
     */
    protected function parsePublicKey()
    {
        $public_key = substr($this->data['registrationData'], $this->offset, U2FHelper::U2F_PUB_KEY_LENGTH);

        $this->offset += U2FHelper::U2F_PUB_KEY_LENGTH;

        $key = U2FHelper::pub2pem($public_key);

        if(is_null($key))
        {
            throw new \UnexpectedValueException("Decoding public key failed.");
        }

        $this->pub_key = $public_key;
        $this->pem_pub_key = $key;

        return $this;
    }


    /**
     * @return $this
     */
    protected function parseKeyHandle()
    {
        $len = $this->data['registration'][$this->offset++];

        $this->key_handle = substr($this->data['registrationData'], $this->offset, $len);

        $this->offset += $len;

        return $this;
    }


    /**
     * @return $this
     */
    public function parseCertificate()
    {
        // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
        $certLen = 4;
        $certLen += ($this->data['registration'][$this->offset + 2] << 8);
        $certLen += $this->data['registration'][$this->offset + 3];

        $rawCert = U2FHelper::fixSignatureUnusedBits(substr($this->data['registrationData'], $this->offset, $certLen));

        $this->offset += $certLen;

        $pemCert  = "-----BEGIN CERTIFICATE-----\r\n";
        $pemCert .= chunk_split(base64_encode($rawCert), 64);
        $pemCert .= "-----END CERTIFICATE-----";

        if(!openssl_pkey_get_public($pemCert))
        {
            throw new \UnexpectedValueException('Unable to retrieve certificate, decoding of public key failed - ' . openssl_error_string());
        }

        $this->pem_cert = $pemCert;

        $this->raw_cert = $rawCert;

        return $this;
    }


    /**
     * @return string
     */
    public function getSignature()
    {
        return substr($this->data['registrationData'], $this->offset);
    }

    /**
     * @todo - implement validate method
     *
     * @param array $data
     */
    protected static function validate(array $data)
    {
        $required = ['registration', 'registrationData', 'client', 'clientData'];

        foreach($required as $key)
        {
            if(!isset($data[$key]))
            {
                throw new \InvalidArgumentException(sprintf("Required key %s is not present.", $key));
            }
        }
    }


    /**
     * @param      $key
     * @param null $default
     *
     * @return mixed
     */
    protected function fromData($key, $default = null)
    {
        return isset($this->data[$key]) ? $this->data[$key] : $default;
    }
}