# -*- coding: utf-8 -*-

# Copyright (c) 2021, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license.  See the LICENSE file for details.

from aniso8601.builders import TupleBuilder, DateTuple, DatetimeTuple
from aniso8601.builders.python import PythonTimeBuilder
from aniso8601.compat import is_string
from aniso8601.date import parse_date
from aniso8601.duration import parse_duration
from aniso8601.exceptions import ISOFormatError
from aniso8601.resolution import IntervalResolution
from aniso8601.time import parse_datetime, parse_time

def get_interval_resolution(isointervalstr, intervaldelimiter='/',
                            datetimedelimiter='T'):
    isointervaltuple = parse_interval(isointervalstr, intervaldelimiter=intervaldelimiter,
                                      datetimedelimiter=datetimedelimiter, builder=TupleBuilder)

    return _get_interval_resolution(isointervaltuple)

def get_repeating_interval_resolution(isointervalstr, intervaldelimiter='/',
                                      datetimedelimiter='T'):
    repeatingintervaltuple = parse_repeating_interval(isointervalstr, intervaldelimiter=intervaldelimiter,
                                                     datetimedelimiter=datetimedelimiter, builder=TupleBuilder)

    return _get_interval_resolution(repeatingintervaltuple.interval)

def _get_interval_resolution(intervaltuple):
    if intervaltuple.start is not None and intervaltuple.end is not None:
        return max(_get_interval_component_resolution(intervaltuple.start), _get_interval_component_resolution(intervaltuple.end))

    if intervaltuple.start is not None and intervaltuple.duration is not None:
        return max(_get_interval_component_resolution(intervaltuple.start), _get_interval_component_resolution(intervaltuple.duration))

    return max(_get_interval_component_resolution(intervaltuple.end), _get_interval_component_resolution(intervaltuple.duration))

def _get_interval_component_resolution(componenttuple):
    if type(componenttuple) is DateTuple:
        if componenttuple.DDD is not None:
            #YYYY-DDD
            #YYYYDDD
            return IntervalResolution.Ordinal

        if componenttuple.D is not None:
            #YYYY-Www-D
            #YYYYWwwD
            return IntervalResolution.Weekday

        if componenttuple.Www is not None:
            #YYYY-Www
            #YYYYWww
            return IntervalResolution.Week

        if componenttuple.DD is not None:
            #YYYY-MM-DD
            #YYYYMMDD
            return IntervalResolution.Day

        if componenttuple.MM is not None:
            #YYYY-MM
            return IntervalResolution.Month

        #Y[YYY]
        return IntervalResolution.Year
    elif type(componenttuple) is DatetimeTuple:
        #Datetime
        if componenttuple.time.ss is not None:
            return IntervalResolution.Seconds

        if componenttuple.time.mm is not None:
            return IntervalResolution.Minutes

        return IntervalResolution.Hours

    #Duration
    if componenttuple.TnS is not None:
        return IntervalResolution.Seconds

    if componenttuple.TnM is not None:
        return IntervalResolution.Minutes

    if componenttuple.TnH is not None:
        return IntervalResolution.Hours

    if componenttuple.PnD is not None:
        return IntervalResolution.Day

    if componenttuple.PnW is not None:
        return IntervalResolution.Week

    if componenttuple.PnM is not None:
        return IntervalResolution.Month

    return IntervalResolution.Year

def parse_interval(isointervalstr, intervaldelimiter='/',
                   datetimedelimiter='T', builder=PythonTimeBuilder):
    #Given a string representing an ISO 8601 interval, return an
    #interval built by the given builder. Valid formats are:
    #
    #<start>/<end>
    #<start>/<duration>
    #<duration>/<end>
    #
    #The <start> and <end> values can represent dates, or datetimes,
    #not times.
    #
    #The format:
    #
    #<duration>
    #
    #Is expressly not supported as there is no way to provide the additional
    #required context.

    if is_string(isointervalstr) is False:
        raise ValueError('Interval must be string.')

    if len(isointervalstr) == 0:
        raise ISOFormatError('Interval string is empty.')

    if isointervalstr[0] == 'R':
        raise ISOFormatError('ISO 8601 repeating intervals must be parsed '
                             'with parse_repeating_interval.')

    intervaldelimitercount = isointervalstr.count(intervaldelimiter)

    if intervaldelimitercount == 0:
        raise ISOFormatError('Interval delimiter "{0}" is not in interval '
                              'string "{1}".'
                             .format(intervaldelimiter, isointervalstr))

    if intervaldelimitercount > 1:
        raise ISOFormatError('{0} is not a valid ISO 8601 interval'
                             .format(isointervalstr))

    return _parse_interval(isointervalstr, builder,
                           intervaldelimiter, datetimedelimiter)

