# Copyright (c) 2019 Esben Rossel
 # All rights reserved.
 #
 # Author: Esben Rossel <esbenrossel@gmail.com>
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
 # 1. Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright
 #    notice, this list of conditions and the following disclaimer in the
 #    documentation and/or other materials provided with the distribution.
 #
 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 # ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 # SUCH DAMAGE.


#python imports
from tkinter import messagebox
import serial          
import numpy as np

#application imports

import config
import threading
import tkinter as tk
import time

# The firmware expects 12 bytes from the computer and will not do anything until 12 bytes have been received.
# The format is:
# byte[1-2]: The characters E and R. Defines where the firmware should start reading in its circular input-buffer.
# byte[3-6]: The 4 bytes constituting the 32-bit int holding the SH-period
# byte[7-10]: The 4 bytes constituting the 32-bit int holding the ICG-period
# byte[11]: Continuous flag: 0 equals one acquisition, 1 equals continuous mode
# byte[12]: The number of integrations to average
def rxtx(panel, SerQueue, progress_var):
    
    if (config.AVGn[0] == 0):
        threadser = threading.Thread(target=rxtxoncethread, args=(panel, SerQueue, progress_var), daemon=True)
    elif (config.AVGn[0] == 1):
        threadser = threading.Thread(target=rxtxcontthread, args=(panel, progress_var), daemon=True)
    threadser.start()
    

def rxtxoncethread(panel, SerQueue, progress_var):
    #open serial port
    try:
        ser = serial.Serial(config.port, config.baudrate)
        #share the serial handle with the stop-thread so cancel_read may be called
        SerQueue.put(ser)
        #disable controls
        panelsleep(panel)
        config.stopsignal = 0

        #start the progressbar
        panel.progress.config(mode="determinate")
        threadprogress = threading.Thread(target=progressthread, args=(progress_var,), daemon=True)
        threadprogress.start()

        #wait to clear the input and output buffers, if they're not empty data is corrupted
        while (ser.in_waiting > 0):
           ser.reset_input_buffer()
           ser.reset_output_buffer()
           time.sleep(0.01)


        #Transmit key 'ER'    
        config.txfull[0] = 69
        config.txfull[1] = 82
        #split 32-bit integers to be sent into 8-bit data
        config.txfull[2] = (config.SHperiod >> 24) & 0xff
        config.txfull[3] = (config.SHperiod >> 16) & 0xff
        config.txfull[4] = (config.SHperiod >> 8) & 0xff
        config.txfull[5] = config.SHperiod & 0xff
        config.txfull[6] = (config.ICGperiod >> 24) & 0xff
        config.txfull[7] = (config.ICGperiod >> 16) & 0xff
        config.txfull[8] = (config.ICGperiod >> 8) & 0xff
        config.txfull[9] = config.ICGperiod & 0xff
        #averages to perfom
        config.txfull[10] = config.AVGn[0] 
        config.txfull[11] = config.AVGn[1]

        #transmit everything at once (the USB-firmware does not work if all bytes are not transmitted in one go)
        ser.write(config.txfull)
            
        #wait for the firmware to return data
        config.rxData8 = ser.read(7388)

        #close serial port
        ser.close()

        #enable all buttons
        panelwakeup(panel)

        if (config.stopsignal == 0):
            #combine received bytes into 16-bit data
            for rxi in range(3694):
                config.rxData16[rxi] = (config.rxData8[2*rxi+1] << 8) + config.rxData8[2*rxi]

            #plot the new data
            panel.bupdate.invoke()
            #hold values for saving data to file as the SHperiod and ICGperiod may be updated after acquisition
            config.SHsent = config.SHperiod
            config.ICGsent = config.ICGperiod

        SerQueue.queue.clear()

    except serial.SerialException:
        messagebox.showerror("By the great otter!","There's a problem with the specified serial connection.")

def rxtxcontthread(panel, progress_var):
    #open serial port
    try:
        ser = serial.Serial(config.port, config.baudrate)
        #disable controls
        panelsleep(panel)
        config.stopsignal = 0

        #restart the progressbar for each acquisition
        panel.progress.config(mode="indeterminate")
        panel.progress.start(100)
