Given a known solar-system object ID (human-readable name, MPC number or MPC packed format) and one or more specific epochs, return the calculated ephemerides 

    **Key Arguments:**
        - ``log`` -- logger
        - ``objectId`` -- human-readable name, MPC number or MPC packed format id of the solar-system object or list of names
        - ``mjd`` -- a single MJD, or a list of up to 10,000 MJDs to generate an ephemeris for
        - ``obscode`` -- the observatory code for the ephemeris generation. Default **500** (geocentric)
        - ``verbose`` -- return extra information with each ephemeris

    **Return:**
        - ``resultList`` -- a list of ordered dictionaries containing the returned ephemerides

    **Usage:**

        To generate a an ephemeris for a single epoch run, using ATLAS Haleakala as your observatory:

        .. code-block:: python 

            from rockfinder import jpl_horizons_ephemeris
            eph = jpl_horizons_ephemeris(
                log=log,
                objectId=1,
                mjd=57916.,
                obscode='T05'
            )

        or to generate an ephemeris for multiple epochs:

        .. code-block:: python 

            from rockfinder import jpl_horizons_ephemeris
            eph = jpl_horizons_ephemeris(
                log=log,
                objectId="ceres",
                mjd=[57916.1,57917.234,57956.34523]
                verbose=True
            )

        Note by passing `verbose=True` the essential ephemeris data is supplimented with some extra data

        It's also possible to pass in an array of object IDs:

        .. code-block:: python 

            from rockfinder import jpl_horizons_ephemeris
            eph = jpl_horizons_ephemeris(
                log=log,
                objectId=[1,5,03547,"Shikoku","K10B11A"],
                mjd=[57916.1,57917.234,57956.34523]
            )

