import collections
import json
from utils.DBModels.DeviceModels import DeviceDB
import numpy as np
from protocol.ModBus.ByteOrder import *
from protocol.ModBus.TCPMaster import *
import struct
import time
#从站 "AI" "DI"可强制
#主站 "AO" "DO"可强制

class Area():
    startAddress = None
    endAddress = None
    bytes = None
    type = None
    addressList = []
    order = 'ABCD'
    nums = 0
    forceValue = [0]
    currentValue = [0] * nums
    def __init__(self):
        pass

class Device():
    inputStartAddress = 0
    inputEndAddress = 0
    outputStartAddress = 0
    outputEndAddress = 0
    protocolType = None
    masterOrSlave = None
    deviceName = None
    def __init__(self):
        self.inputAreas = []
        self.outputAreas = []
        self.areas = []
        self.indexDict = collections.OrderedDict()
        # 有序字典 键:总区域索引 值: [类型(输入 : 0, 或输出 : 1), 该类型的第几个区域索引]

    def addArea(self, type, nums, bytes, order = 'ABCD'):
        area = Area()
        bytes = int(bytes)
        area.type = type
        area.order = order
        area.bytes = bytes
        area.length = self.getLength(nums, bytes)
        area.nums = nums
        area.currentValue = [0] * nums
        if type in ["AI", "DI"]:
            area.startAddress = 0 if not self.inputEndAddress else self.inputEndAddress
            area.endAddress = area.startAddress + area.length
            # print(area.startAddress, area.endAddress)
            self.inputEndAddress = area.endAddress
            area.addressList = np.arange(area.startAddress, area.endAddress + 1, area.bytes).tolist()
            self.indexDict[len(self.indexDict.values())] = [0, len(self.inputAreas)]
            self.inputAreas.append(area)
            self.areas.append(area)
        elif type in ["DO" , "AO"]:
            area.startAddress = 0 if not self.outputEndAddress else self.outputEndAddress
            area.endAddress = area.startAddress + area.length
            self.outputEndAddress = area.endAddress
            area.addressList = np.arange(area.startAddress, area.endAddress + 1, area.bytes).tolist()
            self.indexDict[len(self.indexDict.values())] = [1, len(self.outputAreas)]
            self.outputAreas.append(area)
            self.areas.append(area)
        if (self.masterOrSlave == '主站' and type in ['AI', 'DI']) or (self.masterOrSlave == '从站' and type in ['AO', 'DO']):
            return True
        # print(id(self), self.inputAreas)
        # print(self.deviceName, area.addressList, area.startAddress, area.endAddress, self.inputAreas)

    def delArea(self, index, type):
        if type in ["DI", "AI"]:
            self.inputAreas.pop(self.indexDict[index][1])
        elif type in ["AO", "DO"]:
            self.outputAreas.pop(self.indexDict[index][1])
        # self.indexDict.pop(index)
        newDict = collections.OrderedDict()
        rmValue = self.indexDict[index]
        for key, value in self.indexDict.items():
            # print(value)
            if key < index:
                newDict[key] = value
            elif key > index:
                if value[0] == rmValue[0]:
                    newDict[key - 1] = [value[0], value[1] - 1]  # 更新类型索引和区域索引
                else:
                    newDict[key - 1] = [value[0], value[1]]

        self.indexDict = newDict
        self.areas.pop(index)
        self.recalculateAddress()

    def recalculateAddress(self):
        # print(self.inputStartAddress)
        for inputOrOutput, areas in enumerate([self.inputAreas, self.outputAreas]):
            endAddress = 0
            for index, area in enumerate(areas):
                if index == 0 and inputOrOutput == 0:
                    area.startAddress = self.inputStartAddress
                elif index == 0 and inputOrOutput == 1:
                    area.startAddress = self.outputStartAddress
                else:
                    area.startAddress = areas[index - 1].endAddress
                area.endAddress = area.startAddress + area.length
                area.addressList = np.arange(area.startAddress, area.endAddress + 1, area.bytes).tolist()
                # print(area.addressList, area.startAddress, area.endAddress, self.deviceName)
                endAddress = area.endAddress
                # print(self.deviceName, area.startAddress, area.endAddress, area.bytes, time.time())
                # print(endAddress)
            # else:
            #     print(endAddress)
            if endAddress == 0 and inputOrOutput == 0:
                endAddress = self.inputStartAddress
            if endAddress == 0 and inputOrOutput == 1:
                endAddress = self.outputStartAddress
            if inputOrOutput == 0:
                self.inputEndAddress = endAddress
            elif inputOrOutput == 1:
                self.outputEndAddress = endAddress
                # endAddress = 0

    def editArea(self, index, type, order, bytes):
        # if type in ["DI", "AI"]:
        #     self.inputAreas[self.indexDict[index]].order = order
        #     self.inputAreas[self.indexDict[index]].bytes = bytes
        # elif type in ["AO", "DO"]:
        #     self.outputAreas[self.indexDict[index]].order = order
        #     self.outputAreas[self.indexDict[index]].bytes = bytes
        # print(bytes)
        # print(self.areas[index])
        self.areas[index].order = order
        self.areas[index].bytes = bytes
        self.areas[index].length = int(self.areas[index].nums) * int(bytes)
        # self.recalculateAddress()


    def getAreaValues(self, index):
        # if self.masterOrSlave == "从站":
        #     return self.outputAreas[index].currentValue
        # else:
        #     # print(self.inputAreas, index)
        #     # print(id(self), self.inputAreas)
        #     return self.inputAreas[index].currentValue
        # print(self.areas[index].currentValue)
        return self.areas[index].currentValue

    def getLength(self, nums, bytes):
        length = int(nums) * int(bytes)
        # length = length / 2
        return length



    
                

    @classmethod
    def delAreas(self, deviceName, id):
        jsonCon = json.loads(DeviceDB.getByName(deviceName=deviceName).areaJson)
        if len(jsonCon) > id:
            jsonCon.pop(id)
        else:
            return

        if jsonCon == []:
            areaJson = '[]'
            DeviceDB.update(areaJson=areaJson).where(DeviceDB.deviceName == deviceName).execute()
        else:
            for index, areajsonId in enumerate(jsonCon):
                areajsonId["id"] = index
            areaJson = json.dumps(jsonCon)
         
            DeviceDB.update(areaJson=areaJson).where(DeviceDB.deviceName == deviceName).execute()


    @classmethod
    def getAreaJson(self, deviceNames):
        deviceName = deviceNames
        jsonConsStr = json.loads(DeviceDB.getByName(deviceName=deviceName).areaJson)
        if jsonConsStr == []:
            return
        else:
            jsonCons = json.loads(jsonConsStr)
            return jsonCons



