"""
@Time    : 2019/1/9 10:19
@Author  : Sam
@Project : IEC101
@FileName: Base.py
@Software: PyCharm
@Blog    : https://blog.muumlover.com
"""
import asyncio
from abc import abstractmethod

from .Struct import *


class bits(bytearray):
    def __init__(self, byte):
        super().__init__(16)
        if isinstance(byte, bytearray):
            for index, i in enumerate(byte):
                self[index] = i
        else:
            for i in range(0, 16):
                self[i] = (byte >> i) & 0x01

    def hex(self):
        return bytearray([self.byte()]).hex()

    def byte(self):
        byte = 0
        for i in range(0, 16):
            byte |= (self[i] & 0x01) << i
        return byte


class ControlDomain(object):
    """
    控制域C
    """
    _data = 0
    _bit = bytearray(8)

    def __init__(self, data=0):
        self._data = bits(data)

    @property
    def DIR(self):
        """报文方向位（DIR）"""
        return self._data[7]

    @DIR.setter
    def DIR(self, value):
        """报文方向位（DIR）"""
        self._data[7] = value

    @property
    def PRM(self):
        """启动标志位（PRM）"""
        return self._data[6]

    @PRM.setter
    def PRM(self, value):
        """启动标志位（PRM）"""
        self._data[6] = value

    @property
    def FCB(self):
        """帧计数位（FCB）——启动方向"""
        return self._data[5]

    @FCB.setter
    def FCB(self, value):
        """帧计数位（FCB）——启动方向"""
        self._data[5] = value

    @property
    def RES(self):
        """从动方向"""
        return self._data[5]

    @RES.setter
    def RES(self, value):
        """从动方向"""
        self._data[5] = value

    @property
    def FCV(self):
        """帧计数有效位（FCV）——启动方向"""
        return self._data[4]

    @FCV.setter
    def FCV(self, value):
        """帧计数有效位（FCV）——启动方向"""
        self._data[4] = value

    @property
    def DFC(self):
        """从动方向"""
        return self._data[4]

    @DFC.setter
    def DFC(self, value):
        """从动方向"""
        self._data[4] = value

    @property
    def FC(self):
        """链路功能码（FC）"""
        return bits(self._data[0:4]).byte()

    @FC.setter
    def FC(self, value):
        """链路功能码（FC）"""
        self._data[0:4] = bits(value)[0:4]

    @property
    def data(self):
        return self._data.byte()


class PackInterpreter(object):
    cd_location = {
        0x10: 1,  # 固定帧长报文
        0x68: 4  # 可变帧长报文
    }

    def __init__(self, data: bytearray):
        self.data = data
        pass

    @property
    def control_domain(self):
        cd_location = self.cd_location[self.data[0]]
        return ControlDomain(self.data[cd_location])

    @property
    def function_code(self):
        return self.control_domain.FC

    pass


class ControlDomainMaster(ControlDomain):
    """
    控制域C——启动方向
    """
    # 报文方向位（DIR）
    DIR = DIRType.UpLoad  # DIRType()
    # 启动标志位（PRM）
    PRM = PRMType.SlaveStation  # PRMType()
    # 帧计数位（FCB）
    FCB = FCBType.Low  # FCBType()
    # 帧计数有效位（FCV）
    FCV = FCVType.Disabled  # FCVType()
    # 链路功能码（FC）
    FC = FCType.NULL  # FCType()

    # 数据包
    @property
    def join_frame(self):
        bit = (
                ((self.DIR & 0x01) << 7) +
                ((self.PRM & 0x01) << 6) +
                ((self.FCB & 0x01) << 5) +
                ((self.FCV & 0x01) << 4) +
                (self.FC & 0x0f)
        )
        return bit

    @join_frame.setter
    def join_frame(self, value):
        self.DIR = ((value & 0x80) >> 7)
        self.PRM = ((value & 0x40) >> 6)
        self.FCB = ((value & 0x20) >> 5)
        self.FCV = ((value & 0x10) >> 4)
        self.FC = (value & 0x0f)


class ControlDomainSlave(ControlDomain):
    """
    控制域C——从动方向
    """
    # 报文方向位（DIR）
    DIR = DIRType.UpLoad  # DIRType()
    # 启动标志位（PRM）
    PRM = PRMType.SlaveStation  # PRMType()
    RES = RESType.Default  # RESType()
    DFC = DFCType.CanReceive  # DFCType()
    # 链路功能码（FC）
    FC = FCType.NULL  # FCType()

    @property
    def join_frame(self):
        """
        数据包
        """
        bit = (
                ((self.DIR & 0x01) << 7) +
                ((self.PRM & 0x01) << 6) +
                ((self.RES & 0x01) << 5) +
                ((self.DFC & 0x01) << 4) +
                (self.FC & 0x0f))
        return bit

    @join_frame.setter
    def join_frame(self, value):
        self.DIR = ((value & 0x80) >> 7)
        self.PRM = ((value & 0x40) >> 6)
        self.RES = ((value & 0x20) >> 5)
        self.DFC = ((value & 0x10) >> 4)
        self.FC = (value & 0x0f)


