#
#  --------------------------------------------------------------------------
#   Gurux Ltd
#
#
#
#  Filename: $HeadURL$
#
#  Version: $Revision$,
#                   $Date$
#                   $Author$
#
#  Copyright (c) Gurux Ltd
#
# ---------------------------------------------------------------------------
#
#   DESCRIPTION
#
#  This file is a part of Gurux Device Framework.
#
#  Gurux Device Framework is Open Source software; you can redistribute it
#  and/or modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; version 2 of the License.
#  Gurux Device Framework is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#  See the GNU General Public License for more details.
#
#  More information of Gurux products: http://www.gurux.org
#
#  This code is licensed under the GNU General Public License v2.
#  Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt
# ---------------------------------------------------------------------------
from __future__ import print_function
import pkg_resources
from .enums import Standard, ObjectType, DataType
from .GXStandardObisCodeCollection import GXStandardObisCodeCollection
from .GXStandardObisCode import GXStandardObisCode
from .internal._GXCommon import _GXCommon
from .GXEnum import GXEnum
from .GXInt8 import GXInt8
from .GXInt16 import GXInt16
from .GXInt32 import GXInt32
from .GXInt64 import GXInt64
from .GXUInt8 import GXUInt8
from .GXUInt16 import GXUInt16
from .GXUInt32 import GXUInt32
from .GXUInt64 import GXUInt64
from .GXDate import GXDate
from .GXDateTime import GXDateTime
from .GXTime import GXTime
from .GXByteBuffer import GXByteBuffer
from .manufacturersettings.GXObisCode import GXObisCode


