
# ###################################################################
# Import packages
# ###################################################################

import time
import struct # for binary representation
import socket # for hostname identification
import string # for ascii selection
from datetime import datetime, timezone
from twisted.python import log
from martas.core import methods as mm
import subprocess


try:
    import board
    import adafruit_mmc56x3
except:
    log.msg("   mmc5603 i2c requires the following packages: board, adafruit-blinka and adafruit-circuitpython-mmc56x3")

    ## I2C MMC5603 protocol
## -----------------------

class MMC5603I2CProtocol(object):
    """
    Protocol to read MMC5603 data attached to I2C pins of i.e. an raspberry
    This protocol makes use of the Adafruit library.
    pip3 install adafruit-circuitpython-mmc56x3

    """

    def __init__(self, client, sensordict, confdict):
        self.client = client
        self.sensordict = sensordict
        self.confdict = confdict
        self.count = 0  ## counter for sending header information
        self.sensor = sensordict.get('sensorid')
        self.hostname = socket.gethostname()
        self.printable = set(string.printable)
        self.datalst = []
        self.datacnt = 0
        self.metacnt = 10
        self.counter = {}
        self.initi2c = True

        # extract data from configuration dictionary
        debugtest = confdict.get('debug')

        # QOS
        self.qos=int(confdict.get('mqttqos',0))
        if not self.qos in [0,1,2]:
            self.qos = 0
        log.msg("  -> setting QOS:", self.qos)


        # switch on debug mode
        self.debug = False
        if debugtest in ['true','True','TRUE']:
            log.msg('DEBUG - {}: Debug mode activated.'.format(self.sensordict.get('protocol')))
            self.debug = True    # prints many test messages
        else:
            log.msg('  -> Debug mode = {}'.format(debugtest))

        # connect to i2c
        self.conn = self.init_sensor()


    def datetime2array(self,t):
        return [t.year,t.month,t.day,t.hour,t.minute,t.second,t.microsecond]

    def init_sensor(self):
        #log.msg("connecting to I2C ...")
        i2c = board.I2C()  # uses board.SCL and board.SDA
        # i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
        sensor = adafruit_mmc56x3.MMC5603(i2c)
        time.sleep(0.5)
        sensor.reset()  # in Hz, from 1-255 or 1000
        time.sleep(0.5)
        sensor.set_reset()  # in Hz, from 1-255 or 1000
        #sensor.data_rate = 10  # in Hz, from 1-255 or 1000
        #sensor.continuous_mode = True
        return sensor


    def processMMC5603Data(self, sensorid, meta, data):
        """Convert raw ADC counts into SI units as per datasheets"""
        currenttime = datetime.now(timezone.utc).replace(tzinfo=None)
        outdate = datetime.strftime(currenttime, "%Y-%m-%d")
        filename = outdate

        datearray = self.datetime2array(currenttime)

        packcode = '6hL'

        values = []
        multiplier = []
        for dat in data:
            try:
                values.append(float(dat))
                datearray.append(int(float(dat)*10000))
                packcode = packcode + 'q'
                multiplier.append(10000)
            except:
                log.msg('{} protocol: Error while appending data to file (non-float?): {}'.format(self.sensordict.get('protocol'),dat) )

        try:
            data_bin = struct.pack('<'+packcode,*datearray) #little endian
        except:
            log.msg('{} protocol: Error while packing binary data'.format(self.sensordict.get('protocol')))
            pass

        key = '['+str(meta.get('SensorKeys')).replace("'","").strip()+']'
        ele = '['+str(meta.get('SensorElements')).replace("'","").strip()+']'
        unit = '['+str(meta.get('SensorUnits')).replace("'","").strip()+']'
        multplier = str(multiplier).replace(" ","")
        # Correct some common old problem
        unit = unit.replace('deg C', 'degC')

        header = "# MagPyBin %s %s %s %s %s %s %d" % (sensorid, key, ele, unit, multplier, packcode, struct.calcsize(packcode))

        if not self.confdict.get('bufferdirectory','') == '':
            mm.data_to_file(self.confdict.get('bufferdirectory'), sensorid, filename, data_bin, header)

        return ','.join(list(map(str,datearray))), header

    def get_mmc5603_data(self,sensor, debug=False):
        mag_x, mag_y, mag_z = sensor.magnetic
        temp = sensor.temperature
        if debug:
             print(f"X:{mag_x:10.3f}, Y:{mag_y:10.3f}, Z:{mag_z:10.3f} uT, T:{temp:10.3f} degC")
        data = [float(mag_x)*1000., float(mag_y)*1000., float(mag_z)*1000., temp]
        return data

    def calibrate_mmc5603_data(self, data, offsets=None, scales=None, debug=False):
        if not offsets:
            offsets=[0,0,0,0]
        if not scales:
            scales=[1,1,1,1]
        for i, el in enumerate(data):
            data[i] = el+offsets[i]
            data[i] = data[i]*scales[i]
        return data

    def define_mmc5603_meta(self):
        meta = {}
        meta['SensorKeys'] = 'x,y,z,t1'
        meta['SensorElements'] = 'X,Y,Z,Ts'
        meta['SensorUnits'] = 'nT,nT,nT,C'
        return meta

    def sendRequest(self):

        if not self.conn:
            self.conn = self.init_sensor()
            # or put everything in a try except and reconnect on except
        # connect to i2c
        data = self.get_mmc5603_data(self.conn, debug=self.debug)
        data = self.calibrate_mmc5603_data(data, offsets=[0,0,550000,9])
        meta = self.define_mmc5603_meta()
        self.publish_data(data,meta)

    def publish_data(self, data, meta):

        if len(data) > 0:
            topic = self.confdict.get('station') + '/' + self.sensor
            pdata, head = self.processMMC5603Data(self.sensor, meta, data)

            senddata = False
            try:
                coll = int(self.sensordict.get('stack'))
            except:
                coll = 0

            if coll > 1:
                self.metacnt = 1 # send meta data with every block
                if self.datacnt < coll:
                    self.datalst.append(pdata)
                    self.datacnt += 1
                else:
                    senddata = True
                    pdata = ';'.join(self.datalst)
                    self.datalst = []
                    self.datacnt = 0
            else:
                senddata = True

            if senddata:
                self.client.publish(topic+"/data", pdata, qos=self.qos)
                # If multiple sensors are connected, self.count needs to be a dict (with topic)
                # Initialize counter for each topic
                if self.counter.get(self.sensor,'') == '':
                    self.counter[self.sensor] = 0
                cnt = self.counter.get(self.sensor)

                if cnt == 0:
                    ## 'Add' is a string containing dict info like:
                    ## SensorID:ENV05_2_0001,StationID:wic, PierID:xxx,SensorGroup:environment,...
                    add = "SensorID:{},StationID:{},DataPier:{},SensorModule:{},SensorGroup:{},SensorDescription:{},DataTimeProtocol:{}".format( self.sensordict.get('sensorid',''),self.confdict.get('station',''),self.sensordict.get('pierid',''),self.sensordict.get('protocol',''),self.sensordict.get('sensorgroup',''),self.sensordict.get('sensordesc',''),self.sensordict.get('ptime','') )
                    self.client.publish(topic+"/dict", add, qos=self.qos)
                    self.client.publish(topic+"/meta", head, qos=self.qos)

                cnt += 1
                #self.count += 1
                if cnt >= self.metacnt:
                    cnt = 0

                # update counter in dict
                self.counter[self.sensor] = cnt