#        threadprogress = threading.Thread(target=progressthread, args=(progress_var), daemon=True)
#        threadprogress.start()

        #wait to clear the input and output buffers, if they're not empty data is corrupted
        while (ser.in_waiting > 0):
           ser.reset_input_buffer()
           ser.reset_output_buffer()
           time.sleep(0.1)

        #Transmit key 'ER'    
        config.txfull[0] = 69
        config.txfull[1] = 82
        #split 32-bit integers to be sent into 8-bit data
        config.txfull[2] = (config.SHperiod >> 24) & 0xff
        config.txfull[3] = (config.SHperiod >> 16) & 0xff
        config.txfull[4] = (config.SHperiod >> 8) & 0xff
        config.txfull[5] = config.SHperiod & 0xff
        config.txfull[6] = (config.ICGperiod >> 24) & 0xff
        config.txfull[7] = (config.ICGperiod >> 16) & 0xff
        config.txfull[8] = (config.ICGperiod >> 8) & 0xff
        config.txfull[9] = config.ICGperiod & 0xff
        #averages to perfom
        config.txfull[10] = config.AVGn[0] 
        config.txfull[11] = config.AVGn[1]

        #transmit everything at once (the USB-firmware does not work if all bytes are not transmittet in one go)
        ser.write(config.txfull)

        #loop to acquire and plot data continuously
        while (config.stopsignal == 0):
            #wait for the firmware to return data
            config.rxData8 = ser.read(7388)
            
            if (config.stopsignal == 0):
                #combine received bytes into 16-bit data
                for rxi in range(3694):
                    config.rxData16[rxi] = (config.rxData8[2*rxi+1] << 8) + config.rxData8[2*rxi]

                #plot the new data
                panel.bupdate.invoke()
                #hold values for saving data to file
                config.SHsent = config.SHperiod
                config.ICGsent = config.ICGperiod

        #resend settings with continuous transmission disabled to avoid flooding of the serial port
        config.txfull[10] = 0
        ser.write(config.txfull)

        #wait until data is received to close the serial port
        while (ser.out_waiting > 0):
            time.sleep(0.1)

        #close serial port
        ser.close()
        panelwakeup(panel)
        panel.progress.stop()

    except serial.SerialException:
        messagebox.showerror("By the great otter!","There's a problem with the specified serial connection.")

def progressthread(progress_var):
    progress_var.set(0)
    for i in range (1,11):
        progress_var.set(i)
        #wait 1/10th of the time the acquisition requires before adding to progress bar
        time.sleep(config.ICGperiod*config.AVGn[1]/config.MCLK/10)

def rxtxcancel(SerQueue):
    config.stopsignal = 1
    #Are we stopping one very long measurement, or the continuous real-time view?
    if (config.AVGn[0]==0):
        ser = SerQueue.get()
        ser.cancel_read()


def panelsleep(panel):
    panel.bstop.config(state=tk.NORMAL)
    panel.bopen.config(state=tk.DISABLED)
    panel.bsave.config(state=tk.DISABLED)
    panel.bcollect.config(state=tk.DISABLED)
    panel.AVGscale.config(state=tk.DISABLED)
    panel.rcontinuous.config(state=tk.DISABLED)
    panel.roneshot.config(state=tk.DISABLED)
    panel.eICG.config(state=tk.DISABLED)
    panel.eSH.config(state=tk.DISABLED)
    panel.edevice.config(state=tk.DISABLED)
    panel.cinvert.config(state=tk.DISABLED)
    panel.cbalance.config(state=tk.DISABLED)

def panelwakeup(panel):
    panel.bstop.config(state=tk.DISABLED)
    panel.bopen.config(state=tk.NORMAL)
    panel.bsave.config(state=tk.NORMAL)
    panel.bcollect.config(state=tk.NORMAL)
    panel.AVGscale.config(state=tk.NORMAL)
    panel.rcontinuous.config(state=tk.NORMAL)
    panel.roneshot.config(state=tk.NORMAL)
    panel.eICG.config(state=tk.NORMAL)
    panel.eSH.config(state=tk.NORMAL)
    panel.edevice.config(state=tk.NORMAL)
    panel.cinvert.config(state=tk.NORMAL)
    if (config.datainvert == 1):
        panel.cbalance.config(state=tk.NORMAL)

