# Copyright (C) Jan 2020 Mellanox Technologies Ltd. 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 segments.SegmentCreator import SegmentCreator
from utils import constants
from utils import constants as cs
import sys
import os
import math

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

import mtcr  # noqa
import regaccess  # noqa


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):
        self._sequence_number = ResourceDumpFetcher._sequence_incrementor()
        self._device_name = device_name
        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))

    @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)
        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"]))

            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 _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'
        """

        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

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

        while call_res_dump:
            results = self.reg_access_obj.sendResDump(segment,  # "segment_type"
                                                      seg_number,  # "seq_num"  * need check
                                                      1,  # "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" *
                                                      0,  # "mkey" 0
                                                      0,  # "size" 0
                                                      0)  # "address" 0

            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))

            inline_data.extend(results["inline_data"][:size])
            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))
                call_res_dump = False

            seg_number = results["seq_num"]
            self._start_seq_number = seg_number

            if more_dump == 0:
                call_res_dump = False

        return inline_data
