#
# This file is part of pyasn1 software.
#
# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
# License: https://pyasn1.readthedocs.io/en/latest/license.html
#
import datetime

from pyasn1 import error
from pyasn1.type import char
from pyasn1.type import tag
from pyasn1.type import univ

__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime']

NoValue = univ.NoValue
noValue = univ.noValue


class ObjectDescriptor(char.GraphicString):
    __doc__ = char.GraphicString.__doc__

    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
    tagSet = char.GraphicString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7)
    )

    # Optimization for faster codec lookup
    typeId = char.GraphicString.getTypeId()


class TimeMixIn(object):

    _yearsDigits = 4
    _hasSubsecond = False
    _optionalMinutes = False
    _shortTZ = False

    class FixedOffset(datetime.tzinfo):
        """Fixed offset in minutes east from UTC."""

        # defaulted arguments required
        # https: // docs.python.org / 2.3 / lib / datetime - tzinfo.html
        def __init__(self, offset=0, name='UTC'):
            self.__offset = datetime.timedelta(minutes=offset)
            self.__name = name

        def utcoffset(self, dt):
            return self.__offset

        def tzname(self, dt):
            return self.__name

        def dst(self, dt):
            return datetime.timedelta(0)

    UTC = FixedOffset()

    @property
    def asDateTime(self):
        """Create :py:class:`datetime.datetime` object from a |ASN.1| object.

        Returns
        -------
        :
            new instance of :py:class:`datetime.datetime` object
        """
        text = str(self)
        if text.endswith('Z'):
            tzinfo = TimeMixIn.UTC
            text = text[:-1]

        elif '-' in text or '+' in text:
            if '+' in text:
                text, plusminus, tz = text.partition('+')
            else:
                text, plusminus, tz = text.partition('-')

            if self._shortTZ and len(tz) == 2:
                tz += '00'

            if len(tz) != 4:
                raise error.PyAsn1Error('malformed time zone offset %s' % tz)

            try:
                minutes = int(tz[:2]) * 60 + int(tz[2:])
                if plusminus == '-':
                    minutes *= -1

            except ValueError:
                raise error.PyAsn1Error('unknown time specification %s' % self)

            tzinfo = TimeMixIn.FixedOffset(minutes, '?')

        else:
            tzinfo = None

        if '.' in text or ',' in text:
            if '.' in text:
                text, _, ms = text.partition('.')
            else:
                text, _, ms = text.partition(',')

            try:
                # Normalize variable-length fraction to microseconds
                ms = int(ms.ljust(6, '0')[:6])

            except ValueError:
                raise error.PyAsn1Error('bad sub-second time specification %s' % self)

        else:
            ms = 0

        if self._optionalMinutes and len(text) - self._yearsDigits == 6:
            text += '0000'
        elif len(text) - self._yearsDigits == 8:
            text += '00'

        try:
            dt = datetime.datetime.strptime(text, self._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')

        except ValueError:
            raise error.PyAsn1Error('malformed datetime format %s' % self)

        return dt.replace(microsecond=ms, tzinfo=tzinfo)

    @classmethod
    def fromDateTime(cls, dt):
        """Create |ASN.1| object from a :py:class:`datetime.datetime` object.

        Parameters
        ----------
        dt: :py:class:`datetime.datetime` object
            The `datetime.datetime` object to initialize the |ASN.1| object
            from

        Returns
        -------
        :
            new instance of |ASN.1| value
        """
        text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
        if cls._hasSubsecond and dt.microsecond:
            text += ('.%06d' % dt.microsecond).rstrip('0')

        if dt.utcoffset():
            seconds = dt.utcoffset().seconds
            if seconds < 0:
                text += '-'
            else:
                text += '+'
            text += '%.2d%.2d' % (seconds // 3600, seconds % 3600)
        else:
            text += 'Z'

        return cls(text)


class GeneralizedTime(char.VisibleString, TimeMixIn):
    __doc__ = char.VisibleString.__doc__

    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
    tagSet = char.VisibleString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24)
    )

    # Optimization for faster codec lookup
    typeId = char.VideotexString.getTypeId()

    _yearsDigits = 4
    _hasSubsecond = True
    _optionalMinutes = True
    _shortTZ = True


class UTCTime(char.VisibleString, TimeMixIn):
    __doc__ = char.VisibleString.__doc__

    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
    tagSet = char.VisibleString.tagSet.tagImplicitly(
        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23)
    )

    # Optimization for faster codec lookup
    typeId = char.VideotexString.getTypeId()

    _yearsDigits = 2
    _hasSubsecond = False
    _optionalMinutes = False
    _shortTZ = False
