#!/usr/bin/python3

# Copyright (c) 2023 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.


#######################################################
#
# MlxResDump.py
# Python implementation of the Class MlxResDump
# Generated by Enterprise Architect
# Created on:      14-Aug-2019 10:11:58 AM
# Original author: talve
#
#######################################################
import tools_version
import sys
import os
import argparse

if sys.version_info[0] < 3:
    print("Error: This tool supports python 3.x only. Exiting...")
    exit(1)

from resourcedump_lib.commands.CommandFactory import CommandFactory
from resourcedump_lib.utils import constants as cs
from mstresourceparse import ResourceParse
from resourceparse_lib.parsers.RawParser import RawParser
from resourceparse_lib.parsers.MenuParser import MenuParser
from resourceparse_lib.ResourceParseManager import ResourceParseManager
from resourceparse_lib.utils.Exceptions import ResourceParseException

sys.path.append(os.path.join("common"))


class MlxResDump:
    """This class is responsible for the resource dump UI by handling the user inputs and
       and running the right command.
    """

    tool_name = os.path.basename(__file__).split('.')[0]

    @staticmethod
    def _decimal_hex_to_str_hex(inp):
        """This method check if the string input is hex or decimal.
           and convert it to hex number.
           in case that the input is not a number, the method will
           return the input as is (str).
        """
        try:
            return int(inp, 0)
        except Exception as _:
            return inp

    @staticmethod
    def _num_of_objs_check(inp):
        """This method check if the num of objects parameter is valid
        """
        if inp in (['all', 'active']):
            if inp == 'all':
                inp = cs.NUM_OF_OBJ_ALL
            else:
                inp = cs.NUM_OF_OBJ_ACTIVE
            return inp
        else:
            try:
                return int(inp, 0)
            except Exception as _:
                msg = "numOfObj accepts the following values: ['all', 'active', number]"
                raise argparse.ArgumentTypeError(msg)

    @staticmethod
    def _depth_check(inp):
        """This method check if the num depth parameter is valid
        """
        if inp == "inf":
            return cs.INF_DEPTH
        else:
            try:
                return int(inp, 0)
            except Exception as _:
                msg = "depth accepts the following values: ['inf', number]"
                raise argparse.ArgumentTypeError(msg)

    def parse_resourcedump_args(self):
        # main parser
        arg_parser = argparse.ArgumentParser(prog=self.tool_name,
                                             epilog="Use '{0} <command> -h' to read about a specific command.".format(self.tool_name),
                                             formatter_class=argparse.RawTextHelpFormatter)
        arg_parser.add_argument('-v', '--version', action='version', help='Shows tool version',
                                version=tools_version.GetVersionString(self.tool_name, None))

        # commands sub parser
        commands = arg_parser.add_subparsers(title='commands')

        # dump sub parser
        dump_args_description = """This command is used for retrieving resources from a device.

Dump Arguments:
    These arguments control the dump from the device, the type of request and the manner it retrieved.
"""
        dump_arg_parser = commands.add_parser(cs.RESOURCE_DUMP_COMMAND_TYPE_DUMP, description=dump_args_description, formatter_class=argparse.RawDescriptionHelpFormatter)
        dump_arg_parser.set_defaults(command=cs.RESOURCE_DUMP_COMMAND_TYPE_DUMP)

        dump_required_args = dump_arg_parser.add_argument_group('required arguments')
        dump_required_args.add_argument("-d", "--device", help='The device name', required=True)
        dump_required_args.add_argument("-s", "--segment", help='The segment to dump', required=True, type=self._decimal_hex_to_str_hex)

        dump_optional_args = dump_arg_parser.add_argument_group('optional arguments')
        dump_optional_args.add_argument("-v", "--virtual-hca-id", dest="vHCAid", help='The virtual HCA (host channel adapter, NIC) ID', type=lambda x: int(x, 0), default=cs.DEFAULT_VHCA)
        dump_optional_args.add_argument("-i1", "--index1", help='The first context index to dump (if supported for this segment)', type=lambda x: int(x, 0), default=0)
        dump_optional_args.add_argument("-i2", "--index2", help='The second context index to dump (if supported for this segment)', type=lambda x: int(x, 0), default=0)
        dump_optional_args.add_argument("-n1", "--num-of-obj1", dest="numOfObj1", help='The number of objects to be dumped (if supported for this segment). accepts: ["all", "active", number, depends on the capabilities]',
                                        type=self._num_of_objs_check, default=0)
        dump_optional_args.add_argument("-n2", "--num-of-obj2", dest="numOfObj2", help='The number of objects to be dumped (if supported for this segment). accepts: ["all", "active", number, depends on the capabilities]',
                                        type=self._num_of_objs_check, default=0)
        dump_optional_args.add_argument("-de", "--depth",
                                        help='The depth of walking through reference segments. 0 stands for flat, '
                                        '1 allows crawling of a single layer down the struct, etc. "inf" for all',
                                        type=self._depth_check, default=0)
        dump_optional_args.add_argument("-b", "--bin",
                                        help='The output to a binary file that replaces the default print in hexadecimal,'
                                        ' a readable format')
        dump_optional_args.add_argument("-m", "--mem", metavar="RDMA_NAME", nargs="?", const=cs.DEVICE_RDMA, default="",
                                        help='Perform the dump through memory (ofed with rdma-core dependency). optionally accepts: [rdma device (for example "mlx5_4")]')

        resourceparse_help, resource_parse_usage = ResourceParse.get_help(arg_parser.prog)

        resourceparse_description = """Parse Arguments:
    These arguments control the parsing of the dump and the format of the output.
    There are several methods of parsing (see "Parse Methods").

""" + resource_parse_usage.replace(arg_parser.prog, arg_parser.prog + " ...")

        help_parser_usage = dump_arg_parser.format_usage().rstrip() + " [PARSE_ARGUMENTS]\n"
        help_parser_help = help_parser_usage + "\n".join(dump_arg_parser.format_help().split("\n")[1:]) + "\n"
        help_parser_help += resourceparse_description + resourceparse_help

        dump_arg_parser.format_help = lambda: help_parser_help

        # query sub parser
        query_description = """This command is used for retrieving the menu of all available reources of a device."""
        query_parser = commands.add_parser(cs.RESOURCE_DUMP_COMMAND_TYPE_QUERY, description=query_description)
        query_parser.set_defaults(command=cs.RESOURCE_DUMP_COMMAND_TYPE_QUERY)

        query_parser.add_argument("-v", "--virtual-hca-id", dest="vHCAid", help='The virtual HCA (host channel adapter, NIC) ID', type=lambda x: int(x, 0), default=cs.DEFAULT_VHCA)
        query_parser.add_argument("-m", "--mem", metavar="RDMA_NAME", nargs="?", const=cs.DEVICE_RDMA, default="",
                                  help='Perform the dump through memory (ofed with rdma-core dependency). optionally accepts: [rdma device (for example "mlx5_4")]')
        query_parser.set_defaults(resource_parser=MenuParser.PARSER_TYPE, bin=None)

        # required arguments by query sub parser
        query_required_args = query_parser.add_argument_group('required arguments')
        query_required_args.add_argument("-d", "--device", help='The device name', required=True)

        arguments, resourceparse_argv = arg_parser.parse_known_args()

        if len(sys.argv) < 2:
            arg_parser.print_usage()
            arg_parser.exit(1)

        if arguments.command == cs.RESOURCE_DUMP_COMMAND_TYPE_DUMP:
            if arguments.bin:
                resourceparse_argv += ["-d", arguments.bin]
            else:
                resourceparse_argv += ["--segments_provided"]
        elif arguments.command == cs.RESOURCE_DUMP_COMMAND_TYPE_QUERY:
            resourceparse_argv += ["-p", "menu"]
            resourceparse_argv += ["--segments_provided"]

        return arguments, resourceparse_argv

    def parse_resourceparse_args(self, resourceparse_argv):
        parse_manager_args, resource_parser_args = ResourceParse.run_arg_parse(resourceparse_argv, self.tool_name)
        return parse_manager_args, resource_parser_args


