from dlkit.abstract_osid.authentication_process import objects as abc_authentication_process_objects
from ..osid import markers as osid_markers
from ..osid import objects as osid_objects
from ..authentication.objects import Agent
from ..primitives import Id
from ..osid.osid_errors import *


class DjangoAuthentication(abc_authentication_process_objects.Authentication, osid_objects.OsidObject):
    """``Authentication`` represents an authentication credential which contains set of ``bytes`` and a format Type.

    Once an ``Authentication`` is created from the
    ``AuthenticationValidationSession,`` the credential data can be
    extracted and sent to the remote peer for validation. The remote
    peer gets another ``Authentication`` object as a result of
    validating the serialized credential data.

    An ``Authentication`` may or may not be valid. ``is_valid()`` should
    be checked before acting upon the ``Agent`` identity to which the
    credential is mapped.

    """
    def __init__(self):
        self._django_user = None
        self._credential = None
        self._use_user_id = None

    def get_agent_id(self):
        """Gets the ``Id`` of the ``Agent`` identified in this authentication credential.

        :return: the ``Agent Id``
        :rtype: ``osid.id.Id``

        *compliance: mandatory -- This method must be implemented.*
        *implementation notes*: The Agent should be determined at the
        time this credential is created.

        """
        if self._django_user is not None:
            if self._use_user_id:
                identifier = self._django_user.id
            else:
                identifier = self._django_user.get_username()
            return Id(identifier=identifier,
                      namespace='osid.agent.Agent',
                      authority='MIT-ODL')
        else:
            # perhaps this id should come from django settings?
            return Id(identifier='MC3GUE$T@MIT.EDU',
                      namespace='osid.agent.Agent',
                      authority='MIT-ODL')

    agent_id = property(fget=get_agent_id)

    def get_agent(self):
        """Gets the ``Agent`` identified in this authentication credential.

        :return: the ``Agent``
        :rtype: ``osid.authentication.Agent``
        :raise: ``OperationFailed`` -- unable to complete request

        *compliance: mandatory -- This method must be implemented.*

        """
        agent_id = self.get_agent_id()
        return Agent(identifier=agent_id.identifier,
                     namespace=agent_id.namespace,
                     authority=agent_id.authority)

    agent = property(fget=get_agent)

    def is_valid(self):
        """Tests whether or not the credential represented by this ``Authentication`` is currently valid.

        A credential may be invalid because it has been destroyed,
        expired, or is somehow no longer able to be used.

        :return: ``true`` if this authentication credential is valid, ``false`` otherwise
        :rtype: ``boolean``

        *compliance: mandatory -- This method must be implemented.*
        *implementation notes*: Any problem in determining the validity
        of this credential should result in ``false``.

        """
        if self._django_user is not None:
            return self._django_user.is_authenticated()
        else:
            return False

    def has_expiration(self):
        """Tests if this authentication has an expiration.

        :return: ``true`` if this authentication has an expiration, ``false`` otherwise
        :rtype: ``boolean``


        *compliance: mandatory -- This method must be implemented.*

        """
        return False

    def get_expiration(self):
        """Gets the expiration date associated with this authentication credential.

        Consumers should check for the existence of a an expiration
        mechanism via ``hasExpiration()``.

        :return: the expiration date of this authentication credential
        :rtype: ``timestamp``
        :raise: ``IllegalState`` -- ``has_expiration()`` is ``false``

        *compliance: mandatory -- This method must be implemented.*

        """
        if not self.has_expiration():
            raise IllegalState()
        else:
            raise Unimplemented()

    expiration = property(fget=get_expiration)

    def has_credential(self):
        """Tests if this authentication has a credential for export.

        :return: ``true`` if this authentication has a credential, ``false`` otherwise
        :rtype: ``boolean``


        *compliance: mandatory -- This method must be implemented.*

        """
        if self._credential is None:
            return False
        else:
            return True

    def get_credential(self, credential_type):
        """Gets the credential represented by the given ``Type`` for transport to a remote service.

        :param credential_type: the credential format ``Type``
        :type credential_type: ``osid.type.Type``
        :return: the credential
        :rtype: ``object``
        :raise: ``IllegalState`` -- ``has_credential()`` is ``false``
        :raise: ``NullArgument`` -- ``credential_type`` is ``null``
        :raise: ``Unsupported`` -- the given ``credential_type`` is not supported

        *compliance: mandatory -- This method must be implemented.*
        *implementation notes*: A provider may support multiple
        credential formats for a variety of applications.

        """
        if self.has_credential():
            return self._credential
        else:
            raise IllegalState()

    def get_authentication_record(self, authentication_record_type):
        """Gets the authentication record corresponding to the given authentication record ``Type``.

        This method is used to retrieve an object implementing the
        requested record. The ``authentication_record_type`` may be the
        ``Type`` returned in ``get_record_types()`` or any of its
        parents in a ``Type`` hierarchy where
        ``has_record_type(authentication_record_type)`` is ``true`` .

        :param authentication_record_type: the type of authentication record to retrieve
        :type authentication_record_type: ``osid.type.Type``
        :return: the authentication record
        :rtype: ``osid.authentication.process.records.AuthenticationRecord``
        :raise: ``NullArgument`` -- ``authentication_record_type`` is ``null``
        :raise: ``OperationFailed`` -- unable to complete request
        :raise: ``PermissionDenied`` -- authorization failure occurred
        :raise: ``Unsupported`` -- ``has_record_type(authenticaton_record_type)`` is ``false``

        *compliance: mandatory -- This method must be implemented.*

        """
        return  # osid.authentication.process.records.AuthenticationRecord

    def set_django_user(self, django_user, use_user_id=False):
        self._django_user = django_user
        self._use_user_id = use_user_id


