# -*- coding: utf-8 -*-
"""
parsedatetime/context.py

Context related classes

"""

from threading import local


class pdtContextStack(object):
    """
    A thread-safe stack to store context(s)

    Internally used by L{Calendar} object
    """

    def __init__(self):
        self.__local = local()

    @property
    def __stack(self):
        if not hasattr(self.__local, 'stack'):
            self.__local.stack = []
        return self.__local.stack

    def push(self, ctx):
        self.__stack.append(ctx)

    def pop(self):
        try:
            return self.__stack.pop()
        except IndexError:
            return None

    def last(self):
        try:
            return self.__stack[-1]
        except IndexError:
            raise RuntimeError('context stack is empty')

    def isEmpty(self):
        return not self.__stack


class pdtContext(object):
    """
    Context contains accuracy flag detected by L{Calendar.parse()}

    Accuracy flag uses bitwise-OR operation and is combined by:

        ACU_YEAR - "next year", "2014"
        ACU_MONTH - "March", "July 2014"
        ACU_WEEK - "last week", "next 3 weeks"
        ACU_DAY - "tomorrow", "July 4th 2014"
        ACU_HALFDAY - "morning", "tonight"
        ACU_HOUR - "18:00", "next hour"
        ACU_MIN - "18:32", "next 10 minutes"
        ACU_SEC - "18:32:55"
        ACU_NOW - "now"

    """

    __slots__ = ('accuracy', )

    ACU_YEAR = 2**0
    ACU_MONTH = 2**1
    ACU_WEEK = 2**2
    ACU_DAY = 2**3
    ACU_HALFDAY = 2**4
    ACU_HOUR = 2**5
    ACU_MIN = 2**6
    ACU_SEC = 2**7
    ACU_NOW = 2**8

    ACU_DATE = ACU_YEAR | ACU_MONTH | ACU_WEEK | ACU_DAY
    ACU_TIME = ACU_HALFDAY | ACU_HOUR | ACU_MIN | ACU_SEC | ACU_NOW

    _ACCURACY_MAPPING = [
        (ACU_YEAR, 'year'),
        (ACU_MONTH, 'month'),
        (ACU_WEEK, 'week'),
        (ACU_DAY, 'day'),
        (ACU_HALFDAY, 'halfday'),
        (ACU_HOUR, 'hour'),
        (ACU_MIN, 'min'),
        (ACU_SEC, 'sec'),
        (ACU_NOW, 'now')]

    _ACCURACY_REVERSE_MAPPING = {
        'year': ACU_YEAR,
        'years': ACU_YEAR,
        'month': ACU_MONTH,
        'months': ACU_MONTH,
        'week': ACU_WEEK,
        'weeks': ACU_WEEK,
        'day': ACU_DAY,
        'days': ACU_DAY,
        'halfday': ACU_HALFDAY,
        'morning': ACU_HALFDAY,
        'afternoon': ACU_HALFDAY,
        'evening': ACU_HALFDAY,
        'night': ACU_HALFDAY,
        'tonight': ACU_HALFDAY,
        'midnight': ACU_HALFDAY,
        'hour': ACU_HOUR,
        'hours': ACU_HOUR,
        'min': ACU_MIN,
        'minute': ACU_MIN,
        'mins': ACU_MIN,
        'minutes': ACU_MIN,
        'sec': ACU_SEC,
        'second': ACU_SEC,
        'secs': ACU_SEC,
        'seconds': ACU_SEC,
        'now': ACU_NOW}

    def __init__(self, accuracy=0):
        """
        Default constructor of L{pdtContext} class.

        @type  accuracy: integer
        @param accuracy: Accuracy flag

        @rtype:  object
        @return: L{pdtContext} instance
        """
        self.accuracy = accuracy

    def updateAccuracy(self, *accuracy):
        """
        Updates current accuracy flag
        """
        for acc in accuracy:
            if not isinstance(acc, int):
                acc = self._ACCURACY_REVERSE_MAPPING[acc]
            self.accuracy |= acc

    def update(self, context):
        """
        Uses another L{pdtContext} instance to update current one
        """
        self.updateAccuracy(context.accuracy)

    @property
    def hasDate(self):
        """
        Returns True if current context is accurate to date
        """
        return bool(self.accuracy & self.ACU_DATE)

    @property
    def hasTime(self):
        """
        Returns True if current context is accurate to time
        """
        return bool(self.accuracy & self.ACU_TIME)

    @property
    def dateTimeFlag(self):
        """
        Returns the old date/time flag code
        """
        return int(self.hasDate and 1) | int(self.hasTime and 2)

    @property
    def hasDateOrTime(self):
        """
        Returns True if current context is accurate to date/time
        """
        return bool(self.accuracy)

    def __repr__(self):
        accuracy_repr = []
        for acc, name in self._ACCURACY_MAPPING:
            if acc & self.accuracy:
                accuracy_repr.append('pdtContext.ACU_%s' % name.upper())
        if accuracy_repr:
            accuracy_repr = 'accuracy=' + ' | '.join(accuracy_repr)
        else:
            accuracy_repr = ''

        return 'pdtContext(%s)' % accuracy_repr

    def __eq__(self, ctx):
        return self.accuracy == ctx.accuracy
