#!/usr/local/bin/python
# encoding: utf-8
"""
*perform a conesearch on a **single** sherlock crossmatch catalogue table*

:Author:
    David Young

:Date Created:
    December 16, 2016

:noindex:
"""
################# GLOBAL IMPORTS ####################
import sys
import os
os.environ['TERM'] = 'vt100'
from fundamentals import tools
from astrocalc.coords import unit_conversion
from HMpTy.mysql import conesearch as hmptyConesearch
import copy


class catalogue_conesearch():
    """
    *The worker class for the conesearch module*

    **Key Arguments:**
        - ``dbConn`` -- mysql database connection to the catalogues database
        - ``log`` -- logger
        - ``ra`` -- ra of transient location (sexegesimal or decimal degrees, J2000, single location or list of locations)
        - ``dec`` -- dec of transient location (sexegesimal or decimal degrees, J2000, single location or list of locations)
        - ``tableName`` -- the name of the database table to perform the conesearch on
        - ``radius`` -- radius of the conesearch to perform (arcsec)
        - ``colMaps`` -- maps of the important column names for each table/view in the crossmatch-catalogues database
        - ``nearestOnly`` -- return only the nearest object. Default *False*
        - ``physicalSearch`` -- is this a physical search, so only return matches with distance information. Default *False*
        - ``upperMagnitudeLimit`` -- the upper magnitude limit if a magnitude cut is requird with the conesearch. Default *False*
        - ``lowerMagnitudeLimit`` -- the lower magnitude limit if a magnitude cut is requird with the conesearch. Default *False*
        - ``magnitudeLimitFilter`` -- the filter to use for the magnitude limit if requird. Default *False*, ("_u"|"_g"|"_r"|"_i"|"_z"|"_y"|"U"|"B"|"V"|"R"|"I"|"Z"|"J"|"H"|"K"|"G")

    **Usage:**

        To setup your logger, settings and database connections, please use the ``fundamentals`` package (`see tutorial here <http://fundamentals.readthedocs.io/en/latest/#tutorial>`_). 

        .. todo::

            - update the package tutorial if needed

        The following examples assume you've connected to the various databases and generated the catalogue column maps in the following menner:

        .. code-block:: python 

            # SETUP ALL DATABASE CONNECTIONS
            from sherlock import database
            db = database(
                log=log,
                settings=settings
            )
            dbConns, dbVersions = db.connect()
            transientsDbConn = dbConns["transients"]
            cataloguesDbConn = dbConns["catalogues"]
            pmDbConn = dbConns["marshall"]

            # GET THE COLUMN MAPS FROM THE CATALOGUE DATABASE
            from sherlock.commonutils import get_crossmatch_catalogues_column_map
            colMaps = get_crossmatch_catalogues_column_map(
                log=log,
                dbConn=cataloguesDbConn
            )

        To perform a single location conesearch on a catalogue in the database, for example the milliquas AGN catalogue:

        .. code-block:: python 

            from sherlock import catalogue_conesearch
            cs = catalogue_conesearch(
                log=log,
                ra="23:01:07.99",
                dec="-01:58:04.5",
                radiusArcsec=60.,
                colMaps=colMaps,
                tableName="tcs_view_agn_milliquas_v4_5",
                dbConn=cataloguesDbConn,
                nearestOnly=False,
                physicalSearch=False
            )
            # catalogueMatches ARE ORDERED BY ANGULAR SEPARATION
            indices, catalogueMatches = cs.search()

            print catalogueMatches

        The output of this search is:

        .. code-block:: text 

            [{'R': 20.1, 'cmSepArcsec': 0.28015184686564643, 'ra': 345.2832267, 'catalogue_object_subtype': u'QR', 'z': 0.777, 'dec': -1.9679629, 'catalogue_object_id': u'PKS 2258-022'}]        

        Note ``catalogue_conesearch`` can accept coordinates in sexegesimal or decimal degrees (J200). It can also accept lists of corrdinates:

        .. code-block:: python 

            from sherlock import catalogue_conesearch
            cs = catalogue_conesearch(
                log=log,
                ra=["23:01:07.99", 45.36722, 13.875250],
                dec=["-01:58:04.5", 30.45671, -25.26721],
                radiusArcsec=60.,
                colMaps=colMaps,
                tableName="tcs_view_agn_milliquas_v4_5",
                dbConn=cataloguesDbConn,
                nearestOnly=False,
                physicalSearch=False
            )

        When passing a list of transient coordinates the returned ``indices`` value becomes important as this list identifies which transient is matched with which crossmatch results (and possibly multiple crossmatch results)

        .. code-block:: python

            indices, catalogueMatches = cs.search()
            for i, c in zip(indices, catalogueMatches):
                print i, c

        The output of this search is:

        .. code-block:: text

            0 {'R': 20.1, 'cmSepArcsec': 0.28015184686564643, 'ra': 345.2832267, 'catalogue_object_subtype': u'QR', 'z': 0.777, 'dec': -1.9679629, 'catalogue_object_id': u'PKS 2258-022'}
            2 {'R': 19.2, 'cmSepArcsec': 0.81509715903447644, 'ra': 13.875, 'catalogue_object_subtype': u'Q', 'z': 2.7, 'dec': -25.2672223, 'catalogue_object_id': u'Q 0053-2532'}

    .. todo ::

        - update key arguments values and definitions with defaults
        - update return values and definitions
        - update usage examples and text
        - update docstring text
        - check sublime snippet exists
        - clip any useful text to docs mindmap
        - regenerate the docs and check redendering of this docstring
    """
    # Initialisation

    def __init__(
            self,
            log,
            ra,
            dec,
            tableName,
            radiusArcsec,
            colMaps,
            dbConn=False,
            nearestOnly=False,
            physicalSearch=False,
            upperMagnitudeLimit=False,
            lowerMagnitudeLimit=False,
            magnitudeLimitFilter=False
    ):
        self.log = log
        log.debug("instansiating a new 'conesearcher' object")
        self.dbConn = dbConn
        self.radius = radiusArcsec
        self.tableName = tableName
        self.nearestOnly = nearestOnly
        self.colMaps = colMaps
        self.physicalSearch = physicalSearch
        self.upperMagnitudeLimit = upperMagnitudeLimit
        self.lowerMagnitudeLimit = lowerMagnitudeLimit
        self.magnitudeLimitFilter = magnitudeLimitFilter
        # xt-self-arg-tmpx

        # CONVERT RA AND DEC TO DEGREES
        if not isinstance(ra, list):
            ra = [ra]
            dec = [dec]

        self.ra = []
        self.dec = []

        converter = unit_conversion(
            log=self.log
        )

        try:
            float(ra[0])
            convert = False
        except:
            convert = True

        if convert == True:

            for r, d in zip(ra, dec):
                self.ra.append(converter.ra_sexegesimal_to_decimal(
                    ra=r
                ))
                self.dec.append(converter.dec_sexegesimal_to_decimal(
                    dec=d
                ))
        else:
            self.ra = ra
            self.dec = dec

        return None

    def search(self):
        """
        *trigger the conesearch*

        **Return:**
            - ``matchIndies`` -- the indicies of the input transient sources (syncs with ``uniqueMatchDicts``)
            - ``uniqueMatchDicts`` -- the crossmatch results

        **Usage:**

            See class docstring for usage examples

        .. todo ::

            - update key arguments values and definitions with defaults
            - update return values and definitions
            - update usage examples and text
            - update docstring text
            - check sublime snippet exists
            - clip any useful text to docs mindmap
            - regenerate the docs and check redendering of this docstring
        """
        self.log.debug('starting the ``search`` method')

        # ACCOUNT FOR TYPE OF SEARCH
        sqlWhere = False
        magnitudeLimitFilter = self.magnitudeLimitFilter
        disCols = ["zColName",
                   "distanceColName", "semiMajorColName"]
        sqlWhere = ""

        if self.physicalSearch == True:
            for d in disCols:
                colName = self.colMaps[self.tableName][d]
                if colName:
                    sqlWhere += " or `%(colName)s` is not null" % locals()
                if len(sqlWhere):
                    sqlWhere = " and (" + sqlWhere[4:] + ")"

        if self.upperMagnitudeLimit != False and self.upperMagnitudeLimit and not self.lowerMagnitudeLimit:
            mag = self.upperMagnitudeLimit
            sqlWhere += " and `%(magnitudeLimitFilter)s` > %(mag)s" % locals()
        if self.lowerMagnitudeLimit != False and self.lowerMagnitudeLimit and not self.upperMagnitudeLimit:
            mag = self.lowerMagnitudeLimit
            sqlWhere += " and `%(magnitudeLimitFilter)s` < %(mag)s" % locals()
        # THE GENERAL (INBETWEEN) CASE
        if self.lowerMagnitudeLimit != False and self.lowerMagnitudeLimit and self.upperMagnitudeLimit != False and self.upperMagnitudeLimit:
            upperMagnitudeLimit = self.upperMagnitudeLimit
            lowerMagnitudeLimit = self.lowerMagnitudeLimit
            sqlWhere += " and ((`%(magnitudeLimitFilter)s` > %(upperMagnitudeLimit)s and `%(magnitudeLimitFilter)s` < %(lowerMagnitudeLimit)s) or `%(magnitudeLimitFilter)s` is null)" % locals()

        if sqlWhere and " and" == sqlWhere[0:4]:
            sqlWhere = sqlWhere[5:]

        # THE COLUMN MAP LIFTED FROM ``tcs_helper_catalogue_tables_info` TABLE
        # IN CATALOGUE DATABASE (COLUMN NAMES ENDDING WITH 'ColName')
        columns = {}
        for k, v in self.colMaps[self.tableName].iteritems():
            name = k.replace("ColName", "")
            if "colname" in k.lower() and v:
                columns[k] = "`%(v)s` as `%(name)s`" % locals()
        columns = ", ".join(columns.values())

        cs = hmptyConesearch(
            log=self.log,
            dbConn=self.dbConn,
            tableName=self.tableName,
            columns=columns,
            ra=self.ra,
            dec=self.dec,
            radiusArcsec=self.radius,
            separations=True,
            distinct=False,
            sqlWhere=sqlWhere,
            closest=self.nearestOnly,
            raCol="ra",
            decCol="dec"
        )
        matchIndies, matches = cs.search()

        # MATCH ARE NOT NECESSARILY UNIQUE IF MANY TRANSIENT MATCH ONE SOURCE
        uniqueMatchDicts = []
        uniqueMatchDicts[:] = [copy.copy(d) for d in matches.list]

        self.log.debug('completed the ``search`` method')
        return matchIndies, uniqueMatchDicts

    # xt-class-method