def parse_repeating_interval(isointervalstr, intervaldelimiter='/',
                             datetimedelimiter='T', builder=PythonTimeBuilder):
    #Given a string representing an ISO 8601 interval repeating, return an
    #interval built by the given builder. Valid formats are:
    #
    #Rnn/<interval>
    #R/<interval>

    if not isinstance(isointervalstr, str):
        raise ValueError('Interval must be string.')

    if len(isointervalstr) == 0:
        raise ISOFormatError('Repeating interval string is empty.')

    if isointervalstr[0] != 'R':
        raise ISOFormatError('ISO 8601 repeating interval must start '
                             'with an R.')

    if intervaldelimiter not in isointervalstr:
        raise ISOFormatError('Interval delimiter "{0}" is not in interval '
                              'string "{1}".'
                             .format(intervaldelimiter, isointervalstr))

    #Parse the number of iterations
    iterationpart, intervalpart = isointervalstr.split(intervaldelimiter, 1)

    if len(iterationpart) > 1:
        R = False
        Rnn = iterationpart[1:]
    else:
        R = True
        Rnn = None

    interval = _parse_interval(intervalpart, TupleBuilder,
                               intervaldelimiter, datetimedelimiter)

    return builder.build_repeating_interval(R=R, Rnn=Rnn, interval=interval)

def _parse_interval(isointervalstr, builder, intervaldelimiter='/',
                    datetimedelimiter='T'):
    #Returns a tuple containing the start of the interval, the end of the
    #interval, and or the interval duration

    firstpart, secondpart = isointervalstr.split(intervaldelimiter)

    if len(firstpart) == 0 or len(secondpart) == 0:
        raise ISOFormatError('{0} is not a valid ISO 8601 interval'
                             .format(isointervalstr))

    if firstpart[0] == 'P':
        #<duration>/<end>
        #Notice that these are not returned 'in order' (earlier to later), this
        #is to maintain consistency with parsing <start>/<end> durations, as
        #well as making repeating interval code cleaner. Users who desire
        #durations to be in order can use the 'sorted' operator.
        duration = parse_duration(firstpart, builder=TupleBuilder)

        #We need to figure out if <end> is a date, or a datetime
        if secondpart.find(datetimedelimiter) != -1:
            #<end> is a datetime
            endtuple = parse_datetime(secondpart,
                                      delimiter=datetimedelimiter,
                                      builder=TupleBuilder)
        else:
            endtuple = parse_date(secondpart, builder=TupleBuilder)

        return builder.build_interval(end=endtuple, duration=duration)
    elif secondpart[0] == 'P':
        #<start>/<duration>
        #We need to figure out if <start> is a date, or a datetime
        duration = parse_duration(secondpart, builder=TupleBuilder)

        if firstpart.find(datetimedelimiter) != -1:
            #<start> is a datetime
            starttuple = parse_datetime(firstpart,
                                        delimiter=datetimedelimiter,
                                        builder=TupleBuilder)
        else:
            #<start> must just be a date
            starttuple = parse_date(firstpart, builder=TupleBuilder)

        return builder.build_interval(start=starttuple,
                                      duration=duration)

    #<start>/<end>
    if firstpart.find(datetimedelimiter) != -1:
        #Both parts are datetimes
        starttuple = parse_datetime(firstpart,
                                    delimiter=datetimedelimiter,
                                    builder=TupleBuilder)
    else:
        starttuple = parse_date(firstpart, builder=TupleBuilder)

    endtuple = _parse_interval_end(secondpart,
                                   starttuple,
                                   datetimedelimiter)

    return builder.build_interval(start=starttuple, end=endtuple)

def _parse_interval_end(endstr, starttuple, datetimedelimiter):
    datestr = None
    timestr = None

    monthstr = None
    daystr = None

    concise = False

    if type(starttuple) is DateTuple:
        startdatetuple = starttuple
    else:
        #Start is a datetime
        startdatetuple = starttuple.date

    if datetimedelimiter in endstr:
        datestr, timestr = endstr.split(datetimedelimiter, 1)
    elif ':' in endstr:
        timestr = endstr
    else:
        datestr = endstr

    if timestr is not None:
        endtimetuple = parse_time(timestr, builder=TupleBuilder)

    #End is just a time
    if datestr is None:
        return endtimetuple

    #Handle backwards concise representation
    if datestr.count('-') == 1:
        monthstr, daystr = datestr.split('-')
        concise = True
    elif len(datestr) <= 2:
        daystr = datestr
        concise = True
    elif len(datestr) <= 4:
        monthstr = datestr[0:2]
        daystr = datestr[2:]
        concise = True

    if concise is True:
        concisedatestr = startdatetuple.YYYY

        #Separators required because concise elements may be missing digits
        if monthstr is not None:
            concisedatestr += '-' + monthstr
        elif startdatetuple.MM is not None:
            concisedatestr += '-' + startdatetuple.MM

        concisedatestr += '-' + daystr

        enddatetuple = parse_date(concisedatestr, builder=TupleBuilder)

        #Clear unsupplied components
        if monthstr is None:
            enddatetuple = TupleBuilder.build_date(DD=enddatetuple.DD)
        else:
            #Year not provided
            enddatetuple = TupleBuilder.build_date(MM=enddatetuple.MM,
                                                   DD=enddatetuple.DD)
    else:
        enddatetuple = parse_date(datestr, builder=TupleBuilder)

    if timestr is None:
        return enddatetuple

    return TupleBuilder.build_datetime(enddatetuple, endtimetuple)
