import adafruit_dht
from . import background_task
import logging
import time

try:
    from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError:
    ROOT_LOGGER_NAME = 'root'

logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)


class dht_22(background_task):
    RUN_SLEEP_TIME = 4.0
    MIN_REFRESH_RATE = RUN_SLEEP_TIME
    KEY_TEMPERATURE = 'temperature'
    KEY_HUMIDITY = 'humidity'
    KEY_TIME = 'time'

    def __init__(self, gpio, data_callback=None):
        self.__data_callback__ = data_callback

        self.__temp_monitor__ = gradient_monitor(.5)
        self.__hum_monitor__ = gradient_monitor(5)
        self.__monitor__ = dht_22_monitor(300)
        self.DEBUG = gpio is None
        if self.DEBUG:
            logger.warning("Switching to DEBUG mode due to gpio is None...")

        # Initial the dht device, with data pin connected to:
        if not self.DEBUG:
            self.__dht_device__ = adafruit_dht.DHT22(gpio, use_pulseio=False)
        # Initialise background_task
        background_task.__init__(self)

    def __run__(self):
        data = self.__dht_data_transmission__()
        if data is not None:
            # check data
            if self.__temp_monitor__.process(data[self.KEY_TEMPERATURE], data[self.KEY_TIME]) and self.__hum_monitor__.process(data[self.KEY_HUMIDITY], data[self.KEY_TIME]):
                #
                logger.info('DHT-Communication successful: %s', repr(data))
                self.__monitor__.process(data)
                if self.__data_callback__ is not None:
                    self.__data_callback__(**data)
                if self.__monitor__.status():
                    logger.warning("DHT-Communication failed: Measurement stopped caused by suspicious constant values!")
                    self.close()
            else:
                logger.debug("DHT-Communication failed: Gradient to high. Ignoring data %s!", repr(data))

    def __dht_data_transmission__(self):
        if not self.DEBUG:
            while self.__active__:
                try:
                    # Store the values
                    data = {}
                    data[self.KEY_TEMPERATURE] = self.__dht_device__.temperature
                    data[self.KEY_HUMIDITY] = self.__dht_device__.humidity
                    data[self.KEY_TIME] = int(time.time())
                    if data[self.KEY_TEMPERATURE] is not None and data[self.KEY_HUMIDITY] is not None:
                        return data
                    time.sleep(self.RUN_SLEEP_TIME)
                except RuntimeError as error:
                    # Errors happen fairly often, DHT's are hard to read, just keep going
                    logger.debug('DHT-Communication failed: ' + error.args[0])
                    time.sleep(2.0)
                except Exception as error:
                    self.__dht_device__.exit()
                    raise error
        else:
            return {
                self.KEY_TEMPERATURE: -17.17,
                self.KEY_HUMIDITY: 1.7,
                self.KEY_TIME: int(time.time()),
            }


class gradient_monitor(object):
    def __init__(self, max_gradient):
        self.__max_gradient__ = max_gradient
        #
        self.__last_value__ = None
        self.__last_time__ = None

    def process(self, value, time):
        if self.__last_value__ is not None and self.__last_time__ is not None:
            # Valid last value exists
            gradient = abs((value - self.__last_value__) / (time - self.__last_time__))
            if gradient > self.__max_gradient__:
                return False

        self.__last_value__ = value
        self.__last_time__ = time
        return True


class dht_22_monitor(object):
    def __init__(self, max_const_treshold=250):
        self.__max_const_treshold__ = max_const_treshold
        self.__fail__ = False
        self.__max_const_measurements__ = 0
        self.__init_statevars__()

    def __init_statevars__(self):
        self.__current_const_measurements__ = 0
        self.__last_measurements__ = None

    def process(self, data):
        if self.__last_measurements__ is None:
            self.__last_measurements__ = data
        else:
            data_is_equal = True
            for key in [dht_22.KEY_HUMIDITY, dht_22.KEY_TEMPERATURE]:
                if data[key] != self.__last_measurements__[key]:
                    data_is_equal = False
            if data_is_equal:
                self.__current_const_measurements__ += 1
                if self.__current_const_measurements__ > self.__max_const_measurements__:
                    self.__max_const_measurements__ = self.__current_const_measurements__
            else:
                self.__init_statevars__()
        if self.__max_const_measurements__ > self.__max_const_treshold__:
            self.__fail__ = True

    def status(self):
        if self.__fail__:
            self.__fail__ = False
            self.__max_const_measurements__ = 0
            return True
        return False