class XBlockAuthentication(abc_authentication_process_objects.Authentication, osid_objects.OsidObject):

    def __init__(self):
        self._xblock_user = None
        self._credential = None

    def _has_user_id(self, xblock_user):
        try:
            key = self._get_user_id_key(xblock_user)
        except KeyError:
            return False
        else:
            if xblock_user.opt_attrs[key] is not None:
                return True
            else:
                return False

    def _get_user_id_key(self, xblock_user):
        for k in xblock_user.opt_attrs:
            if '.user_id' in k:
                return k
        raise KeyError()

    def _is_authenticated(self, xblock_user):
        key = None
        for k in xblock_user.opt_attrs:
            if '.is_authenticated' in k:
                key = k
        if key is not None:
            try:
                return xblock_user.opt_attrs[key]
            except KeyError:
                return False
        return False

    def get_agent_id(self):
        if (self._xblock_user is not None and
                self._xblock_user.opt_attrs != {} and
                self._has_user_id(self._xblock_user) and
                self._is_authenticated(self._xblock_user)):
            key = self._get_user_id_key(self._xblock_user)
            return Id(identifier=self._xblock_user.opt_attrs[key],
                      namespace='osid.agent.Agent',
                      authority=key.split('.')[0])
        else:
            return Id(identifier='MC3GUE$T@MIT.EDU',
                      namespace='osid.agent.Agent',
                      authority='MIT-ODL')

    agent_id = property(fget=get_agent_id)

    def get_agent(self):
        agent_id = self.get_agent_id()
        return Agent(identifier=agent_id.identifier,
                     namespace=agent_id.namespace,
                     authority=agent_id.authority)

    agent = property(fget=get_agent)

    def is_valid(self):
        if (self._xblock_user is not None and
                self._is_authenticated(self._xblock_user)):
            return True
        else:
            return False

    def has_expiration(self):
        return False

    def get_expiration(self):
        if not self.has_expiration():
            raise IllegalState()
        else:
            raise Unimplemented()

    expiration = property(fget=get_expiration)

    def has_credential(self):
        if self._credential is None:
            return False
        else:
            return True

    def get_credential(self, credential_type):
        if self.has_credential():
            return self._credential
        else:
            raise IllegalState()

    def get_authentication_record(self, authentication_record_type):
        return  # osid.authentication.process.records.AuthenticationRecord

    def set_xblock_user(self, xblock_user):
        self._xblock_user = xblock_user