class VariableStructureQualifier(object):
    """
    可变结构限定符
    """
    SQ = bool()
    length = bytes()

    @property
    def join_frame(self):
        bit = (((self.SQ & 0x01) << 7) + (self.length & 0x7f))
        return bit

    @join_frame.setter
    def join_frame(self, value):
        self.SQ = bool((value & 0x80) >> 7)
        self.length = (value & 0x7f)


class ICEState(object):
    """
    ICE状态
    """
    # 监听链路状态（链路未复位）
    LinkNoReset = 0
    # 已知链路状态
    LinkState = 1
    # 主站链路复位完毕
    LinkFinishMaster = 2
    # 子站链路复位完毕
    LinkFinishSlave = 3
    # 复位过程完毕
    LinkFinish = 4
    # 总召执行中
    LinkDataSend = 5


FCTypeShow = {

}

TypeIdShow = {
    1: '单点信息',
    2: '带时标的单点信息',
    3: '双点信息',
    4: '带时标的双点信息',
    5: '步位置信息',
    6: '带时标的步位置信息',
    7: '32比特串',
    8: '带时标的32比特串',
    9: '测量值,规一化值',
    10: '测量值,带时标的规一化值',
    11: '测量值,标度化值',
    12: '测量值,带时标的标度化值',
    13: '测量值,短浮点数',
    14: '测量值,带时标的短浮点数',
    15: '累计量',
    16: '带时标的累计量',
    17: '带时标的继电保护设备事件',
    18: '带时标的继电保护设备成组启动事件',
    19: '带时标的继电保护设备成组输出电路信息',
    20: '带变位检出的成组单点信息',
    21: '测量值,不带品质描述词的规一化值',
    22: '为将来兼容定义保留',
    23: '为将来兼容定义保留',
    24: '为将来兼容定义保留',
    25: '为将来兼容定义保留',
    26: '为将来兼容定义保留',
    27: '为将来兼容定义保留',
    28: '为将来兼容定义保留',
    29: '为将来兼容定义保留',
    30: '带CP56Time2a时标的单点信息',
    31: '带CP56Time2a时标的双点信息',
    32: '带CP56Time2a时标的步位置信息',
    33: '带CP56Time2a时标的32比特串',
    34: '带CP56Time2a时标的测量值,规一化值',
    35: '带CP56Time2a时标的测量值,标度化值',
    36: '带CP56Time2a时标的测量值,短浮点数',
    37: '带CP56Time2a时标的累计量',
    38: '带CP56Time2a时标的继电保护设备事件',
    39: '带CP56Time2a时标的继电保护设备成组启动事件',
    40: '带CP56Time2a时标的继电保护设备成组输出电路信息',
    41: '为将来兼容定义保留',
    42: '为将来兼容定义保留',
    43: '为将来兼容定义保留',
    44: '为将来兼容定义保留',
    45: '单点命令',
    46: '双点命令',
    47: '步调节命令',
    48: '设定值命令,规一化值',
    49: '设定值命令,标度化值',
    50: '设定值命令,短浮点数',
    51: '32比特串',
    52: '为将来兼容定义保留',
    53: '为将来兼容定义保留',
    54: '为将来兼容定义保留',
    55: '为将来兼容定义保留',
    56: '为将来兼容定义保留',
    57: '为将来兼容定义保留',
    58: '为将来兼容定义保留',
    59: '为将来兼容定义保留',
    60: '为将来兼容定义保留',
    61: '为将来兼容定义保留',
    62: '为将来兼容定义保留',
    63: '为将来兼容定义保留',
    64: '为将来兼容定义保留',
    65: '为将来兼容定义保留',
    66: '为将来兼容定义保留',
    67: '为将来兼容定义保留',
    68: '为将来兼容定义保留',
    69: '为将来兼容定义保留',
    70: '初始化结束',
    100: '总召唤命令',
    101: '计数量召唤命令',
    102: '读命令',
    103: '时钟同步命令',
    104: '测试命今',
    105: '复位进程命令',
    106: '延时获得命今',
    110: '测量值参数,规一化值',
    111: '测量值参数,标度化值',
    112: '测量值参数,短浮点数',
    113: '参数激活',
    120: '文件淮备就绪',
    121: '节淮备就绪',
    122: '召唤目录,选择文件,召唤文件，召唤节',
    123: '最后的节,最后的段',
    124: '认可文件,认可节',
    125: '段',
    126: '目录',
    127: '为将来兼容定义保留'
}

