"""
These tests load an inferior into an LLDB instance and then issue API requests
using the client.

Tests:
Client -> Server -> LLDBAdaptor

Using an instantiated SBDebugger instance
"""

import tempfile
import sys
import json
import time
import logging
import subprocess
import time

from mock import Mock
from nose.tools import *

import voltron
from voltron.core import *
from voltron.api import *
from voltron.plugin import *

import platform
if platform.system() == 'Darwin':
    sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python")

try:
    import lldb

    from .common import *

    log = logging.getLogger("tests")

    def setup():
        global server, client, target, pm, adaptor, methods

        log.info("setting up API tests")

        # set up voltron
        voltron.setup_env()
        pm = PluginManager()
        plugin = pm.debugger_plugin_for_host('lldb')
        adaptor = plugin.adaptor_class()
        voltron.debugger = adaptor

        # start up a voltron server
        server = Server()
        # import pdb;pdb.set_trace()
        server.start()

        time.sleep(0.1)

        # set up client
        client = Client()

        # compile and load the test inferior
        subprocess.call("cc -o tests/inferior tests/inferior.c", shell=True)
        target = adaptor.host.CreateTargetWithFileAndArch("tests/inferior", lldb.LLDB_ARCH_DEFAULT)
        main_bp = target.BreakpointCreateByName("main", target.GetExecutable().GetFilename())

    def teardown():
        server.stop()
        time.sleep(5)

    def test_state_no_target():
        req = api_request('state')
        res = client.send_request(req)
        assert res.is_error
        assert res.code == 4101

    def test_state_stopped():
        process = target.LaunchSimple(None, None, os.getcwd())
        req = api_request('state')
        res = client.send_request(req)
        assert res.status == 'success'
        assert res.state == "stopped"
        target.process.Destroy()

    def test_targets():
        req = api_request('targets')
        res = client.send_request(req)
        assert res.status == 'success'
        t = res.targets[0]
        assert t["id"] == 0
        assert t["arch"] == "x86_64"
        assert t["file"].endswith("inferior")

    def test_registers():
        process = target.LaunchSimple(None, None, os.getcwd())
        req = api_request('registers')
        res = client.send_request(req)
        assert res.status == 'success'
        assert len(res.registers) > 0
        assert res.registers['rip'] != 0
        target.process.Destroy()

    def test_memory():
        process = target.LaunchSimple(None, None, os.getcwd())
        res = client.perform_request('registers')
        rsp = res.registers['rsp']
        res = client.perform_request('memory', address=rsp, length=0x40)
        assert res.status == 'success'
        assert len(res.memory) > 0
        res = client.perform_request('memory', address=rsp, length=0x40, deref=True)
        assert res.status == 'success'
        assert len(res.deref) > 0
        target.process.Destroy()

    def test_stack():
        process = target.LaunchSimple(None, None, os.getcwd())
        req = api_request('stack', length=0x40)
        res = client.send_request(req)
        assert res.status == 'success'
        assert len(res.memory) > 0
        target.process.Destroy()

    def test_command():
        process = target.LaunchSimple(None, None, os.getcwd())
        req = api_request('command', command="reg read")
        res = client.send_request(req)
        assert res.status == 'success'
        assert len(res.output) > 0
        assert 'rax' in res.output
        target.process.Destroy()

    def test_disassemble():
        process = target.LaunchSimple(None, None, os.getcwd())
        req = api_request('disassemble', count=16)
        res = client.send_request(req)
        assert res.status == 'success'
        assert len(res.disassembly) > 0
        assert 'push' in res.disassembly
        req = api_request('disassemble', count=16, use_capstone=True)
        res = client.send_request(req)
        assert res.status == 'success'
        assert len(res.disassembly) > 0
        assert 'push' in res.disassembly
        target.process.Destroy()

    def test_dereference():
        process = target.LaunchSimple(None, None, os.getcwd())
        res = client.perform_request('registers')
        res = client.perform_request('dereference', pointer=res.registers['rsp'])
        assert res.status == 'success'
        assert res.output[0][0] == 'pointer'
        assert res.output[-1][1] == 'start + 0x1'
        target.process.Destroy()

    def test_breakpoints():
        process = target.LaunchSimple(None, None, os.getcwd())
        res = client.perform_request('breakpoints')
        assert res.status == 'success'
        assert len(res.breakpoints) == 1
        assert res.breakpoints[0]['one_shot'] == False
        assert res.breakpoints[0]['enabled']
        assert res.breakpoints[0]['id'] == 1
        assert res.breakpoints[0]['hit_count'] > 0
        assert res.breakpoints[0]['locations'][0]['name'] == "inferior`main"
        target.process.Destroy()

    def test_multi_request():
        process = target.LaunchSimple(None, None, os.getcwd())
        reg_res, dis_res = client.send_requests(api_request('registers'),
                                                api_request('disassemble', count=16))
        assert reg_res.status == 'success'
        assert len(reg_res.registers) > 0
        assert reg_res.registers['rip'] != 0
        assert dis_res.status == 'success'
        assert len(dis_res.disassembly) > 0
        assert 'push' in dis_res.disassembly
        target.process.Destroy()

except:
    print("No LLDB")