###Python 2 requires this
# pylint: disable=bad-option-value,old-style-class
class GXDLMSConverter:
    #
    # Constructor.
    #
    # value: Used standard.
    #
    def __init__(self, value=Standard.DLMS):
        self.standard = value
        # Collection of standard OBIS codes.
        self.codes = GXStandardObisCodeCollection()

    #
    # Get OBIS code description.
    #
    # @param logicalName
    # Logical name (OBIS code).
    # @param type
    # Object type.
    # @param description
    # Description filter.
    # Array of descriptions that match given OBIS code.
    #
    def getDescription(self, logicalName, type_=ObjectType.NONE, description=None):
        if not self.codes:
            self.__readStandardObisInfo(self.standard, self.codes)
        list_ = []
        all_ = not logicalName
        for it in self.codes.find(logicalName, type_):
            if description and not it.description.lower().contains(description.lower()):
                continue
            if all_:
                list_.append(
                    "A="
                    + it.getOBIS()[0]
                    + ", B="
                    + it.getOBIS()[1]
                    + ", C="
                    + it.getOBIS()[2]
                    + ", D="
                    + it.getOBIS()[3]
                    + ", E="
                    + it.getOBIS()[4]
                    + ", F="
                    + it.getOBIS()[5]
                    + "\r\n"
                    + it.description
                )
            else:
                list_.append(it.description)
        return list_

    # pylint: disable=too-many-boolean-expressions
    @classmethod
    def __updateOBISCodeInfo(cls, codes, it, standard):
        ln = it.logicalName
        list_ = codes.find(ln, it.objectType)
        code_ = list_[0]
        if code_:
            if not it.description:
                it.description = code_.description
            # Update data type from DLMS standard.
            if standard != Standard.DLMS:
                d = list_[len(list_) - 1]
                code_.dataType = d.dataType
            if not code_.uiDataType:
                if "10" in code_.dataType:
                    code_.uiDataType = "10"
                elif "25" in code_.dataType or "26" in code_.dataType:
                    code_.uiDataType = "25"
                elif "9" in code_.dataType:
                    if (
                        GXStandardObisCodeCollection.equalsMask2(
                            "0.0-64.96.7.10-14.255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "0.0-64.0.1.5.0-99,255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "0.0-64.0.1.2.0-99,255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.1.2.0-99,255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.1.5.0-99,255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.9.0.255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.9.6.255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.9.7.255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.9.13.255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.9.14.255", ln
                        )
                        or GXStandardObisCodeCollection.equalsMask2(
                            "1.0-64.0.9.15.255", ln
                        )
                    ):
                        code_.uiDataType = "25"
                    # Local time
                    elif GXStandardObisCodeCollection.equalsMask2(
                        "1.0-64.0.9.1.255", ln
                    ):
                        code_.uiDataType = "27"
                    # Local date
                    elif GXStandardObisCodeCollection.equalsMask2(
                        "1.0-64.0.9.2.255", ln
                    ):
                        code_.uiDataType = "26"
                    # Active firmware identifier
                    elif GXStandardObisCodeCollection.equalsMask2("1.0.0.2.0.255", ln):
                        code_.uiDataType = "10"
                # Unix time
                elif (
                    it.objectType == ObjectType.DATA
                    and GXStandardObisCodeCollection.equalsMask2(
                        "0.0.1.1.0.255", it.logicalName
                    )
                ):
                    code_.uiDataType = "25"

            if (
                not code_.dataType == "*"
                and not code_.dataType == ""
                and "," not in code_.dataType
            ):
                tp = int(code_.dataType)
                if it.objectType in (
                    ObjectType.DATA,
                    ObjectType.REGISTER,
                    ObjectType.REGISTER_ACTIVATION,
                    ObjectType.EXTENDED_REGISTER,
                ):
                    it.setDataType(2, tp)
            if code_.uiDataType:
                tp = int(code_.uiDataType)
                if it.objectType in (
                    ObjectType.DATA,
                    ObjectType.REGISTER,
                    ObjectType.REGISTER_ACTIVATION,
                    ObjectType.EXTENDED_REGISTER,
                ):
                    it.setUIDataType(2, tp)
        else:
            print("Unknown OBIS Code: " + it.logicalName + " Type: " + it.objectType)

    def updateOBISCodeInformation(self, objects):
        if not self.codes:
            self.__readStandardObisInfo(self.standard, self.codes)
        if isinstance(objects, list):
            for it in objects:
                self.__updateOBISCodeInfo(self.codes, it, self.standard)
        else:
            self.__updateOBISCodeInfo(self.codes, objects, self.standard)

    @classmethod
    def __getObjects(cls, standard):
        codes = []
        if standard == Standard.ITALY:
            str_ = pkg_resources.resource_string(__name__, "Italy.txt").decode("utf-8")
        elif standard == Standard.INDIA:
            str_ = pkg_resources.resource_string(__name__, "India.txt").decode("utf-8")
        elif standard == Standard.SAUDI_ARABIA:
            str_ = pkg_resources.resource_string(__name__, "SaudiArabia.txt").decode(
                "utf-8"
            )
        elif standard == Standard.SPAIN:
            str_ = pkg_resources.resource_string(__name__, "Spain.txt").decode("utf-8")
        if not str_:
            return None
        str_ = str_.replace("\n", "\r")
        rows = str_.split("\r")
        for it in rows:
            if it and not it.startswith("#"):
                items = it.split(";")
                if len(items) > 1:
                    ot = int(items[0])
                    ln = _GXCommon.toLogicalName(_GXCommon.logicalNameToBytes(items[1]))
                    version = int(items[2])
                    desc = items[3]
                    code_ = GXObisCode(ln, ot, 0, desc)
                    code_.version = version
                    if len(items) > 4:
                        code_.uiDataType = items[4]
                    codes.append(code_)
        return codes

    @classmethod
    def __readStandardObisInfo(cls, standard, codes):
        if standard != Standard.DLMS:
            for it in GXDLMSConverter.__getObjects(standard):
                tmp = GXStandardObisCode(it.logicalName.split("."))
                tmp.interfaces = str(it.objectType)
                tmp.description = it.description
                tmp.uiDataType = it.uiDataType
                codes.append(tmp)

        str_ = pkg_resources.resource_string(__name__, "OBISCodes.txt").decode("utf-8")
        str_ = str_.replace("\n", "\r")
        rows = str_.split("\r")
        for it in rows:
            if it and not it.startswith("#"):
                items = it.split(";")
                obis = items[0].split(".")
                try:
                    code_ = GXStandardObisCode(
                        obis,
                        str(items[3])
                        + "; "
                        + str(items[4])
                        + "; "
                        + str(items[5])
                        + "; "
                        + str(items[6])
                        + "; "
                        + str(items[7]),
                        str(items[1]),
                        str(items[2]),
                    )
                    codes.append(code_)
                except UnicodeEncodeError:
                    pass

    @classmethod
    def changeType(cls, value, type_):
        if _GXCommon.getDLMSDataType(value) == type_:
            return value
        if type_ == DataType.ARRAY:
            raise ValueError("Can't change array types.")
        if type_ == DataType.BCD:
            ret = int(value)
        elif type_ == DataType.BOOLEAN:
            ret = bool(int(value))
        elif type_ == DataType.COMPACT_ARRAY:
            raise ValueError("Can't change compact array types.")
        elif type_ == DataType.DATE:
            ret = GXDate(value)
        elif type_ == DataType.DATETIME:
            ret = GXDateTime(value)
        elif type_ == DataType.ENUM:
            ret = GXEnum(value)
        elif type_ == DataType.FLOAT32:
            ret = float(value)
        elif type_ == DataType.FLOAT64:
            ret = float(value)
        elif type_ == DataType.INT16:
            ret = GXInt16(value)
        elif type_ == DataType.INT32:
            ret = GXInt32(value)
        elif type_ == DataType.INT64:
            ret = GXInt64(value)
        elif type_ == DataType.INT8:
            ret = GXInt8(value)
        elif type_ == DataType.NONE:
            ret = None
        elif type_ == DataType.OCTET_STRING:
            if isinstance(value, str):
                ret = GXByteBuffer.hexToBytes(str(value))
            else:
                raise ValueError("Can't change octect string type.")
        elif type_ == DataType.STRING:
            ret = str(value)
        elif type_ == DataType.BITSTRING:
            ret = str(value)
        elif type_ == DataType.STRING_UTF8:
            ret = str(value)
        elif type_ == DataType.STRUCTURE:
            raise ValueError("Can't change structure types.")
        elif type_ == DataType.TIME:
            ret = GXTime(value)
        elif type_ == DataType.UINT16:
            ret = GXUInt16(value)
        elif type_ == DataType.UINT32:
            ret = GXUInt32(value)
        elif type_ == DataType.UINT64:
            ret = GXUInt64(value)
        elif type_ == DataType.UINT8:
            ret = GXUInt8(value)
        else:
            raise ValueError("Invalid data type.")
        return ret

    @classmethod
    def objectTypeToString(cls, ot):
        if ot == ObjectType.ACTION_SCHEDULE:
            ret = "ActionSchedule"
        elif ot == ObjectType.ACTIVITY_CALENDAR:
            ret = "ActivityCalendar"
        elif ot == ObjectType.ASSOCIATION_LOGICAL_NAME:
            ret = "AssociationLogicalName"
        elif ot == ObjectType.ASSOCIATION_SHORT_NAME:
            ret = "AssociationShortName"
        elif ot == ObjectType.AUTO_ANSWER:
            ret = "AutoAnswer"
        elif ot == ObjectType.AUTO_CONNECT:
            ret = "AutoConnect"
        elif ot == ObjectType.CLOCK:
            ret = "Clock"
        elif ot == ObjectType.DATA:
            ret = "Data"
        elif ot == ObjectType.DEMAND_REGISTER:
            ret = "DemandRegister"
        elif ot == ObjectType.MAC_ADDRESS_SETUP:
            ret = "MacAddressSetup"
        elif ot == ObjectType.EXTENDED_REGISTER:
            ret = "ExtendedRegister"
        elif ot == ObjectType.GPRS_SETUP:
            ret = "GprsSetup"
        elif ot == ObjectType.SECURITY_SETUP:
            ret = "SecuritySetup"
        elif ot == ObjectType.IEC_HDLC_SETUP:
            ret = "IecHdlcSetup"
        elif ot == ObjectType.IEC_LOCAL_PORT_SETUP:
            ret = "IecLocalPortSetup"
        elif ot == ObjectType.IEC_TWISTED_PAIR_SETUP:
            ret = "IEC_TWISTED_PAIR_SETUP"
        elif ot == ObjectType.IP4_SETUP:
            ret = "Ip4Setup"
        elif ot == ObjectType.MBUS_SLAVE_PORT_SETUP:
            ret = "MBusSlavePortSetup"
        elif ot == ObjectType.IMAGE_TRANSFER:
            ret = "ImageTransfer"
        elif ot == ObjectType.DISCONNECT_CONTROL:
            ret = "DisconnectControl"
        elif ot == ObjectType.LIMITER:
            ret = "Limiter"
        elif ot == ObjectType.MBUS_CLIENT:
            ret = "MBusClient"
        elif ot == ObjectType.MODEM_CONFIGURATION:
            ret = "ModemConfiguration"
        elif ot == ObjectType.PPP_SETUP:
            ret = "PppSetup"
        elif ot == ObjectType.PROFILE_GENERIC:
            ret = "ProfileGeneric"
        elif ot == ObjectType.REGISTER:
            ret = "Register"
        elif ot == ObjectType.REGISTER_ACTIVATION:
            ret = "RegisterActivation"
        elif ot == ObjectType.REGISTER_MONITOR:
            ret = "RegisterMonitor"
        elif ot == ObjectType.REGISTER_TABLE:
            ret = "RegisterTable"
        elif ot == ObjectType.ZIG_BEE_SAS_STARTUP:
            ret = "ZigBeeSasStartup"
        elif ot == ObjectType.ZIG_BEE_SAS_JOIN:
            ret = "ZigBeeSasJoin"
        elif ot == ObjectType.ZIG_BEE_SAS_APS_FRAGMENTATION:
            ret = "ZigBeeSasApsFragmentation"
        elif ot == ObjectType.ZIG_BEE_NETWORK_CONTROL:
            ret = "ZigBeeNetworkControl"
        elif ot == ObjectType.SAP_ASSIGNMENT:
            ret = "SapAssignment"
        elif ot == ObjectType.SCHEDULE:
            ret = "Schedule"
        elif ot == ObjectType.SCRIPT_TABLE:
            ret = "ScriptTable"
        elif ot == ObjectType.SMTP_SETUP:
            ret = "SMTPSetup"
        elif ot == ObjectType.SPECIAL_DAYS_TABLE:
            ret = "SpecialDaysTable"
        elif ot == ObjectType.STATUS_MAPPING:
            ret = "StatusMapping"
        elif ot == ObjectType.TCP_UDP_SETUP:
            ret = "TcpUdpSetup"
        elif ot == ObjectType.UTILITY_TABLES:
            ret = "UtilityTables"
        elif ot == ObjectType.MBUS_MASTER_PORT_SETUP:
            ret = "MBusMasterPortSetup"
        elif ot == ObjectType.PUSH_SETUP:
            ret = "PushSetup"
        elif ot == ObjectType.ACCOUNT:
            ret = "Account"
        elif ot == ObjectType.CREDIT:
            ret = "Credit"
        elif ot == ObjectType.CHARGE:
            ret = "Charge"
        elif ot == ObjectType.PARAMETER_MONITOR:
            ret = "ParameterMonitor"
        elif ot == ObjectType.TOKEN_GATEWAY:
            ret = "Token"
        elif ot == ObjectType.GSM_DIAGNOSTIC:
            ret = "GSMDiagnostic"
        elif ot == ObjectType.COMPACT_DATA:
            ret = "CompactData"
        elif ot == ObjectType.IP6_SETUP:
            ret = "Ip6Setup"
        elif ot == ObjectType.LLC_SSCS_SETUP:
            ret = "LlcSscsSetup"
        elif ot == ObjectType.PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS:
            ret = "PrimeNbOfdmPlcPhysicalLayerCounters"
        elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_SETUP:
            ret = "PrimeNbOfdmPlcMacSetup"
        elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS:
            ret = "PrimeNbOfdmPlcMacFunctionalParameters"
        elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_COUNTERS:
            ret = "PrimeNbOfdmPlcMacCounters"
        elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA:
            ret = "PrimeNbOfdmPlcMacNetworkAdministrationData"
        elif ot == ObjectType.PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION:
            ret = "PrimeNbOfdmPlcApplicationsIdentification"
        elif ot == ObjectType.NTP_SETUP:
            ret = "NtpSetup"
        elif ot == ObjectType.FUNCTION_CONTROL:
            ret = "FunctionControl"
        else:
            ret = "Manufacture spesific."
        return ret

    @classmethod
    def valueOfObjectType(cls, value):
        if value == "ActionSchedule":
            ot = ObjectType.ACTION_SCHEDULE
        elif value == "ActivityCalendar":
            ot = ObjectType.ACTIVITY_CALENDAR
        elif value == "AssociationLogicalName":
            ot = ObjectType.ASSOCIATION_LOGICAL_NAME
        elif value == "AssociationShortName":
            ot = ObjectType.ASSOCIATION_SHORT_NAME
        elif value == "AutoAnswer":
            ot = ObjectType.AUTO_ANSWER
        elif value == "AutoConnect":
            ot = ObjectType.AUTO_CONNECT
        elif value == "Clock":
            ot = ObjectType.CLOCK
        elif value == "Data":
            ot = ObjectType.DATA
        elif value == "DemandRegister":
            ot = ObjectType.DEMAND_REGISTER
        elif value == "MacAddressSetup":
            ot = ObjectType.MAC_ADDRESS_SETUP
        elif value == "ExtendedRegister":
            ot = ObjectType.EXTENDED_REGISTER
        elif value == "GprsSetup":
            ot = ObjectType.GPRS_SETUP
        elif value == "SecuritySetup":
            ot = ObjectType.SECURITY_SETUP
        elif value in ("IecHdlcSetup", "HdlcSetup"):
            ot = ObjectType.IEC_HDLC_SETUP
        elif value in ("IecLocalPortSetup", "IecOpticalPortSetup"):
            ot = ObjectType.IEC_LOCAL_PORT_SETUP
        elif value == "IEC_TWISTED_PAIR_SETUP":
            ot = ObjectType.IEC_TWISTED_PAIR_SETUP
        elif value == "Ip4Setup":
            ot = ObjectType.IP4_SETUP
        elif value == "MBusSlavePortSetup":
            ot = ObjectType.MBUS_SLAVE_PORT_SETUP
        elif value == "ImageTransfer":
            ot = ObjectType.IMAGE_TRANSFER
        elif value == "DisconnectControl":
            ot = ObjectType.DISCONNECT_CONTROL
        elif value == "Limiter":
            ot = ObjectType.LIMITER
        elif value == "MBusClient":
            ot = ObjectType.MBUS_CLIENT
        elif value == "ModemConfiguration":
            ot = ObjectType.MODEM_CONFIGURATION
        elif value == "PppSetup":
            ot = ObjectType.PPP_SETUP
        elif value == "ProfileGeneric":
            ot = ObjectType.PROFILE_GENERIC
        elif value == "Register":
            ot = ObjectType.REGISTER
        elif value == "RegisterActivation":
            ot = ObjectType.REGISTER_ACTIVATION
        elif value == "RegisterMonitor":
            ot = ObjectType.REGISTER_MONITOR
        elif value == "RegisterTable":
            ot = ObjectType.REGISTER_TABLE
        elif value == "ZigBeeSasStartup":
            ot = ObjectType.ZIG_BEE_SAS_STARTUP
        elif value == "ZigBeeSasJoin":
            ot = ObjectType.ZIG_BEE_SAS_JOIN
        elif value == "ZigBeeSasApsFragmentation":
            ot = ObjectType.ZIG_BEE_SAS_APS_FRAGMENTATION
        elif value == "ZigBeeNetworkControl":
            ot = ObjectType.ZIG_BEE_NETWORK_CONTROL
        elif value == "SapAssignment":
            ot = ObjectType.SAP_ASSIGNMENT
        elif value == "Schedule":
            ot = ObjectType.SCHEDULE
        elif value == "ScriptTable":
            ot = ObjectType.SCRIPT_TABLE
        elif value == "SMTPSetup":
            ot = ObjectType.SMTP_SETUP
        elif value == "SpecialDaysTable":
            ot = ObjectType.SPECIAL_DAYS_TABLE
        elif value == "StatusMapping":
            ot = ObjectType.STATUS_MAPPING
        elif value == "TcpUdpSetup":
            ot = ObjectType.TCP_UDP_SETUP
        elif value == "UtilityTables":
            ot = ObjectType.UTILITY_TABLES
        elif value == "MBusMasterPortSetup":
            ot = ObjectType.MBUS_MASTER_PORT_SETUP
        elif value == "PushSetup":
            ot = ObjectType.PUSH_SETUP
        elif value == "Account":
            ot = ObjectType.ACCOUNT
        elif value == "Credit":
            ot = ObjectType.CREDIT
        elif value == "Charge":
            ot = ObjectType.CHARGE
        elif value == "ParameterMonitor":
            ot = ObjectType.PARAMETER_MONITOR
        elif value in ("Token", "TokenGateway"):
            ot = ObjectType.TOKEN_GATEWAY
        elif value == "GSMDiagnostic":
            ot = ObjectType.GSM_DIAGNOSTIC
        elif value == "CompactData":
            ot = ObjectType.COMPACT_DATA
        elif value == "Ip6Setup":
            ot = ObjectType.IP6_SETUP
        elif value == "LlcSscsSetup":
            ot = ObjectType.LLC_SSCS_SETUP
        elif value == "PrimeNbOfdmPlcPhysicalLayerCounters":
            ot = ObjectType.PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS
        elif value == "PrimeNbOfdmPlcMacSetup":
            ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_SETUP
        elif value == "PrimeNbOfdmPlcMacFunctionalParameters":
            ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS
        elif value == "PrimeNbOfdmPlcMacCounters":
            ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_COUNTERS
        elif value == "PrimeNbOfdmPlcMacNetworkAdministrationData":
            ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA
        elif value == "PrimeNbOfdmPlcApplicationsIdentification":
            ot = ObjectType.PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION
        elif value == "NtpSetup":
            ot = ObjectType.NTP_SETUP
        elif value == "FunctionControl":
            ot = ObjectType.FUNCTION_CONTROL
        else:
            ot = ObjectType.NONE
        return ot
