#!/usr/bin/env python

# ---------------------------------------------------------------------
# Be sure to add the python path that points to the LLDB shared library.
#
# # To use this in the embedded python interpreter using "lldb" just
# import it with the full path using the "command script import"
# command
#   (lldb) command script import /path/to/cmdtemplate.py
# ---------------------------------------------------------------------

from __future__ import print_function

import inspect
import lldb
import optparse
import shlex
import sys


class FrameStatCommand:
    program = 'framestats'

    @classmethod
    def register_lldb_command(cls, debugger, module_name):
        parser = cls.create_options()
        cls.__doc__ = parser.format_help()
        # Add any commands contained in this module to LLDB
        command = 'command script add -c %s.%s %s' % (module_name,
                                                      cls.__name__,
                                                      cls.program)
        debugger.HandleCommand(command)
        print('The "{0}" command has been installed, type "help {0}" or "{0} '
              '--help" for detailed help.'.format(cls.program))

    @classmethod
    def create_options(cls):

        usage = "usage: %prog [options]"
        description = ('This command is meant to be an example of how to make '
                       'an LLDB command that does something useful, follows '
                       'best practices, and exploits the SB API. '
                       'Specifically, this command computes the aggregate '
                       'and average size of the variables in the current '
                       'frame and allows you to tweak exactly which variables '
                       'are to be accounted in the computation.')

        # Pass add_help_option = False, since this keeps the command in line
        #  with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        parser = optparse.OptionParser(
            description=description,
            prog=cls.program,
            usage=usage,
            add_help_option=False)

        parser.add_option(
            '-i',
            '--in-scope',
            action='store_true',
            dest='inscope',
            help='in_scope_only = True',
            default=True)

        parser.add_option(
            '-a',
            '--arguments',
            action='store_true',
            dest='arguments',
            help='arguments = True',
            default=True)

        parser.add_option(
            '-l',
            '--locals',
            action='store_true',
            dest='locals',
            help='locals = True',
            default=True)

        parser.add_option(
            '-s',
            '--statics',
            action='store_true',
            dest='statics',
            help='statics = True',
            default=True)

        return parser

    def get_short_help(self):
        return "Example command for use in debugging"

    def get_long_help(self):
        return self.help_string

    def __init__(self, debugger, unused):
        self.parser = self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, args) = self.parser.parse_args(command_args)
        except:
            # if you don't handle exceptions, passing an incorrect argument to
            # the OptionParser will cause LLDB to exit (courtesy of OptParse
            # dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        # Always get program state from the lldb.SBExecutionContext passed
        # in as exe_ctx
        frame = exe_ctx.GetFrame()
        if not frame.IsValid():
            result.SetError("invalid frame")
            return

        variables_list = frame.GetVariables(
            options.arguments,
            options.locals,
            options.statics,
            options.inscope)
        variables_count = variables_list.GetSize()
        if variables_count == 0:
            print("no variables here", file=result)
            return
        total_size = 0
        for i in range(0, variables_count):
            variable = variables_list.GetValueAtIndex(i)
            variable_type = variable.GetType()
            total_size = total_size + variable_type.GetByteSize()
            average_size = float(total_size) / variables_count
            print("Your frame has %d variables. Their total size "
                             "is %d bytes. The average size is %f bytes" % (
                                    variables_count, total_size, average_size), file=result)
        # not returning anything is akin to returning success


def __lldb_init_module(debugger, dict):
    # Register all classes that have a register_lldb_command method
    for _name, cls in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(cls) and callable(getattr(cls,
                                                     "register_lldb_command",
                                                     None)):
            cls.register_lldb_command(debugger, __name__)
