"""
Key handling utilities for EC keys (ANSI X.62/RFC3279), domain parameter and
signatures.
"""

from asn1crypto.keys import (
    ECDomainParameters,
    ECPrivateKey,
    NamedCurve,
    PublicKeyInfo,
)
from asn1crypto.algos import DSASignature
from asn1crypto.core import OctetString
from ..constants import Attribute, ObjectClass
from ..mechanisms import KeyType


def encode_named_curve_parameters(oid):
    """
    Return DER-encoded ANSI X.62 EC parameters for a named curve.

    Curve names are given by object identifier or common name. Names come
    from `asn1crypto
    <https://github.com/wbond/asn1crypto/blob/master/asn1crypto/keys.py#L338>`_.

    :param str oid: OID or named curve
    :rtype: bytes
    """
    return ECDomainParameters(
        name='named',
        value=NamedCurve.unmap(oid),
    ).dump()


def decode_ec_public_key(der, encode_ec_point=True):
    """
    Decode a DER-encoded EC public key as stored by OpenSSL into a dictionary
    of attributes able to be passed to :meth:`pkcs11.Session.create_object`.

    .. note:: **encode_ec_point**

        For use as an attribute `EC_POINT` should be DER-encoded (True).

        For key derivation implementations can vary.  Since v2.30 the
        specification says implementations MUST accept a raw `EC_POINT` for
        ECDH (False), however not all implementations follow this yet.

    :param bytes der: DER-encoded key
    :param encode_ec_point: See text.
    :rtype: dict(Attribute,*)
    """
    asn1 = PublicKeyInfo.load(der)

    assert asn1.algorithm == 'ec', \
        "Wrong algorithm, not an EC key!"

    ecpoint = bytes(asn1['public_key'])

    if encode_ec_point:
        ecpoint = OctetString(ecpoint).dump()

    return {
        Attribute.KEY_TYPE: KeyType.EC,
        Attribute.CLASS: ObjectClass.PUBLIC_KEY,
        Attribute.EC_PARAMS: asn1['algorithm']['parameters'].dump(),
        Attribute.EC_POINT: ecpoint,
    }


def decode_ec_private_key(der):
    """
    Decode a DER-encoded EC private key as stored by OpenSSL into a dictionary
    of attributes able to be passed to :meth:`pkcs11.Session.create_object`.

    :param bytes der: DER-encoded key
    :rtype: dict(Attribute,*)
    """

    asn1 = ECPrivateKey.load(der)

    return {
        Attribute.KEY_TYPE: KeyType.EC,
        Attribute.CLASS: ObjectClass.PRIVATE_KEY,
        Attribute.EC_PARAMS: asn1['parameters'].chosen.dump(),
        Attribute.VALUE: asn1['private_key'].contents,
    }


def encode_ec_public_key(key):
    """
    Encode a DER-encoded EC public key as stored by OpenSSL.

    :param PublicKey key: EC public key
    :rtype: bytes
    """

    ecparams = ECDomainParameters.load(key[Attribute.EC_PARAMS])
    ecpoint = bytes(OctetString.load(key[Attribute.EC_POINT]))

    return PublicKeyInfo({
        'algorithm': {
            'algorithm': 'ec',
            'parameters': ecparams,
        },
        'public_key': ecpoint,
    }).dump()


def encode_ecdsa_signature(signature):
    """
    Encode a signature (generated by :meth:`pkcs11.SignMixin.sign`) into
    DER-encoded ASN.1 (ECDSA_Sig_Value) format.

    :param bytes signature: signature as bytes
    :rtype: bytes
    """

    return DSASignature.from_p1363(signature).dump()


def decode_ecdsa_signature(der):
    """
    Decode a DER-encoded ASN.1 (ECDSA_Sig_Value) signature (as generated by
    OpenSSL/X.509) into PKCS #11 format.

    :param bytes der: DER-encoded signature
    :rtype bytes:
    """

    asn1 = DSASignature.load(der)
    return asn1.to_p1363()
