#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# vim: expandtab sw=4 ts=4 sts=4:
#
# Beehive-Monitoring with SMS Alerts and Data-Upload to
# Webservice.
#
# Author: Joerg Lehmann, nbit Informatik GmbH
#
"""Beehive Monitoring"""

from __future__ import print_function
import os
import sys
import serial
import time
import yaml
import random
import string
import glob
import re
import json
import datetime

# Root Path
APP_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))

# Read Configuration from YAML-File
with open("%s/bin/beielimon-config.yaml" % (APP_ROOT), 'r') as stream:
    try:
        config_data = yaml.load(stream)
    except yaml.YAMLError as exc:
        print(exc)

# Constants
INVALID_VALUE = -999

# Wegen der Dummy-Waage moechten wir den letzten Messwert holen koennen
def GetLastStoredValue(scale_uuid):
    files = glob.glob("%s/data/weight-%s-????????.log" % (APP_ROOT,scale_uuid))
    if len(files) > 0:
        with open(sorted(files)[-1], "r") as f:
            for line in f: pass
            result =  line


        # Beispiel: 2017-11-07 08:23,0
        m = re.match("(\d{4})-(\d{2})-(\d{2}) (\d\d:\d\d),(\d+)", result)
        if m:
            return int("%s" % (m.group(5)))
        else:
            return INVALID_VALUE

    else:
       return INVALID_VALUE



class Scale(object):
    def __init__(self, scale_config):
        self.last_values = []
        self.scale_config = scale_config
        self.accu = INVALID_VALUE
        self.hum = INVALID_VALUE
        self.temp = INVALID_VALUE

    def __del__(self):
        pass

    def LogValue(self,weigh_in_gram,swarm_alarm):
        cur_time = time.localtime()
        timestamp = time.strftime("%Y-%m-%d %H:%M", cur_time)
        year = time.strftime("%Y", cur_time)
        month = time.strftime("%m", cur_time)
        day = time.strftime("%d", cur_time)
        if swarm_alarm:
            prefix = 'swarmalarm'
        else:
            prefix = 'weight'
        datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
        with open(datafilename, 'a') as file:
            file.write('%s,%d\n' % (timestamp,weigh_in_gram))

        if self.accu != INVALID_VALUE and not(swarm_alarm):
            prefix = 'accu'
            datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
            with open(datafilename, 'a') as file:
                file.write('%s,%.1f\n' % (timestamp,self.accu / 100.0))

        if self.hum != INVALID_VALUE and not(swarm_alarm):
            prefix = 'humidity'
            datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
            with open(datafilename, 'a') as file:
                file.write('%s,%d\n' % (timestamp,self.hum / 10))

        if self.temp != INVALID_VALUE and not(swarm_alarm):
            prefix = 'temp'
            datafilename = "%s/data/%s-%s-%s%s%s.log" % (APP_ROOT,prefix,self.scale_config['scale_uuid'],year,month,day)
            with open(datafilename, 'a') as file:
                file.write('%s,%d\n' % (timestamp,self.temp / 10))



    def AppendReading(self,weigh_in_gram):
        self.last_values.append(weigh_in_gram)
        if len(self.last_values) > config_data['number_of_samples']:
            self.last_values = self.last_values[1:]
        # Wir loggen den Wert noch
        self.LogValue(weigh_in_gram,False)

    def Read(self):
        pass

    def CalibrateToZero(self):
        pass

    def SwarmAlarm(self):
        return (self.GetWeighLoss() > config_data['swarm_alarm_threshold_gram'])

    def ResetValues(self):
        self.last_values = []

    def GetLastValue(self):
        return (self.last_values or [0])[-1]

    def GetWeighLoss(self):
        last_value = self.GetLastValue()
        max_value = max(self.last_values or [0])
        return (max_value - last_value) 

    def GetScaleConfig(self):
        return self.scale_config


class ScaleUSB_PCE(Scale):
    def __init__(self,serial_int, scale_config):
        Scale.__init__(self, scale_config)
        self.ser = serial_int

    def Read(self):
        res = INVALID_VALUE
        self.ser.write('Sx\r\n') 
        weight_string = self.ser.readline()
        if len(weight_string) == 16:
            if (weight_string[12:13] == 'g'):
                try:
                    res = int(weight_string[2:11])
                except: 
                    print('Problem with Read Scale: weight string is not an integer: %s' % (weight_string[2:11]))
            else:
                print('Problem with Read Scale: weigth should be in grams,  but is [%s]' % (weight_string[12:13]))
        else:
            print('Problem with Read Scale: value should be 16 digits: but is %d' % (len(weight_string)))
        if res != INVALID_VALUE:
            self.AppendReading(res)

