class Observer(threading.Thread):

    # observer states
    RUNNING = "RUNNING"
    PAUSED = "PAUSED"
    
    def __init__(self, url, profile, store, assessor, notifications, update_interval = 180, name = "Unnamed Observer"):
        super(Observer, self).__init__()
        self._interval = update_interval
        self._connector = Connector(url, profile)
        self._store = store
        self._assessor = assessor
        self._notifications = notifications
        self._name = name
        self._quit = False
        self._time_mark = datetime.datetime.now() - datetime.timedelta(days = 1)
        self._state = Observer.RUNNING
        self.name = name
    
    def serialize(self):
        d = dict()
        d["name"] = self._name
        d["url"] = self._connector.url
        d["interval"] = self._interval
        d["profile"] = self._connector.profile_name
        d["store"] = self._store.path is not None

        if self._assessor is not None:
            d["criteria"] = [criterion.serialize() for criterion in self._assessor.criteria]
        # if self._notifications is not None:
        #     d["notifications"] = [notification.serialize() for notification in self._notifications]
        return d

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, value):
        self._state = value

    @property
    def notifications(self):
        return self._notifications

    def quit(self):
        """
        Make the Thread quit
        """
        self._quit = True

    def _process_ads(self, ads):
        if len(ads) == 0: return
        hits = map(self._assessor.check, ads)
        hit_ads = [ad for ad in compress(ads, hits)]
        new_ads = self._store.add_ads(hit_ads)
        for ad in new_ads:
            try:
                logging.info("Observer '{}' Found Ad: {}".format(self._name, ad["title"]))
            except KeyError:
                logging.info("Observer '{}' Found Ad: {}".format(self._name, ad.key))
            if self._notifications:
                self._notifications.notify_all(ad)
        self._time_mark = sorted(ads, key = lambda ad: ad.datetime)[-1].datetime

    def run(self):
        self._state = Observer.RUNNING
        while True:
            if self._state == Observer.RUNNING:
                logging.info("Observer '{}' polling for new ads since {}".format(self._name, self._time_mark))
                try:
                    ads = self._connector.ads_after(self._time_mark)
                    if self._quit:
                        return   # Quit now if quit() was called while fetching ads

                    self._process_ads(ads)

                except ConnectionError as ex:
                    logging.info("Observer '{}' connection failed with message: {}".format(self._name, ex.args[0]))

            # going to sleep
            next_time = datetime.datetime.now() + datetime.timedelta(seconds=self._interval)
            while datetime.datetime.now() < next_time:
                time.sleep(1)
                if self._quit:
                    return   # Quit now if quit() was called while sleeping
class Observer(threading.Thread):

    # observer states
    RUNNING = "RUNNING"
    PAUSED = "PAUSED"
    
    def __init__(self, url, profile, store, assessor, notifications, update_interval = 180, name = "Unnamed Observer"):
        super(Observer, self).__init__()
        self._interval = update_interval
        self._connector = Connector(url, profile)
        self._store = store
        self._assessor = assessor
        self._notifications = notifications
        self._name = name
        self._quit = False
        self._time_mark = datetime.datetime.now() - datetime.timedelta(days = 1)
        self._state = Observer.RUNNING
        self.name = name
    
    def serialize(self):
        d = dict()
        d["name"] = self._name
        d["url"] = self._connector.url
        d["interval"] = self._interval
        d["profile"] = self._connector.profile_name
        d["store"] = self._store.path is not None

        if self._assessor is not None:
            d["criteria"] = [criterion.serialize() for criterion in self._assessor.criteria]
        # if self._notifications is not None:
        #     d["notifications"] = [notification.serialize() for notification in self._notifications]
        return d

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, value):
        self._state = value

    @property
    def notifications(self):
        return self._notifications

    def quit(self):
        """
        Make the Thread quit
        """
        self._quit = True

    def _process_ads(self, ads):
        if len(ads) == 0: return
        hits = map(self._assessor.check, ads)
        hit_ads = [ad for ad in compress(ads, hits)]
        new_ads = self._store.add_ads(hit_ads)
        new_ads = [dict(y) for y in set([frozenset(x.items()) for x in new_ads])] # Clear duplicates!
        for ad in new_ads:
            try:
                logging.info("Observer '{}' Found Ad: {}".format(self._name, ad["title"]))
            except KeyError:
                logging.info("Observer '{}' Found Ad: {}".format(self._name, ad.key))
            if self._notifications:
                self._notifications.notify_all(ad)
        self._time_mark = sorted(ads, key = lambda ad: ad.datetime)[-1].datetime

    def run(self):
        self._state = Observer.RUNNING
        while True:
            if self._state == Observer.RUNNING:
                logging.info("Observer '{}' polling for new ads since {}".format(self._name, self._time_mark))
                try:
                    ads = self._connector.ads_after(self._time_mark)
                    if self._quit:
                        return   # Quit now if quit() was called while fetching ads

                    self._process_ads(ads)

                except ConnectionError as ex:
                    logging.info("Observer '{}' connection failed with message: {}".format(self._name, ex.args[0]))

            # going to sleep
            next_time = datetime.datetime.now() + datetime.timedelta(seconds=self._interval)
            while datetime.datetime.now() < next_time:
                time.sleep(1)
                if self._quit:
                    return   # Quit now if quit() was called while sleeping