//===-- TestCase.cpp --------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "TestCase.h"
#include "Results.h"
#include "Xcode.h"

using namespace lldb_perf;

TestCase::TestCase () :
    m_debugger(),
    m_target(),
    m_process(),
    m_thread(),
    m_listener(),
    m_verbose(false),
    m_step(0)
{
    SBDebugger::Initialize();
	SBHostOS::ThreadCreated ("<lldb-tester.app.main>");
	m_debugger = SBDebugger::Create(false);
	m_listener = m_debugger.GetListener();
    m_listener.StartListeningForEventClass (m_debugger, SBProcess::GetBroadcasterClass(), SBProcess::eBroadcastBitStateChanged | SBProcess::eBroadcastBitInterrupt);
}

static std::string
GetShortOptionString (struct option *long_options)
{
    std::string option_string;
    for (int i = 0; long_options[i].name != NULL; ++i)
    {
        if (long_options[i].flag == NULL)
        {
            option_string.push_back ((char) long_options[i].val);
            switch (long_options[i].has_arg)
            {
                default:
                case no_argument:
                    break;
                case required_argument:
                    option_string.push_back (':');
                    break;
                case optional_argument:
                    option_string.append (2, ':');
                    break;
            }
        }
    }
    return option_string;
}

bool
TestCase::Setup (int& argc, const char**& argv)
{
    bool done = false;
    
    struct option* long_options = GetLongOptions();
    
    if (long_options)
    {
        std::string short_option_string (GetShortOptionString(long_options));
        
    #if __GLIBC__
        optind = 0;
    #else
        optreset = 1;
        optind = 1;
    #endif
        while (!done)
        {
            int long_options_index = -1;
            const int short_option = ::getopt_long_only (argc,
                                                         const_cast<char **>(argv),
                                                         short_option_string.c_str(),
                                                         long_options,
                                                         &long_options_index);
            
            switch (short_option)
            {
                case 0:
                    // Already handled
                    break;
                    
                case -1:
                    done = true;
                    break;
                    
                default:
                    done = !ParseOption(short_option, optarg);
                    break;
            }
        }
        argc -= optind;
        argv += optind;
    }
    
    return false;
}

bool
TestCase::Launch (lldb::SBLaunchInfo &launch_info)
{
    lldb::SBError error;
	m_process = m_target.Launch (launch_info, error);
    if (!error.Success())
        fprintf (stderr, "error: %s\n", error.GetCString());
    if (m_process.IsValid())
        return true;
    return false;
}

bool
TestCase::Launch (std::initializer_list<const char*> args)
{
    std::vector<const char*> args_vect(args);
    args_vect.push_back(NULL);
    lldb::SBLaunchInfo launch_info((const char**)&args_vect[0]);
    return Launch(launch_info);
}

void
TestCase::SetVerbose (bool b)
{
    m_verbose = b;
}

bool
TestCase::GetVerbose ()
{
    return m_verbose;
}