class Challenge(abc_authentication_process_objects.Challenge, osid_markers.Extensible, osid_markers.Browsable):
    """The challenge data."""

    def get_challenge_record(self, challenge_record_type):
        """Gets the challenge record corresponding to the given challenge record ``Type``.

        This method is used to retrieve an object implementing the
        requested record. The ``challenge_record_type`` may be the
        ``Type`` returned in ``get_record_types()`` or any of its
        parents in a ``Type`` hierarchy where
        ``has_record_type(challenge_record_type)`` is ``true`` .

        :param challenge_record_type: the type of challenge record to retrieve
        :type challenge_record_type: ``osid.type.Type``
        :return: the challenge record
        :rtype: ``osid.authentication.process.records.ChallengeRecord``
        :raise: ``NullArgument`` -- ``challenge_record_type`` is ``null``
        :raise: ``OperationFailed`` -- unable to complete request
        :raise: ``PermissionDenied`` -- authorization failure occurred
        :raise: ``Unsupported`` -- ``has_record_type(challenge_record_type)`` is ``false``

        *compliance: mandatory -- This method must be implemented.*

        """
        return  # osid.authentication.process.records.ChallengeRecord


class Trust(abc_authentication_process_objects.Trust, osid_objects.OsidObject):
    """``Trust`` represents the level of confidence in an authentication.

    An Authentication OSID Provider may issue different ``Agents`` based
    on the authentication mechanism. ``Trust`` is a grouping of
    ``Agent`` "types" that can be inferred as equivalent from an
    authorization point of view.

    The relationship among ``Agents`` and ``Trust`` is not explicity
    managed but understood by an Authentication OSID Provider when
    orchestration to an Authorization OSID Provider is desired.

    """

    def get_trust_record(self, trust_record_type):
        """Gets the trust record corresponding to the given trust record ``Type``.

        This method is used to retrieve an object implementing the
        requested. The ``trust_record_type`` may be the ``Type``
        returned in ``get_record_types()`` or any of its parents in a
        ``Type`` hierarchy where ``has_record_type(trust_record_type)``
        is ``true`` .

        :param trust_record_type: the type of trust record to retrieve
        :type trust_record_type: ``osid.type.Type``
        :return: the trust record
        :rtype: ``osid.authentication.process.records.TrustRecord``
        :raise: ``NullArgument`` -- ``trust_record_type`` is ``null``
        :raise: ``OperationFailed`` -- unable to complete request
        :raise: ``PermissionDenied`` -- authorization failure occurred
        :raise: ``Unsupported`` -- ``has_record_type(trust_record_type)`` is ``false``

        *compliance: mandatory -- This method must be implemented.*

        """
        return  # osid.authentication.process.records.TrustRecord


class TrustList(abc_authentication_process_objects.TrustList, osid_objects.OsidList):
    """Like all ``OsidLists,``  ``TrustList`` provides a means for accessing ``Trust`` elements sequentially either one at a time or many at a time.

    Examples: while (tl.hasNext()) { Trust trust = tl.getNextTrust(); }

    or
      while (tl.hasNext()) {
           Trust[] trusts = tl.getNextTrusts(tl.available());
      }
    """

    def get_next_trust(self):
        """Gets the next ``Trust`` in this list.

        :return: the next ``Trust`` in this list. The ``has_next()`` method should be used to test that a next ``Trust`` is available before calling this method.
        :rtype: ``osid.authentication.process.Trust``
        :raise: ``IllegalState`` -- no more elements available in this list
        :raise: ``OperationFailed`` -- unable to complete request

        *compliance: mandatory -- This method must be implemented.*

        """
        return  # osid.authentication.process.Trust

    next_trust = property(fget=get_next_trust)

    def get_next_trusts(self, n):
        """Gets the next set of ``Trust`` elements in this list which must be less than or equal to the return from ``available()``.

        :param n: the number of ``Trust`` elements requested which must be less than or equal to ``available()``
        :type n: ``cardinal``
        :return: an array of ``Trust`` elements.The length of the array is less than or equal to the number specified.
        :rtype: ``osid.authentication.process.Trust``
        :raise: ``IllegalState`` -- no more elements available in this list
        :raise: ``OperationFailed`` -- unable to complete request

        *compliance: mandatory -- This method must be implemented.*

        """
        return  # osid.authentication.process.Trust
