# 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.
# --


#######################################################
#
# ResourceDumpFetcher.py
# Python implementation of the Class CoreDumpFetcher
# Generated by Enterprise Architect
# Created on:      14-Aug-2019 10:12:01 AM
# Original author: talve
#
#######################################################

from codecs import utf_16_be_decode
from resource_data.DataPrinter import DataPrinter
from segments.SegmentCreator import SegmentCreator
from utils import constants
from utils import constants as cs
import sys
import os
import math
import struct
import errno
import ctypes
import platform

sys.path.append(os.path.join("mtcr_py"))
sys.path.append(os.path.join("reg_access"))

import mtcr  # noqa
import regaccess  # noqa


class MKEY_PARAMS_ST(ctypes.Structure):
    _fields_ = [("lkey", ctypes.c_uint32),
                ("umem_addr", ctypes.c_uint64)]


class EXTRACTBITS_PARAMS_ST(ctypes.Structure):
    _fields_ = [("val", ctypes.c_uint32)]


class ResourceDumpFetcher:
    """this class is responsible for getting all the segments of a the required dump.
    """
    # very big number that represent the inf number 2^32 - 1 (we will not reach that number)
    INF_DEPTH = 4294967295

    def __init__(self, device_name, bin_file=None):

        self._bin_file_h = None
        self._sequence_number = ResourceDumpFetcher._sequence_incrementor()
        self._device_name = device_name
        self._bin_file = bin_file
        self._start_seq_number = 0
        try:
            mst_device = mtcr.MstDevice(self._device_name)
            if mst_device.is_cable() or mst_device.is_linkx():
                raise Exception("Device is not supported")
            self.reg_access_obj = regaccess.RegAccess(mst_device)
        except Exception as e:
            raise Exception("{0}".format(e))

        if self._bin_file:
            try:
                self._bin_file_h = open(self._bin_file, "wb")
            except Exception as e:
                raise Exception("Failed to create binary file, {0}".format(e))

    def __del__(self):
        if self._bin_file_h:
            self._bin_file_h.close()

    @staticmethod
    def _create_segments(segments_data):
        """convert the data into a list of segments by calling the SegmentCreator.
        """
        return SegmentCreator().create(segments_data)

    def fetch_data(self, **kwargs):
        """this method fetch the segments of the required dump by:
           1. read the core dump register from the reg access while more dump bit is
        equal to  "1" and the sequence number is valid.
           2. use SegmentsCreator for converting it to a list of segments.
           3. iterate stage 1 and 2 according the depth parameter and in case reference
        segments found.
        """
        # read the inline data from the resource dump register and split it to segments list
        self._start_seq_number = 0
        inline_data = self._retrieve_resource_dump_inline_data(kwargs[cs.UI_ARG_SEGMENT], **kwargs)
        if kwargs["fast_mode"]:
            return []

        segments_list = self._create_segments(inline_data)

        segments_list_last_position = 0
        # go over oll the segments from the last position till the and of the segments list
        # and repeat it according the depth parameter
        if kwargs[cs.UI_ARG_DEPTH] == "inf":
            depth = ResourceDumpFetcher.INF_DEPTH
        else:
            depth = int(kwargs[cs.UI_ARG_DEPTH]) if kwargs[cs.UI_ARG_DEPTH] else 0
        for i in range(depth):
            inner_inline_data = []
            for seg in segments_list[segments_list_last_position:]:
                if seg.get_type() == constants.RESOURCE_DUMP_SEGMENT_TYPE_REFERENCE:
                    inner_inline_data.extend(
                        self._retrieve_resource_dump_inline_data(seg.reference_type, index1=seg.index1,
                                                                 index2=seg.index2, numOfObj1=seg.num_of_obj1,
                                                                 numOfObj2=seg.num_of_obj2, vHCAid=kwargs["vHCAid"], mem=kwargs["mem"], fast_mode=kwargs["fast_mode"]))
            segments_list_last_position = len(segments_list)
            segments_list.extend(self._create_segments(inner_inline_data))

            # relevant for inf mode, nothing to show, we can stop search for ref segments
            # because if refs found we expect that the segments list extend to do something
            if segments_list_last_position == len(segments_list):
                break

        return segments_list

    def _validate_sequence_number(self, current_seq_number):
        """validate that the sequence number was incremented.
        """
        return current_seq_number == next(self._sequence_number)

    @staticmethod
    def _sequence_incrementor():
        cnt = 0
        while True:
            yield cnt % 16
            cnt += 1

    def initSourceDump(self):
        source_dump_lib = None
        try:
            mft_py_dir = os.path.dirname(os.path.dirname(__file__))
            source_dump_lib = ctypes.CDLL(os.path.join(mft_py_dir, 'resourcedump_lib/cresourcedump.so'), use_errno=True)
        except OSError:
            raise Exception("resourcedump_lib/cresourcedump.so loading failed")
        return source_dump_lib

    def _retrieve_resource_dump_inline_data(self, segment_type, **kwargs):
        """call the resource dump access register and retrieve the inline data
        till more dump is '0'
        """
        global SCAPY_SUPPORT
        segment = int(segment_type, 16)
        seg_number = self._start_seq_number
        more_dump = 0
        vhca_id_valid = 0
        index1 = int(kwargs["index1"]) if kwargs["index1"] else 0
        index2 = int(kwargs["index2"]) if kwargs["index2"] else 0
        num_of_obj_1 = int(kwargs["numOfObj1"]) if kwargs["numOfObj1"] else 0
        num_of_obj_2 = int(kwargs["numOfObj2"]) if kwargs["numOfObj2"] else 0
        device_opaque = 0
        inline_data = []
        call_res_dump = True
        inline_dump = 1
        mkey = 0
        buf_size = 0
        address = 0
        params_st = ctypes.pointer(MKEY_PARAMS_ST())
        extract_params_st = ctypes.pointer(EXTRACTBITS_PARAMS_ST())

        if kwargs["vHCAid"] is None:
            vhca_id = 0
        else:
            vhca_id = int(kwargs["vHCAid"], 0)
            vhca_id_valid = 1

        if kwargs["mem"] is not None:
            source_dump_lib = None
            try:
                if(platform.system() != 'Linux'):
                    raise Exception("This feature supported only on Linux")
                source_dump_lib = self.initSourceDump()
                gen_lkey_ret = source_dump_lib.generate_lkey(ctypes.c_char_p(bytes(kwargs["mem"], "utf-8")), params_st)
                if gen_lkey_ret != 0:
                    raise Exception("Check the driver is up and the device's rdma name is correct")
                mkey = params_st.contents.lkey
            except Exception as e:
                raise(e)

            inline_dump = 0
            buf_size = cs.MKEY_BUF_SIZE
            address = params_st.contents.umem_addr

        while call_res_dump:

            try:
                results = self.reg_access_obj.sendResDump(segment,  # "segment_type"
                                                          seg_number,  # "seq_num"  * need check
                                                          inline_dump,  # "inline_dump"
                                                          more_dump,  # "more_dump" *
                                                          vhca_id,  # "vHCAid"
                                                          vhca_id_valid,  # "vHCAid_valid"
                                                          index1,  # "index_1" *
                                                          index2,  # "index_2" *
                                                          num_of_obj_2,  # "num_of_obj_2" *
                                                          num_of_obj_1,  # "num_of_obj_1" *
                                                          device_opaque,  # "device_opaque" *
                                                          mkey,  # "mkey"
                                                          buf_size,  # "size"
                                                          address)  # "address"
            except Exception as _:
                if _.args[0] != '':
                    raise Exception(_.args[0] + "\n Check the driver is up and the device's rdma name is correct.")

            more_dump = results["more_dump"]
            vhca_id = results["vhca_id"]
            index1 = results["index_1"]
            index2 = results["index_2"]
            num_of_obj_1 = results["num_of_obj_1"]
            num_of_obj_2 = results["num_of_obj_2"]
            device_opaque = results["device_opaque"]
            size = int(math.ceil(results["size"] / 4))
            bin_data = None

            if kwargs["mem"] is not None:
                if not kwargs["fast_mode"] or segment_type == cs.RESOURCE_DUMP_SEGMENT_TYPE_MENU:
                    # Read all dwords and change endianness
                    for i in range(size):
                        ret = source_dump_lib.bit_extracted_32(ctypes.c_uint64(address), i, extract_params_st)
                        if(ret == 0):
                            inline_data.append(extract_params_st.contents.val)
                        else:
                            raise Exception("Extracting bits from c code failed!")
                else:
                    bin_data = bytes(results["size"])
                    ret = source_dump_lib.extrac_all(ctypes.c_uint64(address), ctypes.c_int32(results["size"]), bin_data)
            else:
                if not kwargs["fast_mode"] or segment_type == cs.RESOURCE_DUMP_SEGMENT_TYPE_MENU:
                    inline_data.extend(results["inline_data"][:size])
                else:
                    bin_data = struct.pack(">{}I".format(size), *results["inline_data"][:size])
            if bin_data:
                DataPrinter.append_binary_data_to_file(bin_data, self._bin_file_h)

            if not self._validate_sequence_number(seg_number):
                raise Exception("E - wrong sequence number while calling resource dump register with seq num = {0}"
                                .format(seg_number))
            seg_number = results["seq_num"]
            self._start_seq_number = seg_number
            if more_dump == 0:
                call_res_dump = False

        return inline_data