class ScaleBT(Scale):
    def __init__(self,scale_config):
        Scale.__init__(self, scale_config)

    def Read(self):
        res = INVALID_VALUE
        
        filename = "/home/beieli/bt-readings/%s.json" % (self.scale_config['mac'])
        try:
            data = json.load(open(filename))
        except:
            return

        # Wenn die Daten aelter als 15 Minuten sind loeschen wir das File
        delta_in_seconds = (datetime.datetime.now() - datetime.datetime.strptime(data['datetime'],'%d.%m.%Y %H:%M')).total_seconds()
        print(delta_in_seconds)
        if ( delta_in_seconds > 15*60):
            print('DEBUG [%s,%s]' % (datetime.datetime.now(),data['datetime']))
            print('Readings are older than 15 minutes, removing them [%s]' % (self.scale_config['mac']))
            os.remove(filename)
        else:  
            res = ((data['w1'] - self.scale_config['offset1']) / self.scale_config['ratio1']) + ((data['w2'] - self.scale_config['offset2']) / self.scale_config['ratio2'])
            if res < 0:
                print('DEBUG: Read Bluetooth Scale; Value less than 0, set it to 0: %d' % (res))
                res = 0
     
            self.accu = data['accu']
            self.hum = data['hum']
            self.temp = data['temp']

            print('DEBUG: Read Bluetooth Scale: %d' % (res))

        if res != INVALID_VALUE:
            self.AppendReading(res)


class ScaleDummy(Scale):
    def __init__(self,scale_config):
        Scale.__init__(self, scale_config)
        # Wir versuchen, den letzten Wert wieder zu holen
        last_value = GetLastStoredValue(self.scale_config['scale_uuid'])
        if last_value != INVALID_VALUE:
            self.last_values = [ last_value ]
        else:
            self.last_values = [ random.randint(500,1000) ]


    def Read(self):
        # Gewichts- Zu/Abnahme ist Random, manchmal gibt es einen
        # Zufaelligen Schwarmalarm
        delta = random.randint(-2, 4)
        if (random.randint(0,1000) == 500):
            delta = random.randint(-1000, - 501)

        last_value = self.GetLastValue()
        # Wir duerfen nicht negativ werden...
        if (last_value + delta < 0):
           delta = random.randint(0,4)


        print('DEBUG: Read Dummy Scale: %d' % (last_value + delta))
        self.AppendReading(last_value + delta)


def send_sms(phonenumbers , text):
    data = {}
    data['phonenumbers'] = phonenumbers
    data['text'] = text
    # wird von smsmon verarbeitet...
    print("Send SMS to %s, Text: %s" % (phonenumbers, text))
    randomstr = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(7))
    with open("%s/send_sms_queue/%s.yml" % (APP_ROOT,randomstr), "w") as outfile:
        yaml.dump(data, outfile, default_flow_style=False)

def main():
    scales = []
    for scale_config  in config_data['scales']:
        if scale_config['interface_type'] == 'usb_pce':
             ser = serial.Serial(port='/dev/' + scale_config['interface_name'], 
                                 baudrate=9600,
                                 bytesize=serial.EIGHTBITS,
                                 parity=serial.PARITY_EVEN,
                                 timeout=20)
             scale=ScaleUSB_PCE(ser, scale_config)
             scales.append(scale)
        elif scale_config['interface_type'] == 'btscale':
             scale=ScaleBT(scale_config)
             scales.append(scale)
        elif scale_config['interface_type'] == 'dummy':
             scale=ScaleDummy(scale_config)
             scales.append(scale)
                        
    # Main Loop
    while True:
        for scale in scales:
            scale.Read()
            if scale.SwarmAlarm():
                date_time = time.strftime("%d.%m.%Y %H:%M")
                last_value = scale.GetLastValue()
                weigh_loss = scale.GetWeighLoss()
                # Wir loggen den Wert noch
                scale.LogValue(last_value,True)
                sms_message = '*** Schwarmalarm ***\nDatum/Zeit: %s\nWaage: %s\nLetztes Gewicht [g]: %d\nGewichtsverlust [g]: %d' % (date_time,scale.GetScaleConfig()['alias'],last_value,weigh_loss)
                send_sms(scale.GetScaleConfig()['sms_alert_phonenumbers'],sms_message)
                scale.ResetValues()

        sys.stdout.flush()
        time.sleep(config_data['read_scale_interval_sec']) 

if __name__ == "__main__":
    main()