class DevicesManange():
    def __init__(self):
        self.dpMasterDevices = collections.OrderedDict()
        self.dpSlaveDevices = collections.OrderedDict() # 有序字典 (OrderedDict)
        self.paMasterDevices = collections.OrderedDict()
        self.paSlaveDevices = collections.OrderedDict()
    def connect(self):
        self.dpSlaveModbus = TcpMaster(host = '192.168.2.10', port = 502)
        self.paSlaveModbus = TcpMaster(host = '192.168.4.10', port = 502)
        self.dpMasterModbus = TcpMaster(host = '192.168.1.10', port = 502)
        self.paMasterModbus = TcpMaster(host = '192.168.3.10', port = 502)



    def addDevice(self, proType, masterSlaveModel, deviceName):
        device = Device()
        device.type = proType
        device.masterOrSlave = masterSlaveModel
        device.deviceName = deviceName

        if proType == "DP" and masterSlaveModel == "主站":
            curProDict = self.dpMasterDevices
        elif proType == "DP" and masterSlaveModel == "从站":
            curProDict = self.dpSlaveDevices
        elif proType == "PA" and masterSlaveModel == "主站":
            curProDict = self.paMasterDevices
        elif proType == "PA" and masterSlaveModel == "从站":
            curProDict = self.paSlaveDevices

        if len(curProDict) == 0:
            device.inputStartAddress = 0
            device.outputStartAddress = 0
        else:
            device.inputStartAddress = list(curProDict.values())[-1].inputEndAddress
            device.outputStartAddress = list(curProDict.values())[-1].outputEndAddress
        curProDict[deviceName] = device

    def initDevices(self):
        pass

    def delDevice(self, deviceName):
        for devicesDict in [self.paMasterDevices, self.paSlaveDevices, self.dpMasterDevices, self.dpSlaveDevices]:
            if deviceName in devicesDict:
                del devicesDict[deviceName]
                self.recalculateAddress()

    def recalculateAddress(self):
        for devicesDict in [self.paMasterDevices, self.paSlaveDevices, self.dpMasterDevices, self.dpSlaveDevices]:
            # print(len(devicesDict))
            # print(devicesDict)
            # print(self.dpMasterDevices)
            for index, (deviceName, device) in enumerate(devicesDict.items()):
                if index == 0:
                    device.inputStartAddress = 0
                    device.outputStartAddress = 0
                else:
                    inputAddress = list(devicesDict.values())[index - 1].inputEndAddress
                    outputAddress = list(devicesDict.values())[index - 1].outputEndAddress
                    device.inputStartAddress = 0 if inputAddress == 0 else inputAddress
                    device.outputStartAddress = 0 if outputAddress == 0 else outputAddress
                # print(device.inputStartAddress, device.inputEndAddress, deviceName, '输入')
                device.recalculateAddress()
                # print(device.outputStartAddress, device.outputEndAddress, deviceName)
                # print(device.outputStartAddress, device.outputEndAddress, deviceName, 'shuchu')
                # previousDevice = device


    def getDevice(self, deviceName):
        for devicesDict in [self.paMasterDevices, self.paSlaveDevices, self.dpMasterDevices, self.dpSlaveDevices]:
            if deviceName in devicesDict:
                return devicesDict[deviceName]

    def writeAreas(self, deviceName, areaIndex, values):
        # print(values)
        bytes = b""
        device = self.getDevice(deviceName)
        # print(device, deviceName)
        if device.type == "DP" and device.masterOrSlave == "主站":
            curProDict = self.dpMasterDevices
            # areas = device.outputAreas
            modbusM = self.dpMasterModbus
        elif device.type == "DP" and device.masterOrSlave == "从站":
            curProDict = self.dpSlaveDevices
            # areas = device.inputAreas
            modbusM = self.dpSlaveModbus
        elif device.type == "PA" and device.masterOrSlave == "主站":
            # areas = device.outputAreas
            curProDict = self.paMasterDevices
            modbusM = self.paMasterModbus
        elif device.type == "PA" and device.masterOrSlave == "从站":
            # areas = device.inputAreas
            curProDict = self.paSlaveDevices
            modbusM = self.paSlaveModbus
        # print(values)
        # for area, value in zip(areas, values):
        area = device.areas[areaIndex]
        area.forceValue = values
        if area.type in ['DO', 'DI'] and device.type == 'PA':
            area.forceValue = values[8:] + values[:8]
        elif area.type in ['DO', 'DI'] and device.type == 'DP' and device.masterOrSlave == "主站":
            area.forceValue = values[8:] + values[:8]

        for device in curProDict.values():
            forceAreas = device.outputAreas if device.masterOrSlave == "主站" else device.inputAreas
            for area in forceAreas:
                # print(area.forceValue)
                if area.type in ["AI", "AO"]:
                    # print(area.forceValue)
                    byte = floatToBytes(area.forceValue, area.bytes, order = area.order)
                elif area.type in ["DI", "DO"]:
                    # if device.type == "PA" and device.masterOrSlave == "从站" and len(area.forceValue) == 16:
                    byte = coilsToBytes([int(x) for x in area.forceValue], area.bytes)
                bytes += byte
        else:
            if len(bytes) % 2 != 0:
                bytes += struct.pack('B', 0)
        # print(bytes)
        # print(bytes)
        values = struct.unpack('!' + 'H' * int(len(bytes) / 2), bytes)
        modbusM.writeMultipleRegister(slaveId = 1, address = 0, outputValue = values)
            # print(struct.unpack('>f', struct.pack('!HH', *values[:2])))

    def readAreas(self):
        for index, curProDict in enumerate([self.paMasterDevices, self.paSlaveDevices, self.dpMasterDevices, self.dpSlaveDevices]):
            match index:
                case 0:
                    areaType = 'input'
                    modbusM = self.paMasterModbus
                case 1:
                    areaType = 'output'
                    modbusM = self.paSlaveModbus
                case 2:
                    areaType = 'input'
                    modbusM = self.dpMasterModbus
                case 3:
                    areaType = 'output'
                    modbusM = self.dpSlaveModbus
            if len(list(curProDict.values())) == 0:
                continue
            inputEndAddress = max([x.inputEndAddress for x in list(curProDict.values())])
            outputEndAddress = max([x.outputEndAddress for x in list(curProDict.values())])
            inputAreaNums = sum([len(x.inputAreas) for x in list(curProDict.values())]) - 1
            outputAreaNums = sum([len(x.outputAreas) for x in list(curProDict.values())]) - 1
            bytesNums = inputEndAddress if areaType == 'input' else outputEndAddress
            intNums = int(bytesNums / 2) if bytesNums % 2 == 0 else int(bytesNums / 2) + 1
            if bytesNums == 0:
                continue
            intValues = modbusM.readHoldingRegisters(slaveId = 1, startAddress = 0, varNums = intNums)
            if intValues == 'error':
                self.connect()
                return
            bytesValues = struct.pack(f"!{'H' * len(intValues)}", *intValues)
            # print(bytesValues, intNums, bytesNums)
                    # print(bytes)
            for device in curProDict.values():
                readAreas = device.inputAreas if areaType == 'input' else device.outputAreas
                for area in readAreas:
                    # print(area.startAddress, area.endAddress)
                    if area.startAddress == 0:
                        bytes = bytesValues[area.startAddress:area.endAddress]
                    else:
                        bytes = bytesValues[area.startAddress:area.endAddress]
                    # print(area.startAddress, area.endAddress)
                    if area.type in ['AI', 'AO']:
                        # print(area)
                        for i in range(0, len(bytes), 4):
                            byte = bytes[i:i+4]
                            if len(byte) != 4:
                                continue
                            # print(round(struct.unpack('!f', reorderBytes(byte, area.order))[0], 4))
                            area.currentValue[i] = round(struct.unpack('!f', reorderBytes(byte, area.order))[0], 4)
                            # print(round(struct.unpack('!f', reorderBytes(byte, area.order))[0], 4))
                    elif area.type in ['DI', 'DO']:
                        if device.masterOrSlave == '主站' and device.type == 'DP':
                            bytes = bytes
                        else:
                            bytes = bytes[::-1]
                        area.currentValue = bytesToCoils(bytes)
                    # print(area.currentValue, device.deviceName, area.startAddress,area.endAddress)
            # print()
    @classmethod
    def addAreas(self, type, order, bytes, deviceName):
        jsonCon = json.loads(DeviceDB.getByName(deviceName=deviceName).areaJson)
        if jsonCon == []:
            jsonCon = ([{
                "id": 0,
                "type": type,
                "order": order,
                "bytes": bytes,

            }])
        else:
            id = jsonCon[-1]["id"] + 1
            jsonCon.append({
                "id": id,
                "type": type,
                "order": order,
                "bytes": bytes,
            })
        areaJson = json.dumps(jsonCon)
        DeviceDB.update(areaJson=areaJson).where(DeviceDB.deviceName == deviceName).execute()

    @classmethod
    def updataAreas(self, type, order, bytes, deviceName, index):
        jsonCon = json.loads(DeviceDB.getByName(deviceName=deviceName).areaJson)
        if jsonCon == []:
            return False
        else:
            for area in jsonCon:
                if index == area["id"]:
                    area["type"] = type
                    area["order"] = order
                    area["bytes"] = bytes
            areaJson = json.dumps(jsonCon)
            DeviceDB.update(areaJson=areaJson).where(DeviceDB.deviceName == deviceName).execute()

    @classmethod
    def getAreaID(self, deviceNames):
        deviceName = deviceNames
        jsonConsStr = DeviceDB.getByName(deviceName=deviceName).areaJson
        if jsonConsStr is None:
            return
        else:
            jsonCons = json.loads(jsonConsStr)
            id = []
            for jsonCon in jsonCons:
                id.append(jsonCon["id"])
            return id


    @classmethod
    def getChannelLength(self, deviceName):
        number = 0
        if DeviceDB.getByName(deviceName=deviceName).areaJson is None:
            return number
        else:
            numbers = json.loads(DeviceDB.getByName(deviceName=deviceName).areaJson)
            for i in numbers:
                number += int(i['nums'])
            return number

    def editDevies(self):
        pass

    @classmethod
    def getAllDevice(self):
        # 查询所有设备
        devices = DeviceDB.get_all()
        if devices is 'error':
            return
        l = []
        for x in devices:
            l.append([x.deviceName, x.proType, x.masterSlaveModel, x.areaJson, x.pvLowerLimit, x.pvUpperLimit, x.pvUnit])
        return l
    
    @classmethod
    def getLimitData(cls, deviceName):
        try:
            pvUpperLimit = DeviceDB.getByName(deviceName=deviceName).pvUpperLimit
            pvLowerLimit = DeviceDB.getByName(deviceName=deviceName).pvLowerLimit
            pvUnit = DeviceDB.getByName(deviceName=deviceName).pvUnit
            l =[pvUpperLimit, pvLowerLimit, pvUnit]
            return l
        except Exception as e:
            return print(e)