CauseShow = {
    1: '周期、循环',
    2: '背景扫描1',
    3: '突发(自发)',
    4: '初始化',
    5: '请求或者被请求',
    6: '激活',
    7: '激活确认',
    8: '仃止激活',
    9: '仃止激活确认',
    10: '激活终止',
    11: '远方命令引起的返送信息',
    12: '当地命令引起的返送信息',
    13: '文件传输',
    14: '为配套标准兼容范围保留',
    19: '为配套标准兼容范围保留',
    20: '响应站召唤',
    21: '响应第1组召唤',
    22: '响应第2组召唤',
    23: '响应第3组召唤',
    24: '响应第4组召唤',
    25: '响应第5组召唤',
    26: '响应第6组召唤',
    27: '响应第7组召唤',
    28: '响应第8组召唤',
    29: '响应第9组召唤',
    30: '响应第10组召唤',
    31: '响应第11组召唤',
    32: '响应第12组召唤',
    33: '响应第13组召唤',
    34: '响应第14组召唤',
    35: '响应第15组召唤',
    36: '响应第16组召唤',
    37: '响应计数量总召唤',
    38: '响应笫1组计数量召唤',
    39: '响应笫2组计数量召唤',
    40: '响应笫3组计数量召唤',
    41: '响应笫4组计数量召唤',
    42: '为配套标准兼容范围保留',
    43: '为配套标准兼容范围保留',
    44: '未知的类型标识',
    45: '未知的传送原因',
    46: '未知的应用服务数据单元公共地址',
    47: '未知的信息对象地址',
    48: '特殊应用保苗(专用范围)',
    63: '特殊应用保苗(专用范围)'
}


class ICE_Para(object):
    # 通讯设置
    CallAfterInit = True  # 配电主站接收到配电终端的“初始化结束”报文后，对该终端进行总召唤过程

    # 通讯参数
    YxStartAddr = 0
    Yx2StartAddr = 0
    YcStartAddr = 0
    LinkAddrLength = 2  # 链路地址长度
    CauseLength = 2
    InfoAddrLength = 2
    PublicAddrLength = 2

    # 显示参数
    FCTypeShow = FCTypeShow
    CauseShow = CauseShow
    TypeIdShow = TypeIdShow

    # 运行状态
    id = 1
    name = "未知设备"
    try_addr = None  # 尝试通讯的设备地址
    thisICEState = ICEState.LinkNoReset
    thisDIRType = DIRType.DownLoad
    thisPRMType = PRMType.MasterStation
    thisFCBType = FCBType.Low
    thisFCVType = FCVType.Disabled


