#
# Copyright (c) 2019-2021 NVIDIA CORPORATION & AFFILIATES.
# Apache-2.0
#
# 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.
#
#############################################################################
# Mellanox
#
# Module contains an implementation of SONiC Platform Base API and
# provides the eeprom information which are available in the platform
#
#############################################################################
import os
import subprocess

from sonic_py_common.logger import Logger
try:
    from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo
except ImportError as e:
    raise ImportError (str(e) + "- required module not found")

from .device_data import DeviceDataManager
from .utils import default_return, is_host, wait_until

logger = Logger()

#
# this is mlnx-specific
# should this be moved to chassis.py or here, which better?
#
EEPROM_SYMLINK = "/var/run/hw-management/eeprom/vpd_info"
platform_name = DeviceDataManager.get_platform_name()
if platform_name and 'simx' in platform_name:
    if not os.path.exists(EEPROM_SYMLINK):
        if is_host():
            platform_path = os.path.join('/usr/share/sonic/device', platform_name)
        else:
            platform_path = '/usr/share/sonic/platform'
        if not os.path.exists(os.path.dirname(EEPROM_SYMLINK)):
            os.makedirs(os.path.dirname(EEPROM_SYMLINK))
        subprocess.check_call(['/usr/bin/xxd', '-r', '-p', 'syseeprom.hex', EEPROM_SYMLINK], cwd=platform_path)

WAIT_EEPROM_READY_SEC = 10


class Eeprom(eeprom_tlvinfo.TlvInfoDecoder):
    def __init__(self):
        if not wait_until(predict=os.path.exists, timeout=WAIT_EEPROM_READY_SEC, path=EEPROM_SYMLINK):
            logger.log_error("Nowhere to read syseeprom from! No symlink found")
            raise RuntimeError("No syseeprom symlink found")

        self.eeprom_path = EEPROM_SYMLINK
        super(Eeprom, self).__init__(self.eeprom_path, 0, '', True)
        self._eeprom_info_dict = None

    @default_return(return_value='Undefined.')
    def get_base_mac(self):
        """
        Retrieves the base MAC address for the chassis

        Returns:
            A string containing the MAC address in the format
            'XX:XX:XX:XX:XX:XX'
        """
        return self._get_eeprom_value(self._TLV_CODE_MAC_BASE)
        
    @default_return(return_value='Undefined.')
    def get_serial_number(self):
        """
        Retrieves the hardware serial number for the chassis

        Returns:
            A string containing the hardware serial number for this chassis.
        """
        return self._get_eeprom_value(self._TLV_CODE_SERIAL_NUMBER)

    @default_return(return_value='Undefined.')
    def get_product_name(self):
        """
        Retrieves the hardware product name for the chassis

        Returns:
            A string containing the hardware product name for this chassis.
        """
        return self._get_eeprom_value(self._TLV_CODE_PRODUCT_NAME)

    @default_return(return_value='Undefined.')
    def get_part_number(self):
        """
        Retrieves the hardware part number for the chassis

        Returns:
            A string containing the hardware part number for this chassis.
        """
        return self._get_eeprom_value(self._TLV_CODE_PART_NUMBER)

    @default_return({})
    def get_system_eeprom_info(self):
        """
        Retrieves the full content of system EEPROM information for the chassis

        Returns:
            A dictionary where keys are the type code defined in
            OCP ONIE TlvInfo EEPROM format and values are their corresponding
            values.
        """
        if self._eeprom_info_dict is None:
            self._eeprom_info_dict = {}
            # Try get from DB first
            db_initialized = self._redis_hget('EEPROM_INFO|State', 'Initialized')
            if db_initialized == '1':
                code = self._TLV_CODE_PRODUCT_NAME
                while code <= self._TLV_CODE_SERVICE_TAG:
                    value = self._redis_hget('EEPROM_INFO|{}'.format(hex(code)), 'Value')
                    if value:
                        self._eeprom_info_dict[hex(code)] = value
                    code += 1

                # Handle vendor extension TLV
                vendor_extension_tlv_code = hex(self._TLV_CODE_VENDOR_EXT)
                try:
                    vendor_extension_num = int(self._redis_hget('EEPROM_INFO|{}'.format(vendor_extension_tlv_code), 'Num_vendor_ext'))
                except (ValueError, TypeError):
                    vendor_extension_num = 0

                if vendor_extension_num != 0:
                    for i in range(vendor_extension_num):
                        value = self._redis_hget('EEPROM_INFO|{}'.format(vendor_extension_tlv_code), 'Value_{}'.format(i))
                        if value:
                            if vendor_extension_tlv_code not in self._eeprom_info_dict:
                                self._eeprom_info_dict[vendor_extension_tlv_code] = [value]
                            else:
                                self._eeprom_info_dict[vendor_extension_tlv_code].append(value)
            
                # Get CRC 
                value = self._redis_hget('EEPROM_INFO|{}'.format(hex(self._TLV_CODE_CRC_32)), 'Value')
                if value:
                    self._eeprom_info_dict[hex(self._TLV_CODE_CRC_32)] = value
            else:
                eeprom = self.read_eeprom()
                visitor = EepromContentVisitor(self._eeprom_info_dict)
                self.visit_eeprom(eeprom, visitor)
        return self._eeprom_info_dict

    def _get_eeprom_value(self, code):
        """Helper function to help get EEPROM data by code

        Args:
            code (int): EEPROM TLV code

        Returns:
            str: value of EEPROM TLV
        """
        eeprom_info_dict = self.get_system_eeprom_info()
        return eeprom_info_dict[hex(code)]


class EepromContentVisitor(eeprom_tlvinfo.EepromDefaultVisitor):
    def __init__(self, content):
        self.content = content

    def visit_tlv(self, name, code, length, value):
        if code != Eeprom._TLV_CODE_VENDOR_EXT:
            self.content[hex(code)] = value.rstrip('\0')
        else:
            if value:
                value = value.rstrip('\0')
                if value:
                    code = hex(code)
                    if code not in self.content:
                        self.content[code] = [value]
                    else:
                        self.content[code].append(value)