def create_command(arguments):
    """This method creates the right command.
    """
    try:
        command_type = arguments.command
        created_command = CommandFactory.create(command_type, **vars(arguments))
        return created_command
    except Exception as exp:
        raise Exception("failed to create command of type: {0} with exception: {1}".format(command_type, exp))


if __name__ == '__main__':
    try:
        dump_args, resourceparse_argv = MlxResDump().parse_resourcedump_args()
        print("{:#^100}".format(" Dump started "))
        command = create_command(dump_args)
        command.execute()
        print("{:#^100}".format(" Dump finished successfully "))
    except Exception as e:
        print("Error: {0}. Exiting...".format(e))
        sys.exit(1)

    try:
        parse_manager_args, resource_parser_args = MlxResDump().parse_resourceparse_args(resourceparse_argv)
        parse_data = not (parse_manager_args.resource_parser is RawParser and dump_args.bin)
        if parse_data:
            print("{:#^100}".format(" Parse started "))
            segments = command.get_segments(parse_manager_args.resource_parser is not RawParser) if not dump_args.bin else None
            parse_manager = ResourceParseManager(parse_manager_args, resource_parser_args, segments)
            parse_manager.parse()
            print("{:#^100}".format(" Parse finished successfully "))

    except ResourceParseException as rpe:
        print("ResourceParse failed!\n{0}.\nExiting...".format(rpe))
        sys.exit(1)
    except Exception as e:
        print("FATAL ERROR!\n{0}.\nExiting...".format(e))
        sys.exit(1)