void
TestCase::Loop ()
{
	while (true)
	{
        bool call_test_step = false;
        if (m_process.IsValid())
        {
            SBEvent evt;
            m_listener.WaitForEvent (UINT32_MAX, evt);
            StateType state = SBProcess::GetStateFromEvent (evt);
            if (m_verbose)
                printf("event = %s\n",SBDebugger::StateAsCString(state));
            if (SBProcess::GetRestartedFromEvent(evt))
            {
                if (m_verbose)
                {
                    const uint32_t num_threads = m_process.GetNumThreads();
                    for (auto thread_index = 0; thread_index < num_threads; thread_index++)
                    {
                        SBThread thread(m_process.GetThreadAtIndex(thread_index));
                        SBFrame frame(thread.GetFrameAtIndex(0));
                        SBStream strm;
                        strm.RedirectToFileHandle(stdout, false);
                        frame.GetDescription(strm);
                    }
                    puts("restarted");
                }
                call_test_step = false;
            }
            else
            {
                switch (state)
                {
                case eStateInvalid:
                case eStateDetached:
                case eStateCrashed:
                case eStateUnloaded:
                    break;
                case eStateExited:
                    return;
                case eStateConnected:
                case eStateAttaching:
                case eStateLaunching:
                case eStateRunning:
                case eStateStepping:
                    call_test_step = false;
                    break;
        
                case eStateStopped:
                case eStateSuspended:
                    {
                        call_test_step = true;
                        bool fatal = false;
                        bool selected_thread = false;
                        const uint32_t num_threads = m_process.GetNumThreads();
                        for (auto thread_index = 0; thread_index < num_threads; thread_index++)
                        {
                            SBThread thread(m_process.GetThreadAtIndex(thread_index));
                            SBFrame frame(thread.GetFrameAtIndex(0));
                            SBStream strm;
                            strm.RedirectToFileHandle(stdout, false);
                            frame.GetDescription(strm);
                            bool select_thread = false;
                            StopReason stop_reason = thread.GetStopReason();
                            if (m_verbose) printf("tid = 0x%llx pc = 0x%llx ",thread.GetThreadID(),frame.GetPC());
                            switch (stop_reason)
                            {
                                case eStopReasonNone:
                                    if (m_verbose)
                                        printf("none\n");
                                    break;
                                    
                                case eStopReasonTrace:
                                    select_thread = true;
                                    if (m_verbose)
                                        printf("trace\n");
                                    break;
                                    
                                case eStopReasonPlanComplete:
                                    select_thread = true;
                                    if (m_verbose)
                                        printf("plan complete\n");
                                    break;
                                case eStopReasonThreadExiting:
                                    if (m_verbose)
                                        printf("thread exiting\n");
                                    break;
                                case eStopReasonExec:
                                    if (m_verbose)
                                        printf("exec\n");
                                    break;
                                case eStopReasonInvalid:
                                    if (m_verbose)
                                        printf("invalid\n");
                                    break;
                                case eStopReasonException:
                                    select_thread = true;
                                    if (m_verbose)
                                        printf("exception\n");
                                    fatal = true;
                                    break;
                                case eStopReasonBreakpoint:
                                    select_thread = true;
                                    if (m_verbose)
                                        printf("breakpoint id = %lld.%lld\n",thread.GetStopReasonDataAtIndex(0),thread.GetStopReasonDataAtIndex(1));
                                    break;
                                case eStopReasonWatchpoint:
                                    select_thread = true;
                                    if (m_verbose)
                                        printf("watchpoint id = %lld\n",thread.GetStopReasonDataAtIndex(0));
                                    break;
                                case eStopReasonSignal:
                                    select_thread = true;
                                    if (m_verbose)
                                        printf("signal %d\n",(int)thread.GetStopReasonDataAtIndex(0));
                                    break;
                            }
                            if (select_thread && !selected_thread)
                            {
                                m_thread = thread;
                                selected_thread = m_process.SetSelectedThread(thread);
                            }
                        }
                        if (fatal)
                        {
                            if (m_verbose) Xcode::RunCommand(m_debugger,"bt all",true);
                            exit(1);
                        }
                    }
                    break;
                }
            }
		}
        else
        {
            call_test_step = true;
        }

        if (call_test_step)
        {
        do_the_call:
            if (m_verbose)
                printf("RUNNING STEP %d\n",m_step);
            ActionWanted action;
            TestStep(m_step, action);
            m_step++;
            SBError err;
            switch (action.type)
            {
            case ActionWanted::Type::eNone:
                // Just exit and wait for the next event
                break;
            case ActionWanted::Type::eContinue:
                err = m_process.Continue();
                break;
            case ActionWanted::Type::eStepOut:
                if (action.thread.IsValid() == false)
                {
                    if (m_verbose)
                    {
                        Xcode::RunCommand(m_debugger,"bt all",true);
                        printf("error: invalid thread for step out on step %d\n", m_step);
                    }
                    exit(501);
                }
                m_process.SetSelectedThread(action.thread);
                action.thread.StepOut();
                break;
            case ActionWanted::Type::eStepOver:
                if (action.thread.IsValid() == false)
                {
                    if (m_verbose)
                    {
                        Xcode::RunCommand(m_debugger,"bt all",true);
                        printf("error: invalid thread for step over %d\n",m_step);
                    }
                    exit(500);
                }
                m_process.SetSelectedThread(action.thread);
                action.thread.StepOver();
                break;
            case ActionWanted::Type::eRelaunch:
                if (m_process.IsValid())
                {
                    m_process.Kill();
                    m_process.Clear();
                }
                Launch(action.launch_info);
                break;
            case ActionWanted::Type::eKill:
                if (m_verbose)
                    printf("kill\n");
                m_process.Kill();
                return;
            case ActionWanted::Type::eCallNext:
                goto do_the_call;
                break;
            }
        }

	}
    
	if (GetVerbose()) printf("I am gonna die at step %d\n",m_step);
}

int
TestCase::Run (TestCase& test, int argc, const char** argv)
{
    if (test.Setup(argc, argv))
    {
        test.Loop();
        Results results;
        test.WriteResults(results);
        return RUN_SUCCESS;
    }
    else
        return RUN_SETUP_ERROR;
}

