# Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
This module contains convenience functions for getting a coordinate object
for a named object by querying SESAME and getting the first returned result.
Note that this is intended to be a convenience, and is very simple. If you
need precise coordinates for an object you should find the appropriate
reference for that measurement and input the coordinates manually.
"""



# Standard library
import os
import re
import sys
import http.client
import urllib.request, urllib.parse, urllib.error
import urllib.request, urllib.error, urllib.parse
from urllib.parse import urlparse
import socket

# Third party
import numpy as np

# Astropy
from ..config import ConfigurationItem
from .builtin_systems import ICRSCoordinates
from .. import units as u

__all__ = ["get_icrs_coordinates"]

SESAME_URL = ConfigurationItem("sesame_url", 
                        ["http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/",
                         "http://vizier.cfa.harvard.edu/viz-bin/nph-sesame/"],
                        "The URL to Sesame's web-queryable database.",
                        cfgtype='string_list')

SESAME_DATABASE = ConfigurationItem("sesame_database", ['all', 'simbad', 'ned', 
                                    'vizier'],
                                    "This specifies the default database that "
                                    "SESAME will query when using the name "
                                    "resolve mechanism in the coordinates "
                                    "subpackage. Default is to search all "
                                    "databases, but this can be 'all', "
                                    "'simbad', 'ned', or 'vizier'.")
                                    
NAME_RESOLVE_TIMEOUT = ConfigurationItem('name_resolve_timeout', 5,
                                         "This is the maximum time to wait "
                                         "for a response from a name resolve "
                                         "query to SESAME in seconds.")

class NameResolveError(Exception):
    pass

def _parse_response(resp_data):
    """
    Given a string response from SESAME, parse out the coordinates by looking
    for a line starting with a J, meaning ICRS J2000 coordinates.
    
    Parameters
    ----------
    resp_data : str
        The string HTTP response from SESAME.
        
    Returns
    -------
    ra : str
        The string Right Ascension parsed from the HTTP response.
    dec : str
        The string Declination parsed from the HTTP response.
    """
    
    pattr = re.compile(r"%J\s*([0-9\.]+)\s*([\+\-\.0-9]+)")
    matched = pattr.search(resp_data.decode('utf-8'))
    
    if matched == None:
        return None, None
    else:
        ra,dec = matched.groups()
        return ra,dec

def get_icrs_coordinates(name):
    """ 
    Retrieve an ICRSCoordinates object by using an online name resolving
    service to retrieve coordinates for the specified name. By default,
    this will search all available databases until a match is found. If
    you would like to specify the database, use the configuration item
    `name_resolve.SESAME_DATABASE` . You can also specify a list of servers 
    to use for querying Sesame using the configuration item 
    `name_resolve.SESAME_URL`. This will try each one in order until a valid
    response is returned. By default, this list includes the main Sesame 
    host and a mirror at vizier. A final configuration item, 
    `name_resolve.NAME_RESOLVE_TIMEOUT`, is the number of seconds to wait
    for a response from the server before giving up. By default this is 
    5 seconds.

    Parameters
    ----------
    name : str
        The name of the object to get coordinates for, e.g. M42.

    Returns
    -------
    coord : SphericalCoordinatesBase
        An `ICRSCoordinates` instance for the object name specified.
        
    """
    
    database = SESAME_DATABASE()
    # The web API just takes the first letter of the database name
    db = database.upper()[0]
    
    # Make sure we don't have duplicates in the url list
    urls = []
    domains = []
    for url in SESAME_URL():
        domain = urlparse(url).netloc
        
        # Check for duplicates
        if domain not in domains:
            domains.append(domain)
            
            # Add the query to the end of the url, add to url list
            fmt_url = os.path.join(url, "{db}?{name}")
            fmt_url = fmt_url.format(name=urllib.parse.quote(name), db=db)
            urls.append(fmt_url)
    
    for url in urls:
        try:
            # Retrieve ascii name resolve data from CDS
            resp = urllib.request.urlopen(url, timeout=NAME_RESOLVE_TIMEOUT())
            break
        except urllib.error.URLError as e:
            # This catches a timeout error, see:
            #   http://stackoverflow.com/questions/2712524/handling-urllib2s-timeout-python
            if isinstance(e.reason, socket.timeout):
                # If it was a timeout, try with the next URL
                continue
            else:
                raise NameResolveError("Unable to retrieve coordinates for name "
                                       "'{0}'".format(name))
    
    # All Sesame URL's timed out...
    else:
        raise NameResolveError("All Sesame queries timed out. Unable to "
                               "retrieve coordinates.")

    resp_data = resp.read()

    ra,dec = _parse_response(resp_data)
    
    if ra == None and dec == None:
        if db == "A":
            err = "Unable to find coordinates for name '{0}'".format(name)
        else:
            err = "Unable to find coordinates for name '{0}' in database {1}"\
                  .format(name, database)

        raise NameResolveError(err)

    return ICRSCoordinates(ra, dec, unit=(u.degree, u.degree))