from __future__ import division
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import *
from past.utils import old_div
from builtins import object
import logging

import emission.storage.timeseries.abstract_timeseries as esta
import emission.storage.decorations.place_queries as esdp
import emission.storage.decorations.analysis_timeseries_queries as esda
import emission.storage.pipeline_queries as epq

import emission.core.wrapper.transition as ecwt
import emission.core.wrapper.location as ecwl
import emission.core.wrapper.rawtrip as ecwrt
import emission.core.wrapper.rawplace as ecwrp
import emission.core.wrapper.entry as ecwe
import emission.core.wrapper.untrackedtime as ecwut

import emission.analysis.intake.segmentation.restart_checking as eaisr

import emission.core.common as ecc

class TripSegmentationMethod(object):
    def segment_into_trips(self, timeseries, time_query):
        """
        Examines the timeseries database for a specific range and returns the
        segmentation points. Note that the input is the entire timeseries and
        the time range. This allows algorithms to use whatever combination of
        data that they want from the sensor streams in order to determine the
        segmentation points.
        
        Returns array of location point tuples corresponding to the start and end of
        trips in this time range. The first trip returned starts with the first
        location point in the time range.
    
        i.e. [(start1, end1), (start2, end2)...]. Points between end1 and
        start2 are assumed to be within place 2 and are generally ignored.

        If there are no segments, returns an empty array.
        If the method is not yet ready to segment the trips (e.g. maybe it is
            waiting until the end of the day in order to use a clustering algorithm, returns None)
        """
        pass
    
def segment_current_trips(user_id):
    ts = esta.TimeSeries.get_time_series(user_id)
    time_query = epq.get_time_range_for_segmentation(user_id)

    import emission.analysis.intake.segmentation.trip_segmentation_methods.dwell_segmentation_time_filter as dstf
    import emission.analysis.intake.segmentation.trip_segmentation_methods.dwell_segmentation_dist_filter as dsdf
    dstfsm = dstf.DwellSegmentationTimeFilter(time_threshold = 5 * 60, # 5 mins
                                              point_threshold = 9,
                                              distance_threshold = 100) # 100 m

    dsdfsm = dsdf.DwellSegmentationDistFilter(time_threshold = 10 * 60, # 10 mins
                                              point_threshold = 9,
                                              distance_threshold = 50) # 50 m

    filter_methods = {"time": dstfsm, "distance": dsdfsm}
    filter_method_names = {"time": "DwellSegmentationTimeFilter", "distance": "DwellSegmentationDistFilter"}
    # We need to use the appropriate filter based on the incoming data
    # So let's read in the location points for the specified query
    loc_df = ts.get_data_df("background/filtered_location", time_query)
    if len(loc_df) == 0:
        # no new segments, no need to keep looking at these again
        logging.debug("len(loc_df) == 0, early return")
        epq.mark_segmentation_done(user_id, None)
        return

    out_of_order_points = loc_df[loc_df.ts.diff() < 0]
    if len(out_of_order_points) > 0:
        logging.info("Found out of order points!")
        logging.info("%s" % out_of_order_points)
        # drop from the table
        loc_df = loc_df.drop(out_of_order_points.index.tolist())
        # delete from the database. Should be generally discouraged, so we
        # are kindof putting it in here secretively
        import emission.core.get_database as edb

        out_of_order_id_list = out_of_order_points["_id"].tolist()
        logging.debug("out_of_order_id_list = %s" % out_of_order_id_list)
        edb.get_timeseries_db().delete_many({"_id": {"$in": out_of_order_id_list}})

    filters_in_df = loc_df["filter"].dropna().unique()
    logging.debug("Filters in the dataframe = %s" % filters_in_df)
    if len(filters_in_df) == 1:
        # Common case - let's make it easy
        
        segmentation_points = filter_methods[filters_in_df[0]].segment_into_trips(ts,
            time_query)
    else:
        segmentation_points = get_combined_segmentation_points(ts, loc_df, time_query,
                                                               filters_in_df,
                                                               filter_methods)
    # Create and store trips and places based on the segmentation points
    if segmentation_points is None:
        epq.mark_segmentation_failed(user_id)
    elif len(segmentation_points) == 0:
        # no new segments, no need to keep looking at these again
        logging.debug("len(segmentation_points) == 0, early return")
        epq.mark_segmentation_done(user_id, None)
    else:
        try:
            create_places_and_trips(user_id, segmentation_points, filter_method_names[filters_in_df[0]])
            epq.mark_segmentation_done(user_id, get_last_ts_processed(filter_methods))
        except:
            logging.exception("Trip generation failed for user %s" % user_id)
            epq.mark_segmentation_failed(user_id)
            
def get_combined_segmentation_points(ts, loc_df, time_query, filters_in_df, filter_methods):
    """
    We can have mixed filters in a particular time range for multiple reasons.
    a) user switches phones from one platform to another
    b) user signs in simultaneously to phones on both platforms
    case (b) is not a supported case, and can happen even with the same platform - i.e. a user can sign in to two devices of the same platform