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

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes
#include "lldb/Target/LanguageRuntime.h"
#include "Plugins/Language/ObjC/ObjCLanguage.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/SearchFilter.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Target/ObjCLanguageRuntime.h"
#include "lldb/Target/Target.h"

using namespace lldb;
using namespace lldb_private;

ExceptionSearchFilter::ExceptionSearchFilter(const lldb::TargetSP &target_sp,
                                             lldb::LanguageType language,
                                             bool update_module_list)
    : SearchFilter(target_sp, FilterTy::Exception), m_language(language),
      m_language_runtime(nullptr), m_filter_sp() {
  if (update_module_list)
    UpdateModuleListIfNeeded();
}

bool ExceptionSearchFilter::ModulePasses(const lldb::ModuleSP &module_sp) {
  UpdateModuleListIfNeeded();
  if (m_filter_sp)
    return m_filter_sp->ModulePasses(module_sp);
  return false;
}

bool ExceptionSearchFilter::ModulePasses(const FileSpec &spec) {
  UpdateModuleListIfNeeded();
  if (m_filter_sp)
    return m_filter_sp->ModulePasses(spec);
  return false;
}

void ExceptionSearchFilter::Search(Searcher &searcher) {
  UpdateModuleListIfNeeded();
  if (m_filter_sp)
    m_filter_sp->Search(searcher);
}

void ExceptionSearchFilter::GetDescription(Stream *s) {
  UpdateModuleListIfNeeded();
  if (m_filter_sp)
    m_filter_sp->GetDescription(s);
}

void ExceptionSearchFilter::UpdateModuleListIfNeeded() {
  ProcessSP process_sp(m_target_sp->GetProcessSP());
  if (process_sp) {
    bool refreash_filter = !m_filter_sp;
    if (m_language_runtime == nullptr) {
      m_language_runtime = process_sp->GetLanguageRuntime(m_language);
      refreash_filter = true;
    } else {
      LanguageRuntime *language_runtime =
          process_sp->GetLanguageRuntime(m_language);
      if (m_language_runtime != language_runtime) {
        m_language_runtime = language_runtime;
        refreash_filter = true;
      }
    }

    if (refreash_filter && m_language_runtime) {
      m_filter_sp = m_language_runtime->CreateExceptionSearchFilter();
    }
  } else {
    m_filter_sp.reset();
    m_language_runtime = nullptr;
  }
}

SearchFilterSP
ExceptionSearchFilter::DoCopyForBreakpoint(Breakpoint &breakpoint) {
  return SearchFilterSP(
      new ExceptionSearchFilter(TargetSP(), m_language, false));
}

SearchFilter *ExceptionSearchFilter::CreateFromStructuredData(
    Target &target, const StructuredData::Dictionary &data_dict,
    Status &error) {
  SearchFilter *result = nullptr;
  return result;
}

StructuredData::ObjectSP ExceptionSearchFilter::SerializeToStructuredData() {
  StructuredData::ObjectSP result_sp;

  return result_sp;
}

// The Target is the one that knows how to create breakpoints, so this function
// is meant to be used either by the target or internally in
// Set/ClearExceptionBreakpoints.
class ExceptionBreakpointResolver : public BreakpointResolver {
public:
  ExceptionBreakpointResolver(lldb::LanguageType language, bool catch_bp,
                              bool throw_bp)
      : BreakpointResolver(nullptr, BreakpointResolver::ExceptionResolver),
        m_language(language), m_language_runtime(nullptr), m_catch_bp(catch_bp),
        m_throw_bp(throw_bp) {}

  ~ExceptionBreakpointResolver() override = default;

  Searcher::CallbackReturn SearchCallback(SearchFilter &filter,
                                          SymbolContext &context, Address *addr,
                                          bool containing) override {

    if (SetActualResolver())
      return m_actual_resolver_sp->SearchCallback(filter, context, addr,
                                                  containing);
    else
      return eCallbackReturnStop;
  }

  Searcher::Depth GetDepth() override {
    if (SetActualResolver())
      return m_actual_resolver_sp->GetDepth();
    else
      return eDepthTarget;
  }

  void GetDescription(Stream *s) override {
    Language *language_plugin = Language::FindPlugin(m_language);
    if (language_plugin)
      language_plugin->GetExceptionResolverDescription(m_catch_bp, m_throw_bp,
                                                       *s);
    else
      Language::GetDefaultExceptionResolverDescription(m_catch_bp, m_throw_bp,
                                                       *s);

    SetActualResolver();
    if (m_actual_resolver_sp) {
      s->Printf(" using: ");
      m_actual_resolver_sp->GetDescription(s);
    } else
      s->Printf(" the correct runtime exception handler will be determined "
                "when you run");
  }

  void Dump(Stream *s) const override {}

