import pytest
import textwrap

import windows
import windows.generated_def as gdef
import windows.native_exec.simple_x86 as x86
import windows.native_exec.simple_x64 as x64

from .pfwtest import *

pytestmark = pytest.mark.usefixtures('check_for_gc_garbage')



@process_syswow_only
class TestSyswowCurrentProcess(object):
    def test_exec_syswow(self):
        x64_code = x64.assemble("mov rax, 0x4040404040404040; mov r11, 0x0202020202020202; add rax, r11; ret")
        res = windows.syswow64.execute_64bits_code_from_syswow(x64_code)
        assert res == 0x4242424242424242

    def test_self_pebsyswow(self):
        peb64 = windows.current_process.peb_syswow
        modules_names = [m.name for m in peb64.modules]
        assert "wow64.dll" in modules_names
        # Parsing
        wow64 = [m for m in peb64.modules if m.name == "wow64.dll"][0]
        assert "Wow64LdrpInitialize" in wow64.pe.exports

@python_injection
@windows_64bit_only
class TestSyswowRemoteProcess(object):
    def test_remote_pebsyswow(self, proc32):
        peb64 = proc32.peb_syswow
        modules_names = [m.name for m in peb64.modules]
        assert "wow64.dll" in modules_names
        # Parsing
        wow64 = [m for m in peb64.modules if m.name == "wow64.dll"][0]
        assert "Wow64LdrpInitialize" in wow64.pe.exports


    def test_getset_syswow_context(self, proc32):
        addr = proc32.virtual_alloc(0x1000)
        remote_python_code = """
        import windows
        import windows.native_exec.simple_x64 as x64
        x64_code = x64.assemble("mov r11, 0x1122334455667788; mov rax, 0x8877665544332211; mov [{0}], rax ;label :loop; jmp :loop; nop; nop; ret")
        res = windows.syswow64.execute_64bits_code_from_syswow(x64_code)
        windows.current_process.write_qword({0},  res)
        """.format(addr)

        t = proc32.execute_python_unsafe(textwrap.dedent(remote_python_code))
        # Wait for python execution
        while proc32.read_qword(addr) != 0x8877665544332211:
            pass
        ctx = t.context_syswow
        # Check the get context
        assert ctx.R11 == 0x1122334455667788
        assert proc32.read_memory(ctx.Rip, 2) == x64.assemble("label :loop; jmp :loop")
        t.suspend()
        proc32.write_memory(ctx.Rip, "\x90\x90")
        # Check the set context
        RETURN_VALUE = 0x4041424344454647
        ctx.Rax = RETURN_VALUE
        ctx.Rip += 2
        t.set_syswow_context(ctx)
        t.resume()
        t.wait()
        assert RETURN_VALUE == proc32.read_qword(addr)


import threading
import windows.test

threads_error = {}

def loop_query_ppid(proc, target_ppid):
    assert proc.bitness == 64

    try:
        for i in range(10):
            del proc._ppid # Force requery via syswow API
            assert proc.ppid == target_ppid
            for i in [x for x in proc.memory_state() if x.Protect == gdef.PAGE_EXECUTE_READ][:10]:
                assert proc.read_memory(i.BaseAddress, 0x1000)
            # assert False, "LOL"
    except Exception as e:
        # import traceback; traceback.print(
        threads_error[windows.current_thread.tid] = e
        raise
    return True

@process_syswow_only
def test_syswow_call_multithread():
    all_threads = []
    all_procs = []

    # Create multiple thread that will trigger concurrent call to NtQueryInformationProcess_32_to_64
    # Old version of PFW did not handled that thus generating invalid result / crash
    for tnb in range(10):
        new_proc = windows.test.pop_proc_64()
        new_proc_pid = new_proc.ppid
        all_procs.append(new_proc)
        t = threading.Thread(target=loop_query_ppid, args=(new_proc, new_proc_pid))
        all_threads.append(t)

    # import pdb; pdb.set_trace()
    for t in all_threads:
        t.start()
    for t in all_threads:
        t.join()
    for p in all_procs:
        p.exit()

    assert not threads_error, "syswow call inconsistent with MultiThreading inconsistent"
