#!/usr/bin/env python
# -*- coding: utf-8 -*-

import lldb
import re
import optparse
import ds 
import shlex

class GlobalOptions(object):
    symbols = {}

    @staticmethod
    def addSymbols(symbols, options, breakpoint):
        key = str(breakpoint.GetID())
        GlobalOptions.symbols[key] = (symbols, options)
        

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand(
        'command script add -f breakifonfunc.breakifonfunc biof')

def breakifonfunc(debugger, command, exe_ctx, result, internal_dict):
    '''
    usage: biof regex1 [Optional_ModuleName] ||| regex2  ModuleName1
    Regex breakpoint that stops only if the second regex breakpoint is in the stack trace
    For example, to only stop if code in the "Test" module resulted the setTintColor: being called
    biof setTintColor: ||| . Test 
    '''
    command_args = shlex.split(command, posix=False)
    parser = generateOptionParser()
    try:
        (options, args) = parser.parse_args(command_args)
    except:
        result.SetError("Error parsing")
        return 

    # if len(args) >= 2:
    #     result.SetError(parser.usage)
    #     return 

    target = exe_ctx.target
    # if len(command.split('|||')) != 2:
    #     result.SetError(parser.usage)

    t = " ".join(args).split('|||')
    clean_command = t[0].strip().split()
    if len(clean_command) == 2:
        breakpoint = target.BreakpointCreateByRegex(clean_command[0], clean_command[1])
    else:
        breakpoint = target.BreakpointCreateByRegex(clean_command[0], None)

    moduleName = t[1].strip().split()[1]
    module = target.module[moduleName] 
    if not module:
        result.SetError('Invalid module {}'.format(moduleName))
        return

    searchQuery = t[1].strip().split()[0]
    s = [i for i in module.symbols if re.search(searchQuery, i.name)]

    GlobalOptions.addSymbols(s, options, breakpoint)
    breakpoint.SetScriptCallbackFunction("breakifonfunc.breakpointHandler")

    if not breakpoint.IsValid() or breakpoint.num_locations == 0:
        result.AppendWarning("Breakpoint isn't valid or hasn't found any hits: " + clean_command[0])
    else:
        result.AppendMessage("\"{}\" produced {} hits\nOnly will stop if the following stack frame symbols contain:\n{}` \"{}\" produced {} hits".format( 
             clean_command[0], breakpoint.num_locations, module.file.basename, searchQuery, len(s)) )


def breakpointHandler(frame, bp_loc, dict):
    if len(GlobalOptions.symbols) == 0:
        print("womp something internal called reload LLDB init which removed the global symbols")
        return True
    key = str(bp_loc.GetBreakpoint().GetID())
    searchSymbols = GlobalOptions.symbols[key][0]
    options = GlobalOptions.symbols[key][1]

    function_name = frame.GetFunctionName()
    thread = frame.thread
    if options.direct_call:
        frame = thread.frame[1]
        print(frame)
        symbol = frame.symbol
        return any([symbol in searchSymbols])


    s = [i.symbol for i in thread.frames]
    return any(x in s for x in searchSymbols)


def generateOptionParser():
    usage = breakifonfunc.__doc__
    parser = optparse.OptionParser(usage=usage, prog="biof")
    parser.add_option("-d", "--direct",
                  action="store_true",
                  default=False,
                  dest="direct_call",
                  help="Only stop if the second regex directly calls the breakpoint")
    return parser
