"""
plugins.unlinked_fast_movers

MOPS Alert Rule that returns Tracklets which
    1. Are unlinked
    2. Have velocity > 0.5 deg/day
if the current simulation mjd is > start of OC + 19


This is an example of a fairly complicated rule that does not access
DerivedObject instaces at all but rather builds it own queries to the database.
It also keeps track of its state in a specialized table: 'alerted_tracklets'.
"""

from base import Rule

import MOPS.Lib as Lib
from MOPS.DerivedObject import DerivedObject
from MOPS.Tracklet import Tracklet
from MOPS.Constants import FIELD_STATUS_LINKDONE, TRACKLET_STATUS_UNATTRIBUTED, DERIVEDOBJECT_STATUS_NEW, MOPS_EFF_NONSYNTHETIC




# Constants
MIN_DELTA_MJD = 19                      # How many days into the OC should we be


def objFactory(tracklet):
    """
    Given a tracklet, create a dummy DerivedObject instance with null orbit and
    just that Tracklet.
    """
    STABLE_PASS_YES = 'Y'
    obj = DerivedObject(-tracklet._id, 'dummy', DERIVEDOBJECT_STATUS_NEW, STABLE_PASS_YES, None)
    obj.tracklets = [tracklet, ]
    return(obj)



class UnlinkedFastMovers(Rule):
    """
    Return all new or newly modified DerivedObject instances.
    """
    def __init__(self, instance, includeSyntheticObjects=False, channel='mops',
                 minArcLength=None):
        self.tracklets = []
        return(super(UnlinkedFastMovers, self).__init__(instance,
            includeSyntheticObjects, channel, minArcLength))
    
    def isFastMover(self, tracklet):
        """
        Return True iftracklet has velocity > 0.5 deg/day

        @param obj: DerivedObject instance
        """
        return(tracklet.vTot > 0.5)

    
    def evaluate(self):
        """
        Return a list of DerivedObject instances that satisfy the rule.

        Processing is a bit more complicated that usual: first determine the
        current simulation MJD. Then determine the MJD of the start of the
        current simulation OC (observation cycle) (mjd0).

        If the current MJD < mjd0 + 19 then return None
        Otherwise, fetch all unattributed Tracklets belonging to OC.

        For each Tracklet which has velocity > 0.5 deg/day, create a dummy
        DerivedObject instance with null orbit and just that Tracklet.

        Return the list of these dummy DerivedObject instances.
        """
        # We assume here that the current night is the latest night.
        mjd = self._getLatestNight()
        if(not mjd):
            return([])
        # <-- end if
        
        ocNum = Lib.mjd2ocnum(mjd)
        mjd0 = Lib.ocnum2mjd(ocNum)
        if(mjd < mjd0 + MIN_DELTA_MJD):
            return([])
        # <-- end if

        self.tracklets = [t for t in self._getUnattributedTracklets(mjd0, mjd) \
                          if self.isFastMover(t)]
        return([objFactory(t) for t in self.tracklets])


    def cleanup(self):
        """
        Perform any clenup after the alerts have been successfully sent on the
        objects returned by self.evaluate()

        Here we update the alerted_tracklets table.
        """
        dbh = self._instance.get_dbh()
        cursor = dbh.cursor()
        
        sql = 'insert into alerted_tracklets (tracklet_id) values (%s)'
        for t in self.tracklets:
            n = cursor.execute(sql, (t._id, ))
        return(dbh.commit())


    def _getLatestNight(self):
        """
        Query the database for the highest mjd for which we have Tracklets.
        """
        dbh = self._instance.get_dbh()
        cursor = dbh.cursor()
        
        sql = 'select max(epoch_mjd) from `fields` where status = "%s"' \
              %(FIELD_STATUS_LINKDONE)

        n = cursor.execute(sql)
        if(not n):
            # No night with tracklets exists.
            return
        # <-- end if
        return(cursor.fetchone()[0])


    def _getUnattributedTracklets(self, mjdMin, mjdMax):
        """
        Retrieve all unattributed tracklets for fields whose mjd is between
        mjdMin and mjdMax.

        The calling code has to make sure that mjdMin <= mjdMax

        Use a specialized table to keep track of which tracklets we already
        alerted on so that we do not send duplicate alerts.

        This is a generator, by the way.
        """
        dbh = self._instance.get_dbh()
        cursor = dbh.cursor()
        
        sql = '''\
select distinct(t.tracklet_id), 
       t.v_ra, 
       t.v_dec, 
       t.v_tot, 
       t.v_ra_sigma, 
       t.v_dec_sigma, 
       t.acc_ra, 
       t.acc_dec, 
       t.acc_ra_sigma,
       t.acc_dec_sigma, 
       t.ext_epoch, 
       t.ext_ra, 
       t.ext_dec, 
       t.ext_mag, 
       t.ext_ra_sigma, 
       t.ext_dec_sigma, 
       t.ext_mag_sigma, 
       t.probability, 
       t.status, 
       t.ssm_id
from tracklets t, 
     `fields` f 
where not exists (select ta.tracklet_id from alerted_tracklets ta 
                  where ta.tracklet_id = t.tracklet_id) and
      t.status = "%s" and
      t.field_id=f.field_id and
      f.epoch_mjd between %f and %f
''' \
        %(TRACKLET_STATUS_UNATTRIBUTED, mjdMin, mjdMax)

        # Do we want synthetic objects at all?
        if(not self._includeSyntheticObjects):
            sql += ' and t.classification="%s"' %(MOPS_EFF_NONSYNTHETIC)

        # Execute the SQL.
        n = cursor.execute(sql)
        if(not n):
            # No unattributed tracklet found!
            raise(StopIteration())
        # <-- end if

        row = cursor.fetchone()
        while(row):
            row = list(row)
            t = Tracklet(*row)          # ugh, too chummy with Tracklet.__init__() ordering
            t.fetchDetections(dbh)
            yield(t)

            # Next line.
            row = cursor.fetchone()
        # <-- end while
        raise(StopIteration())
# <-- end class

