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

#include "lldb/API/SBDebugger.h"

#include "lldb/lldb-include.h"
#include "lldb/Interpreter/Args.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/State.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/TargetList.h"

#include "lldb/API/SBListener.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBTarget.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBThread.h"
#include "lldb/API/SBSourceManager.h"
#include "lldb/API/SBInputReader.h"

using namespace lldb;
using namespace lldb_private;

void
SBDebugger::Initialize ()
{
    Debugger::Initialize();
}

void
SBDebugger::Terminate ()
{
    Debugger::Terminate();
}

void
SBDebugger::SetAsync (bool b)
{
    static bool value_set_once = false;

    if (!value_set_once)
    {
        value_set_once = true;
        Debugger::GetSharedInstance().SetAsyncExecution(b);
    }
}

void
SBDebugger::SetInputFile (const char *tty_name)
{
    // DEPRECATED: will be removed in next submission
    FILE *fh = ::fopen (tty_name, "r");
    SetInputFileHandle  (fh, true);
}

void
SBDebugger::SetOutputFile (const char *tty_name)
{
    // DEPRECATED: will be removed in next submission
    FILE *fh = ::fopen (tty_name, "w");
    SetOutputFileHandle (fh, true);
    SetErrorFileHandle  (fh, false);
}

void
SBDebugger::SetErrorFile (const char *tty_name)
{
    // DEPRECATED: will be removed in next submission
}


// Shouldn't really be settable after initialization as this could cause lots of problems; don't want users
// trying to switch modes in the middle of a debugging session.
void
SBDebugger::SetInputFileHandle (FILE *fh, bool transfer_ownership)
{
    Debugger::GetSharedInstance().SetInputFileHandle (fh, transfer_ownership);
}

void
SBDebugger::SetOutputFileHandle (FILE *fh, bool transfer_ownership)
{
    Debugger::GetSharedInstance().SetOutputFileHandle (fh, transfer_ownership);
}

void
SBDebugger::SetErrorFileHandle (FILE *fh, bool transfer_ownership)
{
    Debugger::GetSharedInstance().SetErrorFileHandle (fh, transfer_ownership);
}

FILE *
SBDebugger::GetInputFileHandle ()
{
    return Debugger::GetSharedInstance().GetInputFileHandle();
}

FILE *
SBDebugger::GetOutputFileHandle ()
{
    return Debugger::GetSharedInstance().GetOutputFileHandle();
}

FILE *
SBDebugger::GetErrorFileHandle ()
{
    return Debugger::GetSharedInstance().GetErrorFileHandle();
}

SBCommandInterpreter
SBDebugger::GetCommandInterpreter ()
{
    SBCommandInterpreter sb_interpreter(Debugger::GetSharedInstance().GetCommandInterpreter());
    return sb_interpreter;
}

void
SBDebugger::HandleCommand (const char *command)
{
    SBProcess process;
    SBCommandInterpreter sb_interpreter(Debugger::GetSharedInstance().GetCommandInterpreter());
    SBCommandReturnObject result;

    sb_interpreter.HandleCommand (command, result, false);

    if (GetErrorFileHandle() != NULL)
        result.PutError (GetErrorFileHandle());
    if (GetOutputFileHandle() != NULL)
        result.PutOutput (GetOutputFileHandle());

    if (Debugger::GetSharedInstance().GetAsyncExecution() == false)
    {
        process = GetCommandInterpreter().GetProcess ();
        if (process.IsValid())
        {
            EventSP event_sp;
            Listener &lldb_listener = Debugger::GetSharedInstance().GetListener();
            while (lldb_listener.GetNextEventForBroadcaster (process.get(), event_sp))
            {
                SBEvent event(event_sp);
                HandleProcessEvent (process, event, GetOutputFileHandle(), GetErrorFileHandle());
            }
        }
    }
}

SBListener
SBDebugger::GetListener ()
{
    SBListener sb_listener(Debugger::GetSharedInstance().GetListener());
    return sb_listener;
}

