#
# Copyright 2017 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import structlog
from scapy.fields import ByteField, ThreeBytesField, StrFixedLenField, ConditionalField, IntField, Field
from scapy.fields import ShortField, BitField
from scapy.packet import Packet

from voltha.extensions.omci.omci_defs import AttributeAccess, OmciSectionDataSize
from voltha.extensions.omci.omci_fields import OmciTableField
import voltha.extensions.omci.omci_entities as omci_entities


log = structlog.get_logger()


class OmciData(Field):

    __slots__ = Field.__slots__ + ['_entity_class']

    def __init__(self, name, entity_class="entity_class"):
        Field.__init__(self, name=name, default=None, fmt='s')
        self._entity_class = entity_class

    def addfield(self, pkt, s, val):
        class_id = getattr(pkt, self._entity_class)
        entity_class = omci_entities.entity_id_to_class_map.get(class_id)
        for attribute in entity_class.attributes:
            if AttributeAccess.SetByCreate not in attribute.access:
                continue
            if attribute.field.name == 'managed_entity_id':
                continue
            fld = attribute.field
            s = fld.addfield(pkt, s, val.get(fld.name, fld.default))
        return s

    def getfield(self, pkt, s):
        """Extract an internal value from a string"""
        class_id = getattr(pkt, self._entity_class)
        entity_class = omci_entities.entity_id_to_class_map.get(class_id)
        data = {}
        for attribute in entity_class.attributes:
            if AttributeAccess.SetByCreate not in attribute.access:
                continue
            if attribute.field.name == 'managed_entity_id':
                continue
            fld = attribute.field
            s, value = fld.getfield(pkt, s)
            data[fld.name] = value
        return s, data


class OmciMaskedData(Field):

    __slots__ = Field.__slots__ + ['_entity_class', '_attributes_mask']

    def __init__(self, name, entity_class="entity_class",
                 attributes_mask="attributes_mask"):
        Field.__init__(self, name=name, default=None, fmt='s')
        self._entity_class = entity_class
        self._attributes_mask = attributes_mask

    def addfield(self, pkt, s, val):
        class_id = getattr(pkt, self._entity_class)
        attribute_mask = getattr(pkt, self._attributes_mask)
        entity_class = omci_entities.entity_id_to_class_map.get(class_id)
        indices = entity_class.attribute_indices_from_mask(attribute_mask)
        for index in indices:
            fld = entity_class.attributes[index].field
            s = fld.addfield(pkt, s, val[fld.name])
        return s

    def getfield(self, pkt, s):
        """Extract an internal value from a string"""
        class_id = getattr(pkt, self._entity_class)
        attribute_mask = getattr(pkt, self._attributes_mask)
        entity_class = omci_entities.entity_id_to_class_map[class_id]
        indices = entity_class.attribute_indices_from_mask(attribute_mask)
        data = {}
        table_attribute_mask = 0
        for index in indices:
            try:
                fld = entity_class.attributes[index].field
            except IndexError, e:
                log.error("attribute-decode-failure", attribute_index=index,
                          entity_class=entity_class, e=e)
                continue
            try:
                s, value = fld.getfield(pkt, s)
            except Exception, _e:
                raise
            if isinstance(pkt, OmciGetResponse) and isinstance(fld, OmciTableField):
                data[fld.name + '_size'] = value
                table_attribute_mask = table_attribute_mask | (1 << (15 - index))
            else:
                data[fld.name] = value
        if table_attribute_mask:
            data['table_attribute_mask'] = table_attribute_mask
        return s, data


class OmciMessage(Packet):
    name = "OmciMessage"
    message_id = None  # OMCI message_type value, filled by derived classes
    fields_desc = []


class OmciCreate(OmciMessage):
    name = "OmciCreate"
    message_id = 0x44
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        OmciData("data")
    ]


class OmciCreateResponse(OmciMessage):
    name = "OmciCreateResponse"
    message_id = 0x24
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", None),
        ByteField("success_code", 0),
        ShortField("parameter_error_attributes_mask", None),
    ]


class OmciDelete(OmciMessage):
    name = "OmciDelete"
    message_id = 0x46
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", None),
    ]


class OmciDeleteResponse(OmciMessage):
    name = "OmciDeleteResponse"
    message_id = 0x26
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", None),
        ByteField("success_code", 0),
    ]


class OmciSet(OmciMessage):
    name = "OmciSet"
    message_id = 0x48
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ShortField("attributes_mask", None),
        OmciMaskedData("data")
    ]