  /// Methods for support type inquiry through isa, cast, and dyn_cast:
  static inline bool classof(const BreakpointResolverName *) { return true; }
  static inline bool classof(const BreakpointResolver *V) {
    return V->getResolverID() == BreakpointResolver::ExceptionResolver;
  }

protected:
  BreakpointResolverSP CopyForBreakpoint(Breakpoint &breakpoint) override {
    return BreakpointResolverSP(
        new ExceptionBreakpointResolver(m_language, m_catch_bp, m_throw_bp));
  }

  bool SetActualResolver() {
    ProcessSP process_sp;
    if (m_breakpoint) {
      process_sp = m_breakpoint->GetTarget().GetProcessSP();
      if (process_sp) {
        bool refreash_resolver = !m_actual_resolver_sp;
        if (m_language_runtime == nullptr) {
          m_language_runtime = process_sp->GetLanguageRuntime(m_language);
          refreash_resolver = true;
        } else {
          LanguageRuntime *language_runtime =
              process_sp->GetLanguageRuntime(m_language);
          if (m_language_runtime != language_runtime) {
            m_language_runtime = language_runtime;
            refreash_resolver = true;
          }
        }

        if (refreash_resolver && m_language_runtime) {
          m_actual_resolver_sp = m_language_runtime->CreateExceptionResolver(
              m_breakpoint, m_catch_bp, m_throw_bp);
        }
      } else {
        m_actual_resolver_sp.reset();
        m_language_runtime = nullptr;
      }
    } else {
      m_actual_resolver_sp.reset();
      m_language_runtime = nullptr;
    }
    return (bool)m_actual_resolver_sp;
  }

  lldb::BreakpointResolverSP m_actual_resolver_sp;
  lldb::LanguageType m_language;
  LanguageRuntime *m_language_runtime;
  bool m_catch_bp;
  bool m_throw_bp;
};

LanguageRuntime *LanguageRuntime::FindPlugin(Process *process,
                                             lldb::LanguageType language) {
  std::unique_ptr<LanguageRuntime> language_runtime_ap;
  LanguageRuntimeCreateInstance create_callback;

  for (uint32_t idx = 0;
       (create_callback =
            PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(idx)) !=
       nullptr;
       ++idx) {
    language_runtime_ap.reset(create_callback(process, language));

    if (language_runtime_ap)
      return language_runtime_ap.release();
  }

  return nullptr;
}

LanguageRuntime::LanguageRuntime(Process *process) : m_process(process) {}

LanguageRuntime::~LanguageRuntime() = default;

Breakpoint::BreakpointPreconditionSP
LanguageRuntime::CreateExceptionPrecondition(lldb::LanguageType language,
                                             bool catch_bp, bool throw_bp) {
  switch (language) {
  case eLanguageTypeObjC:
    if (throw_bp)
      return Breakpoint::BreakpointPreconditionSP(
          new ObjCLanguageRuntime::ObjCExceptionPrecondition());
    break;
  default:
    break;
  }
  return Breakpoint::BreakpointPreconditionSP();
}

BreakpointSP LanguageRuntime::CreateExceptionBreakpoint(
    Target &target, lldb::LanguageType language, bool catch_bp, bool throw_bp,
    bool is_internal) {
  BreakpointResolverSP resolver_sp(
      new ExceptionBreakpointResolver(language, catch_bp, throw_bp));
  SearchFilterSP filter_sp(
      new ExceptionSearchFilter(target.shared_from_this(), language));
  bool hardware = false;
  bool resolve_indirect_functions = false;
  BreakpointSP exc_breakpt_sp(
      target.CreateBreakpoint(filter_sp, resolver_sp, is_internal, hardware,
                              resolve_indirect_functions));
  if (exc_breakpt_sp) {
    Breakpoint::BreakpointPreconditionSP precondition_sp =
        CreateExceptionPrecondition(language, catch_bp, throw_bp);
    if (precondition_sp)
      exc_breakpt_sp->SetPrecondition(precondition_sp);

    if (is_internal)
      exc_breakpt_sp->SetBreakpointKind("exception");
  }

  return exc_breakpt_sp;
}

void LanguageRuntime::InitializeCommands(CommandObject *parent) {
  if (!parent)
    return;

  if (!parent->IsMultiwordObject())
    return;

  LanguageRuntimeCreateInstance create_callback;

  for (uint32_t idx = 0;
       (create_callback =
            PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(idx)) !=
       nullptr;
       ++idx) {
    if (LanguageRuntimeGetCommandObject command_callback =
            PluginManager::GetLanguageRuntimeGetCommandObjectAtIndex(idx)) {
      CommandObjectSP command =
          command_callback(parent->GetCommandInterpreter());
      if (command) {
        // the CommandObject vended by a Language plugin cannot be created once
        // and cached because we may create multiple debuggers and need one
        // instance of the command each - the implementing function is meant to
        // create a new instance of the command each time it is invoked.
        parent->LoadSubCommand(command->GetCommandName().str().c_str(), command);
      }
    }
  }
}

lldb::SearchFilterSP LanguageRuntime::CreateExceptionSearchFilter() {
  return m_process->GetTarget().GetSearchFilterForModule(nullptr);
}