void
SBDebugger::HandleProcessEvent (const SBProcess &process, const SBEvent &event, FILE *out, FILE *err)
{
     const uint32_t event_type = event.GetType();
     char stdio_buffer[1024];
     size_t len;

     if (event_type & Process::eBroadcastBitSTDOUT)
     {
         while ((len = process.GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0)
             if (out != NULL)
                 ::fwrite (stdio_buffer, 1, len, out);
     }
     else if (event_type & Process::eBroadcastBitSTDERR)
     {
         while ((len = process.GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0)
             if (out != NULL)
                 ::fwrite (stdio_buffer, 1, len, out);
     }
     else if (event_type & Process::eBroadcastBitStateChanged)
     {
         // Drain any stdout messages.
         while ((len = process.GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0)
             if (out != NULL)
                 ::fwrite (stdio_buffer, 1, len, out);

         // Drain any stderr messages.
         while ((len = process.GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0)
             if (out != NULL)
                 ::fwrite (stdio_buffer, 1, len, out);

         StateType event_state = SBProcess::GetStateFromEvent (event);

         if (event_state == eStateInvalid)
             return;

         bool is_stopped = StateIsStoppedState (event_state);
         if (!is_stopped)
             process.ReportCurrentState (event, out);
   }
}

void
SBDebugger::UpdateCurrentThread (SBProcess &process)
{
    if (process.IsValid())
    {
        SBThread curr_thread = process.GetCurrentThread ();
        SBThread thread;
        StopReason curr_thread_stop_reason = eStopReasonInvalid;
        if (curr_thread.IsValid())
        {
            if (curr_thread.GetStopReason() != eStopReasonInvalid)
                curr_thread_stop_reason = curr_thread.GetStopReason ();
        }

        if (! curr_thread.IsValid()
            || curr_thread_stop_reason == eStopReasonInvalid
            || curr_thread_stop_reason == eStopReasonNone)
          {
            // Prefer a thread that has just completed its plan over another thread as current thread.
            SBThread plan_thread;
            SBThread other_thread;
            const size_t num_threads = process.GetNumThreads ();
            size_t i;
            for (i = 0; i < num_threads; ++i)
            {
                thread = process.GetThreadAtIndex(i);
                if (thread.GetStopReason () != eStopReasonInvalid)
                {
                    switch (thread.GetStopReason ())
                    {
                        default:
                        case eStopReasonInvalid:
                        case eStopReasonNone:
                            break;

                        case eStopReasonTrace:
                        case eStopReasonBreakpoint:
                        case eStopReasonWatchpoint:
                        case eStopReasonSignal:
                        case eStopReasonException:
                            if (! other_thread.IsValid())
                                other_thread = thread;
                            break;
                        case eStopReasonPlanComplete:
                            if (! plan_thread.IsValid())
                                plan_thread = thread;
                            break;
                    }
                }
            }
            if (plan_thread.IsValid())
                process.SetCurrentThreadByID (plan_thread.GetThreadID());
            else if (other_thread.IsValid())
                process.SetCurrentThreadByID (other_thread.GetThreadID());
            else
            {
                if (curr_thread.IsValid())
                    thread = curr_thread;
                else
                    thread = process.GetThreadAtIndex(0);

                if (thread.IsValid())
                    process.SetCurrentThreadByID (thread.GetThreadID());
            }
        }
    }
}

void
SBDebugger::ReportCurrentLocation (FILE *out, FILE *err)
{
    if ((out == NULL) || (err == NULL))
        return;

    SBTarget sb_target (GetCurrentTarget());
    if (!sb_target.IsValid())
    {
        fprintf (out, "no target\n");
        return;
    }

    SBProcess process = sb_target.GetProcess ();
    if (process.IsValid())
    {
        StateType state = process.GetState();

        if (StateIsStoppedState (state))
        {
            if (state == eStateExited)
            {
                int exit_status = process.GetExitStatus();
                const char *exit_description = process.GetExitDescription();
                ::fprintf (out, "Process %d exited with status = %i (0x%8.8x) %s\n",
                           process.GetProcessID(),
                           exit_status,
                           exit_status,
                           exit_description ? exit_description : "");
            }
            else
            {
                fprintf (out, "Process %d %s\n", process.GetProcessID(), StateAsCString (state));
                SBThread current_thread = process.GetThreadAtIndex (0);
                if (current_thread.IsValid())
                {
                    process.DisplayThreadsInfo (out, err, true);
                }
                else
                    fprintf (out, "No valid thread found in current process\n");
            }
        }
        else
            fprintf (out, "No current location or status available\n");
    }
}

SBSourceManager &
SBDebugger::GetSourceManager ()
{
    static SourceManager g_lldb_source_manager;
    static SBSourceManager g_sb_source_manager (g_lldb_source_manager);
    return g_sb_source_manager;
}


bool
SBDebugger::GetDefaultArchitecture (char *arch_name, size_t arch_name_len)
{
    if (arch_name && arch_name_len)
    {
        ArchSpec &default_arch = lldb_private::GetDefaultArchitecture ();
        if (default_arch.IsValid())
        {
            ::snprintf (arch_name, arch_name_len, "%s", default_arch.AsCString());
            return true;
        }
    }
    if (arch_name && arch_name_len)
        arch_name[0] = '\0';
    return false;
}


bool
SBDebugger::SetDefaultArchitecture (const char *arch_name)
{
    if (arch_name)
    {
        ArchSpec arch (arch_name);
        if (arch.IsValid())
        {
            lldb_private::GetDefaultArchitecture () = arch;
            return true;
        }
    }
    return false;
}

ScriptLanguage
SBDebugger::GetScriptingLanguage (const char *script_language_name)
{
    return Args::StringToScriptLanguage (script_language_name,
                                         eScriptLanguageDefault,
                                         NULL);
}
//pid_t
/*
SBDebugger::AttachByName (const char *process_name, const char *filename)
{
    SBTarget *temp_target = GetCurrentTarget();
    SBTarget sb_target;
    pid_t return_pid = (pid_t) LLDB_INVALID_PROCESS_ID;

    if (temp_target == NULL)
    {
        if (filename != NULL)
        {
            sb_target = CreateWithFile (filename);
            sb_target.SetArch (LLDB_ARCH_DEFAULT);
        }
    }
    else
    {
          sb_target = *temp_target;
    }

    if (sb_target.IsValid())
    {
        SBProcess process = sb_target.GetProcess ();
        if (process.IsValid())
        {
            return_pid = process.AttachByName (process_name);
        }
    }
    return return_pid;
}
*/

const char *
SBDebugger::GetVersionString ()
{
    return lldb_private::GetVersion();
}

const char *
SBDebugger::StateAsCString (lldb::StateType state)
{
    return lldb_private::StateAsCString (state);
}

bool
SBDebugger::StateIsRunningState (lldb::StateType state)
{
    return lldb_private::StateIsRunningState (state);
}

bool
SBDebugger::StateIsStoppedState (lldb::StateType state)
{
    return lldb_private::StateIsStoppedState (state);
}


SBTarget
SBDebugger::CreateTargetWithFileAndTargetTriple (const char *filename,
                                                 const char *target_triple)
{
    ArchSpec arch;
    FileSpec file_spec (filename);
    arch.SetArchFromTargetTriple(target_triple);
    TargetSP target_sp;
    Error error (Debugger::GetSharedInstance().GetTargetList().CreateTarget (file_spec, arch, NULL, true, target_sp));
    SBTarget target(target_sp);
    return target;
}

SBTarget
SBDebugger::CreateTargetWithFileAndArch (const char *filename, const char *archname)
{
    FileSpec file (filename);
    ArchSpec arch = lldb_private::GetDefaultArchitecture();
    TargetSP target_sp;
    Error error;

    if (archname != NULL)
    {
        ArchSpec arch2 (archname);
        error = Debugger::GetSharedInstance().GetTargetList().CreateTarget (file, arch2, NULL, true, target_sp);
    }
    else
    {
        if (!arch.IsValid())
            arch = LLDB_ARCH_DEFAULT;

        error = Debugger::GetSharedInstance().GetTargetList().CreateTarget (file, arch, NULL, true, target_sp);

        if (error.Fail())
        {
            if (arch == LLDB_ARCH_DEFAULT_32BIT)
                arch = LLDB_ARCH_DEFAULT_64BIT;
            else
                arch = LLDB_ARCH_DEFAULT_32BIT;

            error = Debugger::GetSharedInstance().GetTargetList().CreateTarget (file, arch, NULL, true, target_sp);
        }
    }

    if (error.Success())
        Debugger::GetSharedInstance().GetTargetList().SetCurrentTarget (target_sp.get());
    else
        target_sp.reset();

    SBTarget sb_target (target_sp);
    return sb_target;
}

SBTarget
SBDebugger::CreateTarget (const char *filename)
{
    FileSpec file (filename);
    ArchSpec arch = lldb_private::GetDefaultArchitecture();
    TargetSP target_sp;
    Error error;

    if (!arch.IsValid())
        arch = LLDB_ARCH_DEFAULT;

    error = Debugger::GetSharedInstance().GetTargetList().CreateTarget (file, arch, NULL, true, target_sp);

    if (error.Fail())
    {
        if (arch == LLDB_ARCH_DEFAULT_32BIT)
            arch = LLDB_ARCH_DEFAULT_64BIT;
        else
            arch = LLDB_ARCH_DEFAULT_32BIT;

        error = Debugger::GetSharedInstance().GetTargetList().CreateTarget (file, arch, NULL, true, target_sp);
    }

    if (!error.Fail())
        Debugger::GetSharedInstance().GetTargetList().SetCurrentTarget (target_sp.get());

    SBTarget sb_target (target_sp);
    return sb_target;
}

SBTarget
SBDebugger::GetTargetAtIndex (uint32_t idx)
{
    SBTarget sb_target (Debugger::GetSharedInstance().GetTargetList().GetTargetAtIndex (idx));
    return sb_target;
}

SBTarget
SBDebugger::FindTargetWithProcessID (pid_t pid)
{
    SBTarget sb_target(Debugger::GetSharedInstance().GetTargetList().FindTargetWithProcessID (pid));
    return sb_target;
}

SBTarget
SBDebugger::FindTargetWithFileAndArch (const char *filename, const char *arch_name)
{
    ArchSpec arch;
    if (arch_name)
        arch.SetArch(arch_name);
    return SBTarget (Debugger::GetSharedInstance().GetTargetList().FindTargetWithExecutableAndArchitecture (FileSpec(filename),
                                                                                                            arch_name ? &arch : NULL));
}

SBTarget
SBDebugger::FindTargetWithLLDBProcess (const lldb::ProcessSP &process_sp)
{
    SBTarget sb_target(Debugger::GetSharedInstance().GetTargetList().FindTargetWithProcess (process_sp.get()));
    return sb_target;
}


uint32_t
SBDebugger::GetNumTargets ()
{
    return Debugger::GetSharedInstance().GetTargetList().GetNumTargets ();}

SBTarget
SBDebugger::GetCurrentTarget ()
{
    SBTarget sb_target(Debugger::GetSharedInstance().GetTargetList().GetCurrentTarget ());
    return sb_target;
}

void
SBDebugger::DispatchInput (void *baton, const void *data, size_t data_len)
{
    Debugger::GetSharedInstance().DispatchInput ((const char *) data, data_len);
}

void
SBDebugger::PushInputReader (SBInputReader &reader)
{
    if (reader.IsValid())
    {
        InputReaderSP reader_sp(*reader);
        Debugger::GetSharedInstance().PushInputReader (reader_sp);
    }
}