class OmciSetResponse(OmciMessage):
    name = "OmciSetResponse"
    message_id = 0x28
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", None),
        ByteField("success_code", 0),
        ShortField("unsupported_attributes_mask", None),
        ShortField("failed_attributes_mask", None),
    ]


class OmciGet(OmciMessage):
    name = "OmciGet"
    message_id = 0x49
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ShortField("attributes_mask", None)
    ]


class OmciGetResponse(OmciMessage):
    name = "OmciGetResponse"
    message_id = 0x29
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ByteField("success_code", 0),
        ShortField("attributes_mask", None),
        ConditionalField(
            OmciMaskedData("data"), lambda pkt: pkt.success_code == 0)
    ]


class OmciGetAllAlarms(OmciMessage):
    name = "OmciGetAllAlarms"
    message_id = 0x4b
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),  # Always 0 (ONT instance)
        ByteField("alarm_retrieval_mode", 0)  # 0 or 1
    ]


class OmciGetAllAlarmsResponse(OmciMessage):
    name = "OmciGetAllAlarmsResponse"
    message_id = 0x2b
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
        ShortField("number_of_commands", None)
    ]


class OmciGetAllAlarmsNext(OmciMessage):
    name = "OmciGetAllAlarmsNext"
    message_id = 0x4c
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
        ShortField("command_sequence_number", None)
    ]


class OmciGetAllAlarmsNextResponse(OmciMessage):
    name = "OmciGetAllAlarmsNextResponse"
    message_id = 0x2c
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
        ShortField("alarmed_entity_class", None),
        ShortField("alarmed_entity_id", 0),
        BitField("alarm_bit_map", None, 224)
    ]


class OmciMibUpload(OmciMessage):
    name = "OmciMibUpload"
    message_id = 0x4d
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
    ]


class OmciMibUploadResponse(OmciMessage):
    name = "OmciMibUploadResponse"
    message_id = 0x2d
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
        ShortField("number_of_commands", None)
    ]


class OmciMibUploadNext(OmciMessage):
    name = "OmciMibUploadNext"
    message_id = 0x4e
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
        ShortField("command_sequence_number", None)
    ]


class OmciMibUploadNextResponse(OmciMessage):
    name = "OmciMibUploadNextResponse"
    message_id = 0x2e
    fields_desc = [
        ShortField("entity_class", 2),  # Always 2 (ONT data)
        ShortField("entity_id", 0),
        ShortField("object_entity_class", None),
        ShortField("object_entity_id", 0),
        ShortField("object_attributes_mask", None),
        OmciMaskedData("object_data", entity_class='object_entity_class',
                       attributes_mask='object_attributes_mask')
    ]


class OmciMibReset(OmciMessage):
    name = "OmciMibReset"
    message_id = 0x4f
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0)
    ]


class OmciMibResetResponse(OmciMessage):
    name = "OmciMibResetResponse"
    message_id = 0x2f
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ByteField("success_code", 0)
    ]


class OmciAlarmNotification(OmciMessage):
    name = "AlarmNotification"
    message_id = 0x10
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        BitField("alarm_bit_map", 0, 224),
        ThreeBytesField("zero_padding", 0),
        ByteField("alarm_sequence_number", None)
    ]


class OmciAttributeValueChange(OmciMessage):
    name = "AttributeValueChange"
    message_id = 0x11
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ShortField("attributes_mask", None),
        OmciMaskedData("data")
    ]


class OmciTestResult(OmciMessage):
    name = "TestResult"
    message_id = 0x1B
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0)
        # ME Test specific message contents starts here
        # TODO: Can this be coded easily with scapy?
    ]


class OmciReboot(OmciMessage):
    name = "OmciOnuReboot"
    message_id = 0x59
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ByteField("reboot_code", 0)
    ]


class OmciRebootResponse(OmciMessage):
    name = "OmciOnuRebootResponse"
    message_id = 0x39
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ByteField("success_code", 0)
    ]


class OmciGetNext(OmciMessage):
    name = "OmciGetNext"
    message_id = 0x5A
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ShortField("attributes_mask", None),
        ShortField("command_sequence_number", None)
    ]


class OmciGetNextResponse(OmciMessage):
    name = "OmciGetNextResponse"
    message_id = 0x3A
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ByteField("success_code", 0),
        ShortField("attributes_mask", None),
        ConditionalField(OmciMaskedData("data"),
                         lambda pkt: pkt.success_code == 0)
    ]


