from gssapi.raw.cython_types cimport *
from gssapi.raw.oids cimport OID
from gssapi.raw.cython_converters cimport c_create_oid_set
GSSAPI="BASE"  # This ensures that a full module is generated by Cython

from gssapi.raw.cython_converters cimport c_get_mech_oid_set

from gssapi.raw.named_tuples import InquireAttrsResult, DisplayAttrResult
from gssapi.raw.misc import GSSError

cdef extern from "python_gssapi_ext.h":
    OM_uint32 gss_indicate_mechs_by_attrs(
        OM_uint32 *minor_status,
        const gss_OID_set desired_mech_attrs,
        const gss_OID_set except_mech_attrs,
        const gss_OID_set critical_mech_attrs,
        gss_OID_set *mechs) nogil

    OM_uint32 gss_inquire_attrs_for_mech(
        OM_uint32 *minor_status,
        const gss_OID mech,
        gss_OID_set *mech_attrs,
        gss_OID_set *known_mech_attrs) nogil

    OM_uint32 gss_display_mech_attr(
        OM_uint32 *minor_status,
        const gss_OID mech_attr,
        gss_buffer_t name,
        gss_buffer_t short_desc,
        gss_buffer_t long_desc) nogil


def indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None,
                            critical_mech_attrs=None):
    """
    indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None,
                            critical_mech_attrs=None)
    Get a set of mechanisms that have the specified attributes.

    Args:
        desired_mech_attrs ([OID]): Attributes that the output mechs MUST
            offer
        except_mech_attrs ([OID]): Attributes that the output mechs MUST NOT
            offer
        critical_mech_attrs ([OID]): Attributes that the output mechs MUST
            understand and offer

    Returns:
        [MechType]: a set of mechs which satisfy the given criteria

    Raises:
        GSSError
    """
    cdef OM_uint32 maj_stat, min_stat
    cdef gss_OID_set desired_attrs = GSS_C_NO_OID_SET
    cdef gss_OID_set except_attrs = GSS_C_NO_OID_SET
    cdef gss_OID_set critical_attrs = GSS_C_NO_OID_SET
    cdef gss_OID_set mechs

    if desired_mech_attrs is not None:
        desired_attrs = c_get_mech_oid_set(desired_mech_attrs)

    if except_mech_attrs is not None:
        except_attrs = c_get_mech_oid_set(except_mech_attrs)

    if critical_mech_attrs is not None:
        critical_attrs = c_get_mech_oid_set(critical_mech_attrs)

    with nogil:
        maj_stat = gss_indicate_mechs_by_attrs(&min_stat, desired_attrs,
                                               except_attrs, critical_attrs,
                                               &mechs)

    if maj_stat == GSS_S_COMPLETE:
        return c_create_oid_set(mechs)
    else:
        raise GSSError(maj_stat, min_stat)


def inquire_attrs_for_mech(OID mech):
    """
    inquire_attrs_for_mech(mech)
    Gets the set of attrs supported and known by a mechanism.

    Args:
        mech (MechType): Mechanism to inquire about

    Returns:
        InquireAttrsResult: the results of inquiry; a mech's attributes and
            known attributes

    Raises:
        GSSError
    """
    cdef OM_uint32 maj_stat, min_stat
    cdef gss_OID m = GSS_C_NO_OID
    cdef gss_OID_set mech_attrs = GSS_C_NO_OID_SET
    cdef gss_OID_set known_mech_attrs = GSS_C_NO_OID_SET

    if mech is not None:
        m = &mech.raw_oid

    with nogil:
        maj_stat = gss_inquire_attrs_for_mech(&min_stat, m, &mech_attrs,
                                              &known_mech_attrs)

    if maj_stat == GSS_S_COMPLETE:
        return InquireAttrsResult(c_create_oid_set(mech_attrs),
                                  c_create_oid_set(known_mech_attrs))
    else:
        raise GSSError(maj_stat, min_stat)


def display_mech_attr(OID attr):
    """
    display_mech_attrs(attr)
    Returns information about attributes in human readable form.

    Args:
        attr (OID): Mechanism attribute to retrive names and descriptions of

    Returns:
        DisplayAttrResult: the results of displaying the attribute; mech name,
            short description, and long description.

    Raises:
        GSSError
    """
    cdef OM_uint32 maj_stat, min_stat
    cdef gss_OID a = GSS_C_NO_OID
    cdef gss_buffer_desc name
    cdef gss_buffer_desc short_desc
    cdef gss_buffer_desc long_desc

    if attr is not None:
        a = &attr.raw_oid

    with nogil:
        maj_stat = gss_display_mech_attr(&min_stat, a, &name, &short_desc,
                                         &long_desc)

    if maj_stat == GSS_S_COMPLETE:
        out_name = (<char*>name.value)[:name.length]
        out_short = (<char*>short_desc.value)[:short_desc.length]
        out_long = (<char*>long_desc.value)[:long_desc.length]

        gss_release_buffer(&min_stat, &name)
        gss_release_buffer(&min_stat, &short_desc)
        gss_release_buffer(&min_stat, &long_desc)

        return DisplayAttrResult(out_name, out_short, out_long)
    else:
        raise GSSError(maj_stat, min_stat)
