#!/usr/bin/env python2.7
# encoding: utf-8

import os
import sys

os.environ["USB_DEVFS_PATH"] = "/dev/bus/usb/temper"

import socket
import temper
import threading
import time
import Queue
import smtplib
import ow
import logging
from collections import deque, namedtuple
from email.mime.text import MIMEText
from email.Utils import formatdate

logging.basicConfig(format='%(asctime)s:%(name)s:%(levelname)s:%(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

READINGINTERVAL = 10

UNIX_SOCKET = "/var/run/collectd-unixsock"

HOSTNAME = "hugin.stusta.mhn.de"


FLOOR_LIMIT = 25
CEILING_LIMIT = 30
CEILING_LIMIT_EMR = 42
FLOOR_LIMIT_EMR = 38
MAX_OUTDOOR_DIFF = 15

MAILINTERVAL = 3600

SPAM = False
#SPAM = True

class TempReader(threading.Thread):
    def __init__(self, export_queue, mail_queue):
        threading.Thread.__init__(self)
        self.export_queue = export_queue
        self.mail_queue = mail_queue
        self.last_mail = 0
        self.iteration = 0

    def queue_mail(self, floor, ceiling, outdoor):
        logger.debug("write mail ...")

        body = u'''DON'T PANIC!

... aber die Temperatur im Serverraum beträgt:
Boden: {:.2f} ℃
Decke: {:.2f} ℃
Outdoor: {:.2f} ℃

Der Temperator
-- 
                          oooo$$$$$$$$$$$$oooo
                      oo$$$$$$$$$$$$$$$$$$$$$$$$o
                   oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o         o$   $$ o$
   o $ oo        o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o       $$ $$ $$o$
oo $ $ "$      o$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$o       $$$o$$o$
"$$$$$$o$     o$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$o    $$$$$$$$
  $$$$$$$    $$$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$$$$$$$$$$$$$$
  $$$$$$$$$$$$$$$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$$$$$$  """$$$
   "$$$""""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$
    $$$   o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$o
   o$$"   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$       $$$o
   $$$    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" "$$$$$$ooooo$$$$o
  o$$$oooo$$$$$  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$   o$$$$$$$$$$$$$$$$$
  $$$$$$$$"$$$$   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     $$$$""""""""
 """"       $$$$    "$$$$$$$$$$$$$$$$$$$$$$$$$$$$"      o$$$
            "$$$o     """$$$$$$$$$$$$$$$$$$"$$"         $$$
              $$$o          "$$""$$$$$$""""           o$$$
               $$$$o                                o$$$"
                "$$$$o      o$$$$$$o"$$$$o        o$$$$
                  "$$$$$oo     ""$$$$o$$$$$o   o$$$$""
                     ""$$$$$oooo  "$$$o$$$$$$$$$"""
                        ""$$$$$$$oo $$$$$$$$$$
                                """"$$$$$$$$$$$
                                    $$$$$$$$$$$$
                                     $$$$$$$$$$"
                                      "$$$""""
'''

        msg = MIMEText(body.format(floor, ceiling, outdoor),  _charset="UTF-8")
        src = "Temperator <root@hugin.stusta.mhn.de>"
        if (ceiling>CEILING_LIMIT_EMR)or(floor>FLOOR_LIMIT_EMR):
            msg['Subject'] = u"TEMPERATURNOTFALL Serverraum!"
        else:
            msg['Subject'] = u"Temperaturalarm Serverraum"
        msg['From'] = src
        if SPAM:
            dst = ["markus.hefele@stusta.net"]
        else:
            dst = ["admins@stustanet.de"]
        msg['To'] = ", ".join(dst)
        msg['Date'] = formatdate(localtime=True)

        #self.mail_queue.put((src, dst, msg.as_string()))

    def run(self):
        while True:
            logger.debug("reading loop")
            start = time.time()
            try:
                th = temper.TemperHandler()

                # TODO naming things is hard!
                for i, temp_device in th._devices.iteritems():

                    temp_sensor = temperature_sensors.get(i)

                    if temp_sensor:
                        try:
                            temp_c = temp_device.get_temperature(calibration=temp_sensor.calibration)
                            record = temp_sensor.update(temp_c)
                            self.export_queue.put(record)
                        except Exception as e:
                            logger.debug(e)
                            #logger.exception(e)

            except Exception as e:
                logger.exception(e)

            try:
                ow.init( 'localhost:4304' )
                for a, owid in iter(temperature_sensors):
                    if a == 'serial':
                        temp_sensor = temperature_sensors.get((a, owid))

                        try:
                            temp_c = float(ow.Sensor( '/uncached/'+ owid ).temperature)
                            if temp_c != float('85'):
                                record = temp_sensor.update(temp_c)
                                self.export_queue.put(record)
                        except Exception as e:
                            logger.debug(e)

                # memory leak?
                #ow.finish()

            except Exception as e:
                logger.exception(e)

            self.iteration += 1

            now = time.time()

            floor, floor_last, floor_avg = temperature_sensors_usb_by_name['floor'].get_current_and_average()
            ceiling, ceiling_last, ceiling_avg = temperature_sensors_usb_by_name['ceiling'].get_current_and_average()
            outdoor, outdoor_last, outdoor_avg = temperature_sensors_usb_by_name['outdoor'].get_current_and_average()

            falling = False

            if (floor is not None and floor_avg is not None and floor <= floor_avg and
                ceiling is not None and ceiling_avg is not None and ceiling <= ceiling_avg and
                    outdoor is not None and outdoor_avg is not None and outdoor <= outdoor_avg):

                falling = True

            # error case: some sensor is not working!
            error = False

            if floor is None or (floor_last is not None and floor_last + MAILINTERVAL < now):
                error = True
                floor = 9000
            if ceiling is None or (ceiling_last is not None and ceiling_last + MAILINTERVAL < now):
                error = True
                ceiling = 9001
            if outdoor is None or (outdoor_last is not None and outdoor_last + MAILINTERVAL < now):
                # for the difference ...
                error = True
                outdoor = 0


            if (self.iteration > 1 and self.last_mail + MAILINTERVAL < now and
                    (error or (not falling and
                    ((floor > FLOOR_LIMIT and floor - MAX_OUTDOOR_DIFF > outdoor) or
                    (ceiling > CEILING_LIMIT and ceiling - MAX_OUTDOOR_DIFF > outdoor))))):

                self.last_mail = now
                self.queue_mail(floor, ceiling, outdoor)

            if (self.iteration > 1 and self.last_mail + 300 < now and (ceiling > CEILING_LIMIT_EMR or floor > FLOOR_LIMIT_EMR)):
                self.last_mail = now
                self.queue_mail(floor, ceiling, outdoor)
                logger.debug("--------------EMERGENCY------------")


            sys.stdout.flush()

            wait = start + READINGINTERVAL - time.time()
            if wait > 0:
                time.sleep(wait)


class Mail0r(threading.Thread):
    def __init__(self, mail_queue):
        threading.Thread.__init__(self)
        self.mail_queue = mail_queue

    def run(self):
        while True:
            try:
                sender, recipient, msg = self.mail_queue.get(True, 3600)
            except Queue.Empty as e:
                logger.debug(e)
                continue

            try:
                #s = smtplib.SMTP("localhost")
                #we don't have a local mta anymore
                s = smtplib.SMTP("mail.stusta.mhn.de")
                s.sendmail(sender, recipient, msg)
                s.quit()
            except Exception as e:
                logger.debug("----ERROR---MAIL----")
                logger.debug(e)


CollectdRecord = namedtuple("CollectdRecord", ["hostname", "path", "interval", "epoch", "value"])


class Exp0rt0r(threading.Thread):
    def __init__(self, export_queue):
        threading.Thread.__init__(self)

        self.socket = None

        # CollectdRecords
        self.export_queue = export_queue

    def run(self):
        while True:
            record = None
            try:
                record = self.export_queue.get(True, 3600)
            except Queue.Empty as e:
                continue

            if not self.socket:
                s = socket.socket(socket.AF_UNIX)
                try:
                    s.connect(UNIX_SOCKET)
                    s.setblocking(False)
                    self.socket = s
                except Exception as e:
                    logger.debug(e)
                    time.sleep(1)
                    continue

            data = "PUTVAL \"%s/%s\" interval=%i %i:%s\n" % \
                    (record.hostname, record.path, int(record.interval), int(record.epoch), record.value)

            try:
                logger.debug("socket write: %s", data.strip())
                self.socket.send(data)

                recv = []
                while True:
                    try:
                        recv = self.socket.recv(1<<12)
                    except socket.error as e:
                        if e.errno == 11:  #EAGAIN
                            break
                        raise e

                recv = ''.join(recv)

                logger.debug("socket read: %s", recv.strip())

            except Exception as e:
                logger.debug(e)
                try:
                    self.socket.close()
                except:
                    pass
                self.socket = None


class TempSensor(object):
    def __init__(self, name):
        self.name = name

        self._path = "tail-temperature/temperature-%s" % (name, )
        self._interval = READINGINTERVAL

        self.last_updated = None
        self.temperature = None

        self.history = deque()

        self._lock = threading.RLock()


    def update(self, temperature):
        with self._lock:
            now = time.time()

            while self.history and self.history[0][1] < (now - MAILINTERVAL):
                self.history.popleft()

            if self.last_updated is not None and self.temperature is not None:
                self.history.append((self.temperature, self.last_updated))

            self.temperature = temperature
            self.last_updated = now

            logger.debug("update %s", self)

            return CollectdRecord(HOSTNAME, self._path, int(self._interval),
                    int(self.last_updated), "%f" % (self.temperature,))

    def get_current_and_average(self):
        with self._lock:
            avg = None
            if self.history:
                avg = float(sum(i[0] for i in self.history))/len(self.history)

            return self.temperature, self.last_updated, avg

    def __str__(self):
        with self._lock:
            return "%s temperature: %s, last_updated:%s" % \
                    (self.name, self.temperature, self.last_updated)


class TempSensorUSB(TempSensor):
    def __init__(self, name, calibration, bus, device):
        super(TempSensorUSB, self).__init__(name)

        self.calibration = calibration
        self.bus = bus
        self.device = device

    @property
    def usb_id(self):
        return self.bus, self.device

    def __str__(self):
        with self._lock:
            return "%s (bus:%s, device:%s, calibration:%i) temperature: %s, last_updated:%s" % \
                    (self.name, self.bus, self.device, self.calibration, self.temperature, self.last_updated)

class TempSensorSerial(TempSensor):
    def __init__(self, name, owid):
        super(TempSensorSerial, self).__init__(name)
        self.owid = owid

    def __str__(self):
        with self._lock:
            return "%s (owid:%s) temperature: %s, last_updated:%s" % \
                    (self.name, self.owid, self.temperature, self.last_updated)

temperature_sensors = {}
#TODO:Type in key, only one dict?
temperature_sensors_usb_by_name = {}
temperature_sensors_serial_by_name = {}

for name, calibration, bus, device in [('floor', 500, '001', '001'), ('ceiling', 300, '002', '001'), ('outdoor', 330, '003', '001')]:
    temperature_sensors[(bus, device)] = TempSensorUSB(name, calibration, bus, device)

    temperature_sensors_usb_by_name[name] = temperature_sensors[(bus, device)]

for name, owid in [('floorserial', '10.C238A5010800'), ('ceilserial', '10.0C33A5010800')]:
    temperature_sensors[('serial', owid)] = TempSensorSerial(name, owid)

    temperature_sensors_serial_by_name[name] = temperature_sensors[('serial', owid)]

if __name__ == "__main__":
    mail_queue = Queue.Queue()
    export_queue = Queue.Queue()

    readerfred = TempReader(export_queue, mail_queue)
    readerfred.setDaemon(True)
    readerfred.start()

    exp0rtfred = Exp0rt0r(export_queue)
    exp0rtfred.setDaemon(True)
    exp0rtfred.start()

    mail0r = Mail0r(mail_queue)
    mail0r.setDaemon(True)
    mail0r.start()

    while True:
        time.sleep(3600)