class OmciSynchronizeTime(OmciMessage):
    name = "OmciSynchronizeTime"
    message_id = 0x58
    fields_desc = [
        ShortField("entity_class", 256),  # OntG
        ShortField("entity_id", 0),
        ShortField("year", 0),       # eg) 2018
        ByteField("month", 0),       # 1..12
        ByteField("day", 0),         # 1..31
        ByteField("hour", 0),        # 0..23
        ByteField("minute", 0),      # 0..59
        ByteField("second", 0)       # 0..59
    ]


class OmciSynchronizeTimeResponse(OmciMessage):
    name = "OmciSynchronizeTimeResponse"
    message_id = 0x38
    fields_desc = [
        ShortField("entity_class", 256),  # OntG
        ShortField("entity_id", 0),
        ByteField("success_code", 0),
        ConditionalField(ShortField("success_info", None),
                         lambda pkt: pkt.success_code == 0)
    ]


class OmciGetCurrentData(OmciMessage):
    name = "OmciGetCurrentData"
    message_id = 0x5C
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ShortField("attributes_mask", None),
    ]


class OmciGetCurrentDataResponse(OmciMessage):
    name = "OmciGetCurrentDataResponse"
    message_id = 0x3C
    fields_desc = [
        ShortField("entity_class", None),
        ShortField("entity_id", 0),
        ByteField("success_code", 0),
        ShortField("attributes_mask", None),
        ShortField("unsupported_attributes_mask", None),
        ShortField("failed_attributes_mask", None),
        ConditionalField(
            OmciMaskedData("data"), lambda pkt: pkt.success_code == 0)
    ]

class OmciStartSoftwareDownload(OmciMessage):
    name = "OmciStartSoftwareDownload"
    message_id = 0x53
    fields_desc = [
        ShortField("entity_class", 7),  # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("window_size", 0),
        IntField("image_size", 0),
        ByteField("image_number", 1),   # Always only 1 in parallel
        ShortField("instance_id", None) # should be same as "entity_id"        
    ]

class OmciStartSoftwareDownloadResponse(OmciMessage):
    name = "OmciStartSoftwareDownloadResponse"
    message_id = 0x33
    fields_desc = [
        ShortField("entity_class", 7),  # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("result", 0),
        ByteField("window_size", 0),
        ByteField("image_number", 1),   # Always only 1 in parallel
        ShortField("instance_id", None) # should be same as "entity_id"        
    ]

class OmciEndSoftwareDownload(OmciMessage):
    name = "OmciEndSoftwareDownload"
    message_id = 0x55
    fields_desc = [
        ShortField("entity_class", 7),  # Always 7 (Software image)
        ShortField("entity_id", None),
        IntField("crc32", 0),
        IntField("image_size", 0),
        ByteField("image_number", 1),   # Always only 1 in parallel
        ShortField("instance_id", None),# should be same as "entity_id"
    ]

class OmciEndSoftwareDownloadResponse(OmciMessage):
    name = "OmciEndSoftwareDownload"
    message_id = 0x35
    fields_desc = [
        ShortField("entity_class", 7),  # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("result", 0),
        ByteField("image_number", 1),   # Always only 1 in parallel
        ShortField("instance_id", None),# should be same as "entity_id"
        ByteField("result0", 0)         # same as result 
    ]

class OmciDownloadSection(OmciMessage):
    name = "OmciDownloadSection"
    message_id = 0x14
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("section_number", 0),  # Always only 1 in parallel
        StrFixedLenField("data", 0, length=OmciSectionDataSize) # section data
    ]

class OmciDownloadSectionLast(OmciMessage):
    name = "OmciDownloadSection"
    message_id = 0x54
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("section_number", 0),  # Always only 1 in parallel
        StrFixedLenField("data", 0, length=OmciSectionDataSize) # section data
    ]

class OmciDownloadSectionResponse(OmciMessage):
    name = "OmciDownloadSectionResponse"
    message_id = 0x34
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("result", 0),
        ByteField("section_number", 0),  # Always only 1 in parallel
    ]

class OmciActivateImage(OmciMessage):
    name = "OmciActivateImage"
    message_id = 0x56
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("activate_flag", 0)    # Activate image unconditionally
    ]

class OmciActivateImageResponse(OmciMessage):
    name = "OmciActivateImageResponse"
    message_id = 0x36
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("result", 0)           # Activate image unconditionally
    ]

class OmciCommitImage(OmciMessage):
    name = "OmciCommitImage"
    message_id = 0x57
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
    ]

class OmciCommitImageResponse(OmciMessage):
    name = "OmciCommitImageResponse"
    message_id = 0x37
    fields_desc = [
        ShortField("entity_class", 7),   # Always 7 (Software image)
        ShortField("entity_id", None),
        ByteField("result", 0)           # Activate image unconditionally
    ]
    