def jpl_horizons_ephemeris(
        log,
        objectId,
        mjd,
        obscode=500,
        verbose=False):
    """Given a known solar-system object ID (human-readable name, MPC number or MPC packed format) and one or more specific epochs, return the calculated ephemerides 

    **Key Arguments:**
        - ``log`` -- logger
        - ``objectId`` -- human-readable name, MPC number or MPC packed format id of the solar-system object or list of names
        - ``mjd`` -- a single MJD, or a list of up to 10,000 MJDs to generate an ephemeris for
        - ``obscode`` -- the observatory code for the ephemeris generation. Default **500** (geocentric)
        - ``verbose`` -- return extra information with each ephemeris

    **Return:**
        - ``resultList`` -- a list of ordered dictionaries containing the returned ephemerides

    **Usage:**

        To generate a an ephemeris for a single epoch run, using ATLAS Haleakala as your observatory:

        .. code-block:: python 

            from rockfinder import jpl_horizons_ephemeris
            eph = jpl_horizons_ephemeris(
                log=log,
                objectId=1,
                mjd=57916.,
                obscode='T05'
            )

        or to generate an ephemeris for multiple epochs:

        .. code-block:: python 

            from rockfinder import jpl_horizons_ephemeris
            eph = jpl_horizons_ephemeris(
                log=log,
                objectId="ceres",
                mjd=[57916.1,57917.234,57956.34523]
                verbose=True
            )

        Note by passing `verbose=True` the essential ephemeris data is supplimented with some extra data

        It's also possible to pass in an array of object IDs:

        .. code-block:: python 

            from rockfinder import jpl_horizons_ephemeris
            eph = jpl_horizons_ephemeris(
                log=log,
                objectId=[1,5,03547,"Shikoku","K10B11A"],
                mjd=[57916.1,57917.234,57956.34523]
            )
    """
    log.debug('starting the ``jpl_horizons_ephemeris`` function')

    # MAKE SURE MJDs ARE IN A LIST
    if not isinstance(mjd, list):
        mjd = [str(mjd)]

    mjd = (" ").join(map(str, mjd))

    if not isinstance(objectId, list):
        objectList = [objectId]
    else:
        objectList = objectId

    keys = ["jd", "solar_presence", "lunar_presence",  "ra_deg", "dec_deg", "ra_arcsec_per_hour", "dec_arcsec_per_hour",  "apparent_mag", "surface_brightness", "heliocentric_distance", "heliocentric_motion", "observer_distance", "observer_motion",
            "sun_obs_target_angle", "apparent_motion_relative_to_sun",  "sun_target_obs_angle", "ra_3sig_error", "dec_3sig_error", "true_anomaly_angle", "phase_angle", "phase_angle_bisector_long", "phase_angle_bisector_lat"]
    if verbose == True:
        order = ["requestId", "objectId", "mjd", "ra_deg", "dec_deg", "ra_3sig_error", "dec_3sig_error", "ra_arcsec_per_hour",  "dec_arcsec_per_hour", "apparent_mag",  "heliocentric_distance", "heliocentric_motion", "observer_distance", "observer_motion", "phase_angle", "true_anomaly_angle", "surface_brightness",
                 "sun_obs_target_angle",  "sun_target_obs_angle", "apparent_motion_relative_to_sun", "phase_angle_bisector_long", "phase_angle_bisector_lat"]
    else:
        order = ["requestId", "objectId", "mjd", "ra_deg", "dec_deg", "ra_3sig_error", "dec_3sig_error", "ra_arcsec_per_hour",
                 "dec_arcsec_per_hour", "apparent_mag",  "heliocentric_distance", "observer_distance", "phase_angle"]

    params = {
        "COMMAND": "",
        "OBJ_DATA": "'NO'",
        "MAKE_EPHEM": "'YES'",
        "TABLE_TYPE": "'OBS'",
        "CENTER": "'%(obscode)s'" % locals(),
        "TLIST": mjd,
        "QUANTITIES": "'1,3,9,19,20,23,24,36,41,43'",
        "REF_SYSTEM": "'J2000'",
        "CAL_FORMAT": "'JD'",
        "ANG_FORMAT": "'DEG'",
        "APPARENT": "'REFRACTED'",
        "TIME_DIGITS": "'FRACSEC'",
        "TIME_ZONE": "'+00:00'",
        "RANGE_UNITS": "'AU'",
        "SUPPRESS_RANGE_RATE": "'NO'",
        "SKIP_DAYLT": "'YES'",
        "EXTRA_PREC": "'YES'",
        "CSV_FORMAT": "'YES'",
        "batch": "1",
    }

    resultList = []
    paramList = []
    for objectId in objectList:

        requestId = objectId
        # FIX THE COMMAND FOR NUMBERED OBJECTS
        try:
            thisId = int(objectId)
            objectId = "%(thisId)s" % locals()
        except Exception as e:
            pass

        theseparams = copy.deepcopy(params)
        theseparams["COMMAND"] = '"' + objectId + '"'

        paramList.append(theseparams)

        # TEST THE URL
        # try:
        #     import requests
        #     response = requests.get(
        #         url="https://ssd.jpl.nasa.gov/horizons_batch.cgi",
        #         params=theseparams,
        #     )
        #     content = response.content
        #     status_code = response.status_code
        #     print response.url
        # except requests.exceptions.RequestException:
        #     print('HTTP Request failed')
        #     sys.exit(0)

    rs = [grequests.get("https://ssd.jpl.nasa.gov/horizons_batch.cgi", params=p)
          for p in paramList]

    def exception_handler(request, exception):
        print "Request failed"
        print exception

    returns = grequests.map(rs, size=1, exception_handler=exception_handler)

    for result, requestId in zip(returns, objectList):
        r = result.content

        match = re.search(
            r'Target body name:\s(.*?)\{',
            r,
            flags=re.S  # re.S
        )

        if not match:
            log.warning(
                "Horizons could not find a match for `%(requestId)s`" % locals())
            try:
                import requests
                response = requests.get(
                    url="https://ssd.jpl.nasa.gov/horizons_batch.cgi",
                    params=theseparams,
                )
                content = response.content
                status_code = response.status_code
                print response.url
            except requests.exceptions.RequestException:
                print('HTTP Request failed')
                sys.exit(0)
            objectDict = {}
            for k in keys:
                v = None
                objectDict[k] = v

            objectDict["objectId"] = requestId + " - NOT FOUND"
            objectDict["requestId"] = requestId
            objectDict["mjd"] = None

            orderDict = collections.OrderedDict({})
            for i in order:
                orderDict[i] = objectDict[i]

            resultList.append(orderDict)
            continue

        horizonsId = match.group(1).replace("(", "").replace(")", "").strip()

        match = re.search(
            r'\$\$SOE\n(.*?)\n\$\$EOE',
            r,
            flags=re.S  # re.S
        )

        keys2 = copy.deepcopy(keys)
        order2 = copy.deepcopy(order)
        if "S-brt," not in r:
            keys2.remove("surface_brightness")
            try:
                order2.remove("surface_brightness")
            except:
                pass

        lines = match.group(1).split("\n")
        for line in lines:

            vals = line.split(",")
            objectDict = {}
            for k, v in zip(keys2, vals):
                v = v.strip().replace("/", "")
                try:
                    v = float(v)
                except:
                    pass
                objectDict[k] = v

            objectDict["mjd"] = objectDict["jd"] - 2400000.5
            objectDict["objectId"] = horizonsId
            objectDict["requestId"] = requestId

            orderDict = collections.OrderedDict({})
            for i in order2:
                orderDict[i] = objectDict[i]

            resultList.append(orderDict)

    log.debug('completed the ``jpl_horizons_ephemeris`` function')
    return resultList