# -*- coding: utf-8 -*-
from decimal import Decimal, setcontext, ExtendedContext

from geolucidate.links.google import google_maps_link
from geolucidate.links.tools import MapLink
from geolucidate.parser import parser_re

setcontext(ExtendedContext)


def _cleanup(parts):
    """
    Normalize up the parts matched by :obj:`parser.parser_re` to
    degrees, minutes, and seconds.

    >>> _cleanup({'latdir': 'south', 'longdir': 'west',
    ...          'latdeg':'60','latmin':'30',
    ...          'longdeg':'50','longmin':'40'})
    ['S', '60', '30', '00', 'W', '50', '40', '00']

    >>> _cleanup({'latdir': 'south', 'longdir': 'west',
    ...          'latdeg':'60','latmin':'30', 'latdecsec':'.50',
    ...          'longdeg':'50','longmin':'40','longdecsec':'.90'})
    ['S', '60', '30.50', '00', 'W', '50', '40.90', '00']

    """

    latdir = (parts['latdir'] or parts['latdir2']).upper()[0]
    longdir = (parts['longdir'] or parts['longdir2']).upper()[0]

    latdeg = parts.get('latdeg')
    longdeg = parts.get('longdeg')

    latmin = parts.get('latmin', '00') or '00'
    longmin = parts.get('longmin', '00') or '00'

    latdecsec = parts.get('latdecsec', '')
    longdecsec = parts.get('longdecsec', '')

    if (latdecsec and longdecsec):
        latmin += latdecsec
        longmin += longdecsec
        latsec = '00'
        longsec = '00'
    else:
        latsec = parts.get('latsec', '') or '00'
        longsec = parts.get('longsec', '') or '00'

    return [latdir, latdeg, latmin, latsec, longdir, longdeg, longmin, longsec]


def _convert(latdir, latdeg, latmin, latsec,
             longdir, longdeg, longmin, longsec):
    """
    Convert normalized degrees, minutes, and seconds to decimal degrees.
    Quantize the converted value based on the input precision and
    return a 2-tuple of strings.

    >>> _convert('S','50','30','30','W','50','30','30')
    ('-50.508333', '-50.508333')

    >>> _convert('N','50','27','55','W','127','27','65')
    ('50.459167', '-127.460833')

    """
    if (latsec != '00' or longsec != '00'):
        precision = Decimal('0.000001')
    elif (latmin != '00' or longmin != '00'):
        precision = Decimal('0.001')
    else:
        precision = Decimal('1')

    latitude = Decimal(latdeg)
    latmin = Decimal(latmin)
    latsec = Decimal(latsec)

    longitude = Decimal(longdeg)
    longmin = Decimal(longmin)
    longsec = Decimal(longsec)

    if latsec > 59 or longsec > 59:
        # Assume that 'seconds' greater than 59 are actually a decimal
        # fraction of minutes
        latitude += (latmin +
                     (latsec / Decimal('100'))) / Decimal('60')
        longitude += (longmin +
                      (longsec / Decimal('100'))) / Decimal('60')
    else:
        latitude += (latmin +
                     (latsec / Decimal('60'))) / Decimal('60')
        longitude += (longmin +
                      (longsec / Decimal('60'))) / Decimal('60')

    if latdir == 'S':
        latitude *= Decimal('-1')

    if longdir == 'W':
        longitude *= Decimal('-1')

    lat_str = str(latitude.quantize(precision))
    long_str = str(longitude.quantize(precision))

    return (lat_str, long_str)


def replace(string, sub_function=google_maps_link()):
    """
    Replace detected coordinates with a map link, using the given substitution
    function.

    The substitution function will be passed a :class:`~.MapLink` instance, and
    should return a string which will be substituted by :func:`re.sub` in place
    of the detected coordinates.

    >>> replace("58147N/07720W")
    '<a href="http://maps.google.com/maps?q=58.235278%2C-77.333333+%2858147N%2F07720W%29&ll=58.235278%2C-77.333333&t=h" title="58147N/07720W (58.235278, -77.333333)">58147N/07720W</a>'

    >>> replace("5814N/07720W", google_maps_link('satellite'))
    '<a href="http://maps.google.com/maps?q=58.233%2C-77.333+%285814N%2F07720W%29&ll=58.233%2C-77.333&t=k" title="5814N/07720W (58.233, -77.333)">5814N/07720W</a>'

    >>> from geolucidate.links.bing import bing_maps_link
    >>> replace("58N/077W", bing_maps_link('map'))
    '<a href="http://bing.com/maps/default.aspx?style=r&cp=58~-77&sp=Point.58_-77_58N%2F077W&v=2" title="58N/077W (58, -77)">58N/077W</a>'

    """

    def do_replace(match):
        original_string = match.group()
        (latitude, longitude) = _convert(*_cleanup(match.groupdict()))
        return sub_function(MapLink(original_string, latitude, longitude))

    return parser_re.sub(do_replace, string)


def get_replacements(string, sub_function=google_maps_link()):
    """
    Return a dict whose keys are instances of :class:`re.Match` and
    whose values are the corresponding replacements.  Use
    :func:`get_replacements` when the replacement cannot be performed
    through ordinary string substitution by :func:`re.sub`, as in
    :func:`replace`.


    >>> get_replacements("4630 NORTH 5705 WEST 58147N/07720W")
    ... #doctest: +ELLIPSIS
    {<re.Match object...>: '<a href="..." title="...">4630 NORTH 5705 WEST</a>', <re.Match object...>: '<a href="..." title="...">58147N/07720W</a>'}

    >>> test_string = "4630 NORTH 5705 WEST 58147N/07720W"
    >>> replacements = get_replacements(test_string)
    >>> offset = 0
    >>> out = bytearray(test_string, encoding="ascii", errors="replace")
    >>> for (match, link) in replacements.items():
    ...     start = match.start() + offset
    ...     end = match.end() + offset
    ...     out[start:end] = bytearray(link, encoding="ascii", errors="replace")
    ...     offset += (len(link) - len(match.group()))
    >>> out.decode(encoding="ascii") == replace(test_string)
    True
    """

    substitutions = {}
    matches = parser_re.finditer(string)

    for match in matches:
        (latitude, longitude) = _convert(*_cleanup(match.groupdict()))
        substitutions[match] = sub_function(MapLink(match.group(),
                                                    latitude, longitude))

    return substitutions

[(8, 'decimal.setcontext', 'setcontext', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (73, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (74, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (75, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (77, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (78, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (79, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (106, 'geolucidate.links.google.google_maps_link', 'google_maps_link', False, 'from geolucidate.links.google import google_maps_link\n'), (132, 'geolucidate.parser.parser_re.sub', 'parser_re.sub', False, 'from geolucidate.parser import parser_re\n'), (135, 'geolucidate.links.google.google_maps_link', 'google_maps_link', False, 'from geolucidate.links.google import google_maps_link\n'), (162, 'geolucidate.parser.parser_re.finditer', 'parser_re.finditer', False, 'from geolucidate.parser import parser_re\n'), (67, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (95, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (98, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (69, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (71, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (85, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (87, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (90, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (92, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (130, 'geolucidate.links.tools.MapLink', 'MapLink', False, 'from geolucidate.links.tools import MapLink\n'), (85, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (87, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (90, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n'), (92, 'decimal.Decimal', 'Decimal', False, 'from decimal import Decimal, setcontext, ExtendedContext\n')]