# Copyright (C) Jan 2020 Mellanox Technologies Ltd. All rights reserved.
# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# This software is available to you under a choice of one of two
# licenses.  You may choose to be licensed under the terms of the GNU
# General Public License (GPL) Version 2, available from the file
# COPYING in the main directory of this source tree, or the
# OpenIB.org BSD license below:
#
#     Redistribution and use in source and binary forms, with or
#     without modification, are permitted provided that the following
#     conditions are met:
#
#      - Redistributions of source code must retain the above
#        copyright notice, this list of conditions and the following
#        disclaimer.
#
#      - Redistributions in binary form must reproduce the above
#        copyright notice, this list of conditions and the following
#        disclaimer in the documentation and/or other materials
#        provided with the distribution.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# --


#######################################################
#
# CResourceDump.py
# ctypes connection to C SDK
# Created on:      5-Sep-2023 8:43:00 AM
# Original author: astrutsovsky
#
#######################################################

# from tools_external_libs import get_external_libs_dir

import sys
import ctypes
import platform
from pathlib import Path
import re
from itertools import zip_longest
import subprocess
from . import cresourcedump_types
import os

# TODO: remove and replace with device_info, when it is fixed for mstflint
import dev_mgt


def exec_command(cmd_list):
    """
    Execute python command
    Return output in sucess, raise exception with error in failure
    """
    process = subprocess.Popen(
        cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = process.communicate()
    rc = process.returncode
    if stderr and isinstance(stderr, bytes):
        stderr = stderr.decode("utf-8")
    if stdout and isinstance(stdout, bytes):
        stdout = stdout.decode("utf-8")
    if rc != 0:
        raise RuntimeError(
            "Command '%s' has failed with error:\n%s" % (cmd_list, stderr))
    return stdout.strip()


class CResourceDump:
    MIN_OFED_VERSION = "5.6"
    _instance = None

    # Throw exception for SDK functions if the wrapping singleton is not initialized
    def uninitialized(*args, **kwargs):
        raise Exception("{} haven't been initialized".format(__class__.__name__))

    c_create_resource_dump = uninitialized
    c_destroy_resource_dump = uninitialized
    c_dump_resource_to_file = uninitialized
    c_get_resource_dump_error = uninitialized

    # class is a singleton
    def __new__(cls, device):
        if not cls._instance:
            cls._instance = super().__new__(cls)
            cls._device = device
            cls.init_sdk(cls)
        return cls._instance

    def CDLL(dist_lib_path, installer_lib_path, *args):
        try:
            # print("Openning: {}".format(dist_lib_path))
            return ctypes.CDLL(str(dist_lib_path), *args)
        except BaseException:
            # print("Openning: {}".format(installer_lib_path))
            return ctypes.CDLL(str(installer_lib_path), *args)

    def get_sdk_path(file_path):
        """This method will return the sdk directory path depends if enviroment variable MFTDEBUG is set"""

        if os.environ.get("MFTDEBUG", False):
            print("{:*^100}".format("MFT_DEBUG mode is ON"))
            return file_path.parents[1] / "src"
        else:
            return file_path.parents[4]

    def get_numeric_ofed_version():
        def is_valid_version(version):
            """A valid version contains digits, periods and dashes only."""
            return bool(re.match(r'^[\d.-]+$', version))
        try:
            ofed_info_output = exec_command(["ofed_info", "-n"])
            return ofed_info_output if is_valid_version(ofed_info_output) else "0"
        except FileNotFoundError:
            return "0"
        except RuntimeError:
            return "0"

    def is_ofed_up():
        """
        This method checks that OFED is up with '/etc/init.d/openibd status'.
        If ofed is up returns True.
        else False."""
        try:
            exec_command(["/etc/init.d/openibd", "status"])
        except FileNotFoundError:
            return False
        except RuntimeError:
            return False
        return True

    def version_compare(v1, v2):
        split_pattern = re.compile(r'[.-]')
        s1 = [int(x) for x in split_pattern.split(v1)]
        s2 = [int(x) for x in split_pattern.split(v2)]
        for n1, n2 in zip_longest(s1, s2, fillvalue=0):
            if n1 > n2:
                return 1
            elif n1 < n2:
                return -1
        return 0

    @staticmethod
    def init_sdk(cls):
        try:
            cls.c_resource_dump_sdk = None
            rd_sdk_lib_basename = "libresource_dump_sdk"
            so_suffix = ''
            if platform.system() != 'Windows':
                so_suffix = 'so'
                if platform.system() != 'VMkernel':
                    dev_info = dev_mgt.DevMgt(cls._device).getHwId()
                    if not dev_mgt.DevMgt.is_hca(dev_info["dm_device_id"]) or not cls.is_ofed_up() or cls.version_compare(cls.get_numeric_ofed_version(), cls.MIN_OFED_VERSION) < 0:
                        rd_sdk_lib_basename += "_no_ofed"
                cls.c_resource_dump_sdk = ctypes.CDLL(str(cls.get_sdk_path(Path(__file__).resolve()) / "sdk" / "{}.{}".format(rd_sdk_lib_basename, so_suffix)), ctypes.RTLD_GLOBAL)
            else:
                mtcr_lib_basename = "libmtcr-1"
                so_suffix = 'dll'
                cls.CDLL(Path(".") / "{}.{}".format(mtcr_lib_basename, so_suffix), Path("..") / "{}.{}".format(mtcr_lib_basename, so_suffix), ctypes.RTLD_GLOBAL)
                cls.c_resource_dump_sdk = cls.CDLL(Path(".") / "{}.{}".format(rd_sdk_lib_basename, so_suffix), Path("..") / "{}.{}".format(rd_sdk_lib_basename, so_suffix), ctypes.RTLD_GLOBAL)
        except OSError as ose:
            print("Error: Failed loading shared-library - {0}. Exiting...".format(ose))
            sys.exit(1)

        cls.c_create_resource_dump = cls.c_resource_dump_sdk.create_resource_dump
        cls.c_create_resource_dump.restype = ctypes.c_uint16
        cls.c_create_resource_dump.argtypes = [
            cresourcedump_types.c_device_attributes,
            cresourcedump_types.c_dump_request,
            ctypes.POINTER(cresourcedump_types.c_resource_dump_data),
            ctypes.c_uint32
        ]

        cls.c_destroy_resource_dump = cls.c_resource_dump_sdk.destroy_resource_dump
        cls.c_destroy_resource_dump.restype = None
        cls.c_destroy_resource_dump.argtypes = [
            cresourcedump_types.c_resource_dump_data
        ]

        cls.c_dump_resource_to_file = cls.c_resource_dump_sdk.dump_resource_to_file
        cls.c_dump_resource_to_file.restype = ctypes.c_uint16
        cls.c_dump_resource_to_file.argtypes = [
            cresourcedump_types.c_device_attributes,
            cresourcedump_types.c_dump_request,
            ctypes.c_uint32,
            ctypes.c_char_p,
            ctypes.c_uint8
        ]

        cls.c_get_resource_dump_error = cls.c_resource_dump_sdk.get_resource_dump_error
        cls.c_get_resource_dump_error.restype = ctypes.c_char_p
        cls.c_get_resource_dump_error.argtypes = []
