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

# Copyright (c) 2016, 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.

import datetime

from aniso8601.timezone import parse_timezone, build_utcoffset
from aniso8601.date import parse_date
from aniso8601.resolution import TimeResolution

def get_time_resolution(isotimestr):
    #Valid time formats are:
    #
    #hh:mm:ss
    #hhmmss
    #hh:mm
    #hhmm
    #hh
    #hh:mm:ssZ
    #hhmmssZ
    #hh:mmZ
    #hhmmZ
    #hhZ
    #hh:mm:ss±hh:mm
    #hhmmss±hh:mm
    #hh:mm±hh:mm
    #hhmm±hh:mm
    #hh±hh:mm
    #hh:mm:ss±hhmm
    #hhmmss±hhmm
    #hh:mm±hhmm
    #hhmm±hhmm
    #hh±hhmm
    #hh:mm:ss±hh
    #hhmmss±hh
    #hh:mm±hh
    #hhmm±hh
    #hh±hh

    timestr = _split_tz(isotimestr)[0]

    if timestr.count(':') == 2:
        #hh:mm:ss
        return TimeResolution.Seconds
    elif timestr.count(':') == 1:
        #hh:mm
        return TimeResolution.Minutes

    #Format must be hhmmss, hhmm, or hh
    if timestr.find('.') == -1:
        #No time fractions
        timestrlen = len(timestr)
    else:
        #The lowest order element is a fraction
        timestrlen = len(timestr.split('.')[0])

    if timestrlen == 6:
        #hhmmss
        return TimeResolution.Seconds
    elif timestrlen == 4:
        #hhmm
        return TimeResolution.Minutes
    elif timestrlen == 2:
        #hh
        return TimeResolution.Hours
    else:
        raise ValueError('String is not a valid ISO 8601 time.')

def parse_time(isotimestr):
    #Given a string in any ISO 8601 time format, return a datetime.time object
    #that corresponds to the given time. Fixed offset tzdata will be included
    #if UTC offset is given in the input string. Valid time formats are:
    #
    #hh:mm:ss
    #hhmmss
    #hh:mm
    #hhmm
    #hh
    #hh:mm:ssZ
    #hhmmssZ
    #hh:mmZ
    #hhmmZ
    #hhZ
    #hh:mm:ss±hh:mm
    #hhmmss±hh:mm
    #hh:mm±hh:mm
    #hhmm±hh:mm
    #hh±hh:mm
    #hh:mm:ss±hhmm
    #hhmmss±hhmm
    #hh:mm±hhmm
    #hhmm±hhmm
    #hh±hhmm
    #hh:mm:ss±hh
    #hhmmss±hh
    #hh:mm±hh
    #hhmm±hh
    #hh±hh

    (timestr, tzstr) = _split_tz(isotimestr)

    if tzstr == None:
        return _parse_time_naive(timestr)
    elif tzstr == 'Z':
        return _parse_time_naive(timestr).replace(tzinfo=build_utcoffset('UTC', datetime.timedelta(hours=0)))
    else:
        return _parse_time_naive(timestr).replace(tzinfo=parse_timezone(tzstr))

def parse_datetime(isodatetimestr, delimiter='T'):
    #Given a string in ISO 8601 date time format, return a datetime.datetime
    #object that corresponds to the given date time.
    #By default, the ISO 8601 specified T delimiter is used to split the
    #date and time (<date>T<time>). Fixed offset tzdata will be included
    #if UTC offset is given in the input string.

    isodatestr, isotimestr = isodatetimestr.split(delimiter)

    datepart = parse_date(isodatestr)
    timepart = parse_time(isotimestr)

    return datetime.datetime.combine(datepart, timepart)

def _parse_time_naive(timestr):
    #timestr is of the format hh:mm:ss, hh:mm, hhmmss, hhmm, hh
    #
    #hh is between 0 and 24, 24 is not allowed in the Python time format, since
    #it represents midnight, a time of 00:00:00 is returned
    #
    #mm is between 0 and 60, with 60 used to denote a leap second
    #
    #No tzinfo will be included
    return _resolution_map[get_time_resolution(timestr)](timestr)

def _parse_hour(timestr):
    #Format must be hh or hh.
    isohour = float(timestr)

    if isohour == 24:
        return datetime.time(hour=0, minute=0)

    #Since the time constructor doesn't handle fractional hours, we put
    #the hours in to a timedelta, and add it to the time before returning
    hoursdelta = datetime.timedelta(hours=isohour)

    return _build_time(datetime.time(hour=0), hoursdelta)

def _parse_minute_time(timestr):
    #Format must be hhmm, hhmm., hh:mm or hh:mm.
    if timestr.count(':') == 1:
        #hh:mm or hh:mm.
        timestrarray = timestr.split(':')

        isohour = int(timestrarray[0])
        isominute = float(timestrarray[1])  #Minute may now be a fraction
    else:
        #hhmm or hhmm.
        isohour = int(timestr[0:2])
        isominute = float(timestr[2:])

    if isominute > 60:
        raise ValueError('ISO 8601 minute element cannot be greater than 60.')

    if isohour == 24:
        return datetime.time(hour=0, minute=0)

    #Since the time constructor doesn't handle fractional minutes, we put
    #the minutes in to a timedelta, and add it to the time before returning
    minutesdelta = datetime.timedelta(minutes = isominute)

    return _build_time(datetime.time(hour=isohour), minutesdelta)

def _parse_second_time(timestr):
    #Format must be hhmmss, hhmmss., hh:mm:ss or hh:mm:ss.
    if timestr.count(':') == 2:
        #hh:mm:ss or hh:mm:ss.
        timestrarray = timestr.split(':')

        isohour = int(timestrarray[0])
        isominute = int(timestrarray[1])

        #Since the time constructor doesn't handle fractional seconds, we put
        #the seconds in to a timedelta, and add it to the time before returning
        secondsdelta = datetime.timedelta(seconds = float(timestrarray[2]))
    else:
        #hhmmss or hhmmss.
        isohour = int(timestr[0:2])
        isominute = int(timestr[2:4])

        #Since the time constructor doesn't handle fractional seconds, we put
        #the seconds in to a timedelta, and add it to the time before returning
        secondsdelta = datetime.timedelta(seconds = float(timestr[4:]))

    if isominute > 60:
        raise ValueError('ISO 8601 minute element cannot be greater than 60.')

    if isohour == 24:
        return datetime.time(hour=0, minute=0)

    return _build_time(datetime.time(hour=isohour, minute=isominute),
                      secondsdelta)

def _build_time(time, delta):
    #Combine today's date (just so we have a date object), the time, the
    #delta, and return the time component
    base_datetime = datetime.datetime.combine(datetime.date.today(), time)
    return (base_datetime + delta).time()

def _split_tz(isotimestr):
    if isotimestr.find('+') != -1:
        timestr = isotimestr[0:isotimestr.find('+')]
        tzstr = isotimestr[isotimestr.find('+'):]
    elif isotimestr.find('-') != -1:
        timestr = isotimestr[0:isotimestr.find('-')]
        tzstr = isotimestr[isotimestr.find('-'):]
    elif isotimestr.endswith('Z'):
        timestr = isotimestr[:-1]
        tzstr = 'Z'
    else:
        timestr = isotimestr
        tzstr = None
    return (timestr, tzstr)

_resolution_map = {
    TimeResolution.Hours: _parse_hour,
    TimeResolution.Minutes: _parse_minute_time,
    TimeResolution.Seconds: _parse_second_time
}
