from os import times
import struct
from .interfaces import adc_default, gra_default, sdr_default, sync_default
from . import models
from . import serializers
from multiprocessing import *
import numpy as np
from rest_framework import permissions, viewsets
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import JSONParser,ParseError
from rest_framework import status
from django.conf import settings
import threading
import subprocess
import base64
import time as tm

class NewThread(threading.Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None

    def run(self):
        if self._target != None:
            self._return = self._target(*self._args, **self._kwargs)
        else:
            self._return = ((0x00, 0), 0)

    def join(self, *args):
        threading.Thread.join(self, *args)
        return self._return

class DefaultEngine:
    def __init__(self, instance, lock):
        self.measure = instance
        if(self.measure.info.iadc.enabled):
            if(self.measure.info.iadc.device.proto == 'USB'):
                self.adc_interface = adc_default(port=-1)
            else:
                self.adc_interface = adc_default(port=5003)

        if(self.measure.info.isync.device.proto == 'USB'):
            self.sync_interface = sync_default(port=-1)
        else:
            self.sync_interface = sync_default(port=5001)

        if(self.measure.info.isdr.device.proto == 'USB'):
            self.sdr_interface = sdr_default(port=-1)
        else:
            self.sdr_interface = sdr_default(port=5003)

        if(self.measure.info.igrax.enabled):
            self.grax_interface = gra_default(ip = self.measure.info.igrax.ip, port=5002)
        if(self.measure.info.igray.enabled):
            self.gray_interface = gra_default(ip = self.measure.info.igray.ip, port=5002)
        if(self.measure.info.igraz.enabled):
            self.graz_interface = gra_default(ip = self.measure.info.igraz.ip, port=5002)

        self.engine_thread = threading.Thread(target=self.run)
        self.lock = lock
        self.engine_thread.start()
        self.measure.state.status = 'STARTING'

        self.lock.acquire()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

    def getThread(self):
        return self.engine_thread
    
    def syncUp(self):
        ret = self.sync_interface.upload(str(settings.BASE_DIR) + '\\bin\\Sync.exe',
                                        self.measure.info.isync.file,
                                        self.measure.info.isync.port)
        
        if(ret != 0):
            self.measure.state.grax.status = 'CONFIG ERROR'
            self.measure.state.grax.code = -1
            self.measure.state.status = 'SYNC ERROR'
            self.measure.state.code = -1

            self.lock.acquire()
            self.measure.state.grax.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            return -1

        # Final configuration: set the status to configured for sync and overall.
        self.measure.state.sync.status = 'CONFIGURED'
        self.measure.state.sync.code = 1
        self.measure.state.status = 'SYNC CONFIGURED'
        self.measure.state.code = 1

        self.lock.acquire()
        self.measure.state.sync.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        print('UPDATED')
        return 0

    def syncWait(self):
        ret = self.sync_interface.trig_wait(self.measure.info.isync.port)
        if(ret[0] == 0xFF):
            self.measure.state.sync.status = 'TRIG SET ERROR'
            self.measure.state.sync.code = -4
            self.measure.state.status = 'SYNC ERROR'
            self.measure.state.code = -1

            self.lock.acquire()
            self.measure.state.sync.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            return -1
        
        self.measure.state.sync.status = 'WAIT FOR TRIGGER'
        self.measure.state.sync.code = 2
        self.measure.state.status = 'SYNC WAIT'
        self.measure.state.code = 2

        print('SENDED')
        return 0

    def syncDown(self):
        ret = self.sync_interface.serial_close()
        if(self.measure.info.iadc.enabled):
            self.adc_interface.client_socket.close()
        self.measure.state.sync.status = 'TRIGGERING COMPLETE'
        self.measure.state.sync.code = 0
        return 0
        
    def graUp(self):
        if(self.measure.info.igrax.enabled == True):
            try:
                self.grax_interface.connect()
            except Exception as e:
                self.measure.state.grax.status = 'CONN ERROR'
                self.measure.state.grax.code = -1
                self.measure.state.status = 'GRA X ERROR'
                self.measure.state.code = -2

                self.lock.acquire()
                self.measure.state.grax.save()
                self.measure.state.save()
                self.measure.save()
                self.lock.release()

                return -1
            
            # Reset and power on
            self.grax_interface.reset()
            ret = self.grax_interface.state()
            self.measure.state.grax.code = ret[2]
            self.measure.state.grax.status = f'RETCODE {ret}'

            # Power on
            self.grax_interface.ps_on()
            ret = self.grax_interface.state()
            self.measure.state.grax.code = ret[2]
            self.measure.state.grax.status = f'RETCODE {ret}'

            data = b''
            with open(self.measure.info.igrax.file, 'rb') as file:
                data = file.read()
            i = 0
            nodes = []
            while(i < len(nodes)):
                time, ampl = struct.unpack('<IH', data[i:])
                node = []
                node.append(time)
                node.append(ampl)
                nodes.append(node)
                i += 6
            points = len(nodes)
            self.grax_interface.upload(points, nodes)
            ret = self.grax_interface.state()
            self.measure.state.grax.code = ret[2]
            self.measure.state.grax.status = f'RETCODE {ret}'

            self.grax_interface.ps_off()
            ret = self.grax_interface.state()
            self.measure.state.grax.code = ret[2]
            self.measure.state.grax.status = f'RETCODE {ret}'

        self.measure.state.grax.code = 1
        self.measure.state.grax.status = 'CONFIGURED'

        self.lock.acquire()
        self.measure.state.grax.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        if(self.measure.info.igray.enabled == True):
            try:
                self.gray_interface.connect()
            except Exception as e:
                self.measure.state.gray.status = 'CONN ERROR'
                self.measure.state.gray.code = -1
                self.measure.state.status = 'GRA Y ERROR'
                self.measure.state.code = -2

                self.lock.acquire()
                self.measure.state.gray.save()
                self.measure.state.save()
                self.measure.save()
                self.lock.release()

                return -1
            
            self.gray_interface.reset()
            ret = self.gray_interface.state()
            self.measure.state.gray.code = ret[2]
            self.measure.state.gray.status = f'RETCODE {ret}'

            self.gray_interface.ps_on()
            ret = self.gray_interface.state()
            self.measure.state.gray.code = ret[2]
            self.measure.state.gray.status = f'RETCODE {ret}'

            data = b''
            with open(self.measure.info.igray.file, 'rb') as file:
                data = file.read()
            i = 0
            nodes = []
            while(i < len(nodes)):
                time, ampl = struct.unpack('<IH', data[i:])
                node = []
                node.append(time)
                node.append(ampl)
                nodes.append(node)
                i += 6
            points = len(nodes)
            self.gray_interface.upload(points, nodes)
            ret = self.gray_interface.state()
            self.measure.state.gray.code = ret[2]
            self.measure.state.gray.status = f'RETCODE {ret}'

            self.gray_interface.ps_off()
            ret = self.gray_interface.state()
            self.measure.state.gray.code = ret[2]
            self.measure.state.gray.status = f'RETCODE {ret}'

        self.measure.state.gray.code = 1
        self.measure.state.gray.status = 'CONFIGURED'

        self.lock.acquire()
        self.measure.state.gray.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        if(self.measure.info.igraz.enabled == True):
            try:
                self.graz_interface.connect()
            except Exception as e:
                self.measure.state.graz.status = 'CONN ERROR'
                self.measure.state.graz.code = -1
                self.measure.state.status = 'GRA Z ERROR'
                self.measure.state.code = -2

                self.lock.acquire()
                self.measure.state.graz.save()
                self.measure.state.save()
                self.measure.save()
                self.lock.release()

                return -1
            
            self.graz_interface.reset()
            ret = self.graz_interface.state()
            self.measure.state.graz.code = ret[2]
            self.measure.state.graz.status = f'RETCODE {ret}'

            self.graz_interface.ps_on()
            ret = self.graz_interface.state()
            self.measure.state.graz.code = ret[2]
            self.measure.state.graz.status = f'RETCODE {ret}'

            data = b''
            with open(self.measure.info.igraz.file, 'rb') as file:
                data = file.read()
            i = 0
            nodes = []
            while(i < len(nodes)):
                time, ampl = struct.unpack('<IH', data[i:])
                node = []
                node.append(time)
                node.append(ampl)
                nodes.append(node)
                i += 6
            points = len(nodes)
            self.graz_interface.upload(points, nodes)
            ret = self.graz_interface.state()
            self.measure.state.graz.code = ret[2]
            self.measure.state.graz.status = f'RETCODE {ret}'

            self.graz_interface.ps_off()
            ret = self.graz_interface.state()
            self.measure.state.graz.code = ret[2]
            self.measure.state.graz.status = f'RETCODE {ret}'
        
        self.measure.state.graz.code = 1
        self.measure.state.graz.status = 'CONFIGURED'
        self.measure.state.status = 'GRA CONFIGURED'
        self.measure.state.code = 2
        
        self.lock.acquire()
        self.measure.state.graz.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()
        return 0

    def adcUp(self):
        try:
            self.adc_interface.connect()
        except:
            self.measure.state.adc.status = 'CONN ERROR'
            self.measure.state.adc.code = -1
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return -1
        
        ret = self.adc_interface.open()

        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'OPEN ERROR'
            self.measure.state.adc.code = -2
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret
        
        self.measure.state.adc.status = 'OPENED'
        self.measure.state.adc.code = 1
        self.measure.state.status = 'ADC OPENED'
        self.measure.state.code = 3

        self.lock.acquire()
        self.measure.state.sync.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        print('UPDATED')
        return 0

    def adcConfig(self):
        rate = self.measure.info.iadc.srate
        ret = self.adc_interface.set_rate(rate)

        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'RATE ERROR'
            self.measure.state.adc.code = -3
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret
        
        points = self.measure.info.iadc.points
        ret = self.adc_interface.set_points(points)
        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'POINTS ERROR'
            self.measure.state.adc.code = -4
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret
        
        ret = self.adc_interface.config_channels(self.measure.info.iadc.n_channels,
                                                self.measure.info.iadc.channel_ranges,
                                                self.measure.info.iadc.trigger_channel)
        
        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'CHANNELS CONFIG ERROR'
            self.measure.state.adc.code = -5
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret
        
        ret = self.adc_interface.trigger_configure(self.measure.info.iadc.trig_direction,
                                                self.measure.info.iadc.threshold,
                                                self.measure.info.iadc.auto_measure_time)
        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'TRIGGER CONFIG ERROR'
            self.measure.state.adc.code = -7
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret

        ret = self.adc_interface.set_premeasure(0)

        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'SET PREMEASURE ERROR'
            self.measure.state.adc.code = -6
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret
        
        self.measure.state.adc.status = 'CONFIGURED'
        self.measure.state.adc.code = 2
        self.measure.state.status = 'ADC CONFIGURED'
        self.measure.state.code = 3

        self.lock.acquire()
        self.measure.state.sync.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        print('UPDATED')
        return 0

    def sdrUp(self, thr):
        ret = self.sdr_interface.transf(str(settings.BASE_DIR) + '\\bin\\hackrftrans00.exe',
                                        self.measure.info.isdr.file,
                                        self.measure.info.isdr.freq,
                                        self.measure.info.isdr.srate,
                                        int(self.measure.info.isdr.ampl),
                                        self.measure.info.isdr.gain)
        if(ret[0] == 0xFF):
            self.measure.state.sdr.status = 'SDR ERROR (WAIT FOR STOPING ADC!)'
            self.measure.state.sdr.code = -1
            self.measure.state.status = 'SDR ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.sdr.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            if(thr != None):
                thr.join()
            self.measure.state.sdr.status = 'SDR ERROR'
            self.measure.state.sdr.code = -2

            self.lock.acquire()
            self.measure.state.sdr.save()
            self.measure.state.save()
            self.measure.save()

            self.lock.release()
            self.adc_interface.client_socket.close()
            return ret
        self.measure.state.sdr.status = 'STARTED'
        self.measure.state.sdr.code = 1
        self.measure.state.status = 'SDR START'
        self.measure.state.code = 4

        self.lock.acquire()
        self.measure.state.sdr.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        return 0

    def adcWrite(self, k):
        ret = self.adc_interface.aqcuire_data()

        if(ret[0] == 0xFF):
            self.measure.state.adc.status = 'AQCUIRE ERROR'
            self.measure.state.adc.code = -8
            self.measure.state.status = 'ADC ERROR'
            self.measure.state.code = -3

            self.lock.acquire()
            self.measure.state.adc.save()
            self.measure.state.save()
            self.measure.save()
            self.lock.release()

            self.adc_interface.client_socket.close()
            return ret

        self.lock.acquire()
        for i in range(len(self.adc_interface.ndata)):
            data = models.measurement_data.objects.create(data_num=i, averaging_num=k, measurement=self.measure)
            for j in range(len(self.adc_interface.ndata[i])):
                cdata = data.channel_data.create(channel_num=j,
                                        measurement_data=data,
                                        channel_data=base64.b64encode(np.array(self.adc_interface.ndata[i][j], dtype=np.int16).tobytes()).decode('utf-8'))
                cdata.save()
                data.save()
        self.measure.save()
        self.lock.release()

        self.adc_interface.clear_ndata()
        tm.sleep(1)

        return 0
    
    def close(self):
        ret = self.sync_interface.serial_close()
        if(self.measure.info.iadc.enabled):
            self.adc_interface.client_socket.close()

        self.measure.state.adc.status = 'MEASURE COMPLETED'
        self.measure.state.data_ready = True
        self.measure.state.adc.code = 1
        self.measure.state.status = 'MEASURE COMPLETED'
        self.measure.state.code = 1

        self.lock.acquire()
        self.measure.state.grax.save()
        self.measure.state.gray.save()
        self.measure.state.graz.save()
        self.measure.state.sync.save()
        self.measure.state.sdr.save()
        self.measure.state.adc.save()
        self.measure.state.save()
        self.measure.save()
        self.lock.release()

        return 0

    def run(self):
        # Connect to the sync product first
        # Open the sync product, check for errors if any returned with status byte 0xFF
        self.syncUp()
        self.graUp()

        self.sync_interface.serial_open(self.measure.info.isync.port)

        if(self.measure.info.iadc.enabled == True):
            for k in range(0, self.measure.info.iadc.averaging):
                print('SENDING TRIGGER')

                # Set trigger wait, check for errors
                ret = self.syncWait()
                if(ret != 0):
                    return ret
                
                ret = self.adcUp()
                if(ret != 0):
                    return ret
                
                ret = self.adcConfig()
                if(ret != 0):
                    return ret
                
                t = NewThread(target=self.adc_interface.start)
                t.start()

                tm.sleep(1)

                ret = self.sdrUp(t)
                
                t.join()

                ret = self.adc_interface.measure_code

                if(ret[0] == 0xFF):
                    self.measure.state.adc.status = 'MEASURE ERROR'
                    self.measure.state.adc.code = -7
                    self.measure.state.status = 'ADC ERROR'
                    self.measure.state.code = -3

                    self.lock.acquire()
                    self.measure.state.adc.save()
                    self.measure.state.save()
                    self.measure.save()
                    self.lock.release()

                    self.adc_interface.client_socket.close()
                    return ret

                self.adcWrite(k)
                tm.sleep(self.measure.info.iadc.averaging_delay)
        else:
            print('SENDING TRIGGER')
            # Set trigger wait, check for errors
            ret = self.syncWait()
            if(ret != 0):
                return ret

            ret = self.sdrUp(None)
            if(ret[0] != 0):
                return ret
            

        ret = self.close()
        if(ret != 0):
            return ret

        return 0

class SimDefaultEngine(DefaultEngine):
    pass

EngineDict = {
    'DefaultEngine': DefaultEngine,
    'SimulateDefault': SimDefaultEngine
}
        

