# -*- coding: utf-8 -*-
"""
datetime manager module.
"""

from datetime import datetime

import pytz

import pyrin.configuration.services as config_services
import pyrin.security.session.services as session_services
import pyrin.utils.datetime as datetime_utils

from pyrin.core.structs import Manager
from pyrin.globalization.datetime import DateTimePackage


class DateTimeManager(Manager):
    """
    datetime manager class.
    """

    package_class = DateTimePackage

    def __init__(self, **options):
        """
        initializes an instance of DateTimeManager.
        """

        super().__init__()

        server_timezone = config_services.get('globalization',
                                              'locale', 'babel_default_timezone')

        client_timezone = config_services.get('globalization',
                                              'locale', 'client_timezone')

        self._server_timezone = pytz.timezone(server_timezone)
        self._client_timezone = pytz.timezone(client_timezone)

    def _add_timezone(self, value, server):
        """
        adds the current timezone info into input value if it has no timezone info.

        :param datetime value: value to add timezone info into it.
        :param bool server: specifies that server or client timezone must be added.

        :rtype: datetime
        """

        localized_value = value
        if value.tzinfo is None:
            localized_value = self.localize(value, server)

        return localized_value

    def now(self, server=True, timezone=None):
        """
        gets the current datetime based on requested timezone.

        :param bool server: if set to True, server timezone will be used.
                            if set to False, client timezone will be used.
                            defaults to True.

        :param str timezone: timezone name to get datetime based on it.
                             if provided, the value of `server` input
                             will be ignored. defaults to None.

        :rtype: datetime
        """

        if timezone not in (None, ''):
            timezone = self.get_timezone(timezone)
        else:
            timezone = self.get_current_timezone(server=server)

        return datetime.now(timezone)

    def client_now(self):
        """
        gets the current datetime based on client timezone.

        this is a helper method to let get the client datetime
        without providing value to `now` method.

        :rtype: datetime
        """

        return self.now(server=False)

    def current_year(self, server=True, timezone=None):
        """
        gets the current year based on requested timezone.

        :param bool server: if set to True, server timezone will be used.
                            if set to False, client timezone will be used.
                            defaults to True.

        :param str timezone: timezone name to get year based on it.
                             if provided, the value of `server` input
                             will be ignored. defaults to None.

        :rtype: int
        """

        now = self.now(server=server, timezone=timezone)
        return now.year

    def current_client_year(self):
        """
        gets the current year based on client timezone.

        this is a helper method to let get the client year
        without providing value to `current_year` method.

        :rtype: int
        """

        client_now = self.client_now()
        return client_now.year

    def get_default_client_timezone(self):
        """
        gets the default client timezone.

        :rtype: tzinfo
        """

        return self._client_timezone

    def get_current_timezone(self, server):
        """
        gets the current timezone for server or client.

        :param bool server: if set to True, server timezone will be returned.
                            if set to False, client timezone will be returned.

        :rtype: tzinfo
        """

        if server is True:
            return self._server_timezone
        else:
            request = session_services.get_safe_current_request()
            timezone = None
            if request is not None:
                timezone = request.timezone

            return timezone or self._client_timezone

    def get_timezone(self, timezone):
        """
        gets the timezone based on given timezone name.

        :param str timezone: timezone name.

        :rtype: tzinfo
        """

        return pytz.timezone(timezone)

    def get_timezone_name(self, server):
        """
        gets the server or client timezone name.

        :param bool server: if set to True, server timezone name will be returned.
                            if set to False, client timezone name will be returned.

        :rtype: str
        """

        return self.get_current_timezone(server).zone

    def as_timezone(self, value, server):
        """
        gets the result of `astimezone` on the given value.

        :param datetime value: value to get normalized.

        :param bool server: if set to True, server timezone name will be used.
                            if set to False, client timezone name will be used.

        :rtype: datetime
        """

        timezone = self.get_current_timezone(server=server)
        return value.astimezone(timezone)

    def normalize(self, value, server):
        """
        normalizes input value using server or client current timezone and returns it.

        :param datetime value: value to get normalized.
        :param bool server: specifies that server or client timezone must used.

        :rtype: datetime
        """

        value = self.as_timezone(value, server=server)
        return self.get_current_timezone(server).normalize(value)

    def localize(self, value, server):
        """
        localizes input datetime with current timezone.

        input value should not have a timezone info.

        :param datetime value: value to be localized.
        :param bool server: specifies that server or client timezone must used.

        :rtype: datetime
        """

        return self.get_current_timezone(server).localize(value)

    def replace_timezone(self, value, server):
        """
        replaces given value's timezone with server or client timezone.

        it returns a new object.
        note that this method does not normalize the value, it just replaces the
        timezone and localizes the value.

        :param datetime value: value to replace its timezone.
        :param bool server: specifies that server or client timezone must used.

        :rtype: datetime
        """

        value = value.replace(tzinfo=None)
        return self._add_timezone(value, server)

    def to_datetime_string(self, value, to_server, from_server=None):
        """
        gets the datetime string representation of input value.

        if the value has no timezone info, it adds the client or server
        timezone info based on `from_server` value.

        example: `2015-12-24T22:40:15+01:00`

        :param datetime value: input object to be converted.

        :param bool to_server: specifies that value must be normalized
                               to server timezone. if set to False, it
                               will be normalized to client timezone.

        :param bool from_server: specifies that value must be normalized
                                 from server timezone. if set to False, it
                                 will be normalized from client timezone.
                                 if not provided, it will be set to opposite
                                 of `to_server` value.

        :rtype: str
        """

        if from_server is None:
            from_server = not to_server

        localized_value = self._add_timezone(value, server=from_server)
        localized_value = self.normalize(localized_value, server=to_server)
        return datetime_utils.to_datetime_string(localized_value)

    def to_date_string(self, value):
        """
        gets the date string representation of input value.

        example: `2015-12-24`

        :param datetime | date value: input object to be converted.

        :rtype: str
        """

        return datetime_utils.to_date_string(value)

    def to_time_string(self, value, to_server, from_server=None):
        """
        gets the time string representation of input value.

        if the value is a datetime and has no timezone info, it adds
        the client or server timezone info based on `from_server` value.

        example: `23:40:15`

        :param datetime | time value: input object to be converted.

        :param bool to_server: specifies that value must be normalized
                               to server timezone. if set to False, it
                               will be normalized to client timezone.

        :param bool from_server: specifies that value must be normalized
                                 from server timezone. if set to False, it
                                 will be normalized from client timezone.
                                 if not provided, it will be set to opposite
                                 of `to_server` value.

        :rtype: str
        """

        if from_server is None:
            from_server = not to_server

        localized_value = value
        if isinstance(value, datetime):
            localized_value = self._add_timezone(value, server=from_server)
            localized_value = self.normalize(localized_value, server=to_server)

        return datetime_utils.to_time_string(localized_value)

    def to_datetime(self, value, to_server, from_server=None):
        """
        converts the input value to it's equivalent python datetime.

        if the value has no timezone info, it adds the client or server
        timezone info based on `from_server` value.

        :param str value: string representation of datetime to be converted.

        :param bool to_server: specifies that value must be normalized
                               to server timezone. if set to False, it
                               will be normalized to client timezone.

        :param bool from_server: specifies that value must be normalized
                                 from server timezone. if set to False, it
                                 will be normalized from client timezone.
                                 if not provided, it will be set to opposite
                                 of `to_server` value.

        :rtype: datetime
        """

        if from_server is None:
            from_server = not to_server

        converted_datetime = datetime_utils.to_datetime(value)
        converted_datetime = self._add_timezone(converted_datetime, server=from_server)
        return self.normalize(converted_datetime, server=to_server)

    def to_date(self, value):
        """
        converts the input value to it's equivalent python date.

        :param str value: string representation of date to be converted.

        :rtype: date
        """

        return datetime_utils.to_date(value)

    def to_time(self, value):
        """
        converts the input value to it's equivalent python time.

        :param str value: string representation of time to be converted.

        :rtype: time
        """

        return datetime_utils.to_time(value)

    def timezone_exists(self, timezone_name):
        """
        gets a value indicating that a timezone with the given name exists.

        :param str timezone_name: timezone name to check for existence.

        :rtype: bool
        """

        return timezone_name in pytz.all_timezones_set

    def get_current_timestamp(self, date_sep='-', main_sep=' ',
                              time_sep=':', server=True, timezone=None):
        """
        gets the current timestamp with specified separators based on requested timezone.

        :param str date_sep: a separator to put between date elements.
        :param str main_sep: a separator to put between date and time part.
        :param str time_sep: a separator to put between time elements.

        :param bool server: if set to True, server timezone will be used.
                            if set to False, client timezone will be used.
                            defaults to True.

        :param str timezone: timezone name to get datetime based on it.
                             if provided, the value of `server` input
                             will be ignored. defaults to None.

        :rtype: str
        """

        if date_sep is None:
            date_sep = ''
        if main_sep is None:
            main_sep = ''
        if time_sep is None:
            time_sep = ''

        current = self.now(server=server, timezone=timezone)
        return '{year}{date_sep}{month}{date_sep}{day}{main_sep}' \
               '{hour}{time_sep}{minute}{time_sep}{second}'.format(
                year=str(current.year).zfill(4),
                month=str(current.month).zfill(2),
                day=str(current.day).zfill(2),
                hour=str(current.hour).zfill(2),
                minute=str(current.minute).zfill(2),
                second=str(current.second).zfill(2),
                date_sep=date_sep,
                main_sep=main_sep,
                time_sep=time_sep)