class ICE101_Base(ICE_Para):
    def CreateFixedPack(self, fCType: FCType, reSend: bool):
        """
        合成定长数据包
        :param fCType:
        :param reSend:
        :return:
        """
        controlDomainMaster = ControlDomainMaster()
        controlDomainMaster.DIR = self.thisDIRType
        controlDomainMaster.PRM = self.thisPRMType
        if self.thisFCVType == FCVType.Enabled:
            if reSend:
                controlDomainMaster.FCB = self.thisFCBType
            elif self.thisFCBType == FCBType.Low:
                controlDomainMaster.FCB = self.thisFCBType = FCBType.High
            else:
                controlDomainMaster.FCB = self.thisFCBType = FCBType.Low
        else:
            controlDomainMaster.FCB = self.thisFCBType = FCBType.Low
        controlDomainMaster.FCV = self.thisFCVType
        self.thisFCVType = FCVType.Disabled
        controlDomainMaster.FC = fCType
        return self.CreateFixedFrame(controlDomainMaster, self.id)

    def CreateVariablePack(self, fCType: FCType, data, reSend: bool):
        """
        合成可变长数据包
        :param fCType:
        :param data:
        :param reSend:
        :return:
        """
        controlDomainMaster = ControlDomainMaster()
        controlDomainMaster.DIR = self.thisDIRType
        controlDomainMaster.PRM = self.thisPRMType
        if self.thisFCVType == FCVType.Enabled:
            if reSend:
                controlDomainMaster.FCB = self.thisFCBType
            elif self.thisFCBType == FCBType.Low:
                controlDomainMaster.FCB = self.thisFCBType = FCBType.High
            else:
                controlDomainMaster.FCB = self.thisFCBType = FCBType.Low
        controlDomainMaster.FCV = self.thisFCVType
        self.thisFCVType = FCVType.Disabled
        controlDomainMaster.FC = fCType
        return self.CreateVariableFrame(controlDomainMaster, self.id, data)

    def CreateFixedFrame(self, control_domain: ControlDomain, address: int):
        """
        固定帧长报文
        :param control_domain: 控制域
        :param address: 地址域
        :return:
        """
        # DIR = DIRType.DownLoad
        addr = address.to_bytes(2, byteorder='little')
        frame = bytearray(4 + self.LinkAddrLength)
        frame[0] = 0x10
        frame[1] = control_domain.join_frame
        frame[2:2 + self.LinkAddrLength] = addr[0:self.LinkAddrLength]
        index = 2 + self.LinkAddrLength
        frame[index] = (sum(addr[0:self.LinkAddrLength]) + control_domain.join_frame) & 0xFF
        frame[index + 1] = 0x16
        return frame

    def CreateVariableFrame(self, control_domain: ControlDomain, address: int, data: bytes):
        """
        可变帧长报文
        :param control_domain: 控制域
        :param address: 地址域
        :param data: 可变数据
        :return:
        """
        addr = address.to_bytes(2, byteorder='little')
        data_len = len(data)
        pack_len = data_len + 3
        # DIR = DIRType.DownLoad
        frame = bytearray(pack_len + 6)
        frame[0] = 0x68
        frame[1] = pack_len
        frame[2] = pack_len
        frame[3] = 0x68
        frame[4] = control_domain.join_frame
        frame[5] = addr[0]
        frame[6] = addr[1]
        frame[7:7 + data_len] = data
        for i in range(4, pack_len + 4):
            frame[pack_len + 4] = (frame[pack_len + 4] + frame[i]) & 0xFF
        frame[pack_len + 5] = 0x16
        return frame

    def GetId(self, datas: bytearray):
        return GetId(datas, self.LinkAddrLength)

    def getControlDomain(self, datas: bytearray):
        if datas[0] == 0x10:  # 固定帧长报文
            locCtrl = 1
        elif datas[0] == 0x68:  # 可变帧长报文
            locCtrl = 4
        else:
            return FCType.NULL  # 报文类型错误

    def getFunctionCode(self, datas: bytearray):
        """
        从数据包解析控制域
        :param datas:
        :return:
        """
        controlDomainMaster = ControlDomainMaster()
        # 定位控制域位置
        if datas[0] == 0x10:  # 固定帧长报文
            locCtrl = 1
        elif datas[0] == 0x68:  # 可变帧长报文
            locCtrl = 4
        else:
            return FCType.NULL  # 报文类型错误
        controlDomainMaster.join_frame = datas[locCtrl]  # 假设终端为启动站
        if controlDomainMaster.PRM == PRMType.MasterStation:  # 终端确实为启动站——报文为主动上报
            self.thisPRMType = PRMType.SlaveStation  # 主站为从动站
            if self.id == self.GetId(datas):
                return controlDomainMaster.FC
            else:
                return FCType.NULL
        else:  # 终端为从动站——这条报文为返回报文
            controlDomainSlave = ControlDomainSlave()
            self.thisPRMType = PRMType.MasterStation  # 主站为启动站
            controlDomainSlave.join_frame = datas[locCtrl]  # 用从动站解析终端
            if controlDomainSlave.DIR == DIRType.UpLoad:
                pass
            if controlDomainSlave.RES == RESType.Default:
                pass
            if controlDomainSlave.DFC == DFCType.CanNotReceive:
                pass
            if self.id == self.GetId(datas):
                return controlDomainSlave.FC
            else:
                return FCType.NULL


class SoctetBase(asyncio.Protocol):
    def __init__(self, loop):
        self.loop = loop
        self.timer = None
        self.transport = None

    def connection_made(self, transport):
        """
        新的设备连接
        :param transport:
        :return:
        """
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        """
        默认数据处理函数，需要被重载
        :param data:
        :return:
        """
        message = data.decode()
        print('Data received: {!r}'.format(message))

    def time_out(self):
        """
        设备超时函数
        :return:
        """
        print('time_out')
        self.transport.close()

    # region ICE101超时处理
    def timer_init(self):
        # timeout = Convert.ToInt32(SqliteProvider.ReadPara(SqliteProvider.runpara, "ICEWatchOverTime"))
        # timeout = 3000
        # Timer(Timer_Tick, None, timeout, Timeout.Infinite)
        if self.timer is not None and not self.timer.cancelled():
            self.timer.cancel()
        self.timer = self.loop.call_later(1, self.timer_tick)
        pass

    # 101协议定时维护
    @abstractmethod
    def timer_tick(self):
        self.timer = None


def GetId(datas: bytes, length: int):
    """
    从数据包内获取子站地址——ID
    :param datas: 数据包
    :param length: 地址长度
    :return:
    """
    # int length = System.Convert.ToInt32(SqliteProvider.ReadPara(SqliteProvider.icepara, "LinkAddrLength"))
    if datas[0] == 0x10:  # 固定帧长报文
        return int.from_bytes(datas[2:2 + length], byteorder='little')  # little big
    elif datas[0] == 0x68:  # 可变帧长报文
        return int.from_bytes(datas[5:5 + length], byteorder='little')
    else:
        return 0
