//===-- CommandCompletions.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
#include <sys/stat.h>
#if defined(__APPLE__) || defined(__linux__)
#include <pwd.h>
#endif

// C++ Includes
// Other libraries and framework includes
#include "llvm/ADT/SmallString.h"

// Project includes
#include "lldb/Core/FileSpecList.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Interpreter/Args.h"
#include "lldb/Interpreter/CommandCompletions.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/Variable.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/CleanUp.h"

#include "llvm/ADT/SmallString.h"

using namespace lldb_private;

CommandCompletions::CommonCompletionElement
    CommandCompletions::g_common_completions[] = {
        {eCustomCompletion, nullptr},
        {eSourceFileCompletion, CommandCompletions::SourceFiles},
        {eDiskFileCompletion, CommandCompletions::DiskFiles},
        {eDiskDirectoryCompletion, CommandCompletions::DiskDirectories},
        {eSymbolCompletion, CommandCompletions::Symbols},
        {eModuleCompletion, CommandCompletions::Modules},
        {eSettingsNameCompletion, CommandCompletions::SettingsNames},
        {ePlatformPluginCompletion, CommandCompletions::PlatformPluginNames},
        {eArchitectureCompletion, CommandCompletions::ArchitectureNames},
        {eVariablePathCompletion, CommandCompletions::VariablePath},
        {eNoCompletion, nullptr} // This one has to be last in the list.
};

bool CommandCompletions::InvokeCommonCompletionCallbacks(
    CommandInterpreter &interpreter, uint32_t completion_mask,
    llvm::StringRef completion_str, int match_start_point,
    int max_return_elements, SearchFilter *searcher, bool &word_complete,
    StringList &matches) {
  bool handled = false;

  if (completion_mask & eCustomCompletion)
    return false;

  for (int i = 0;; i++) {
    if (g_common_completions[i].type == eNoCompletion)
      break;
    else if ((g_common_completions[i].type & completion_mask) ==
                 g_common_completions[i].type &&
             g_common_completions[i].callback != nullptr) {
      handled = true;
      g_common_completions[i].callback(interpreter, completion_str,
                                       match_start_point, max_return_elements,
                                       searcher, word_complete, matches);
    }
  }
  return handled;
}

int CommandCompletions::SourceFiles(CommandInterpreter &interpreter,
                                    llvm::StringRef partial_file_name,
                                    int match_start_point,
                                    int max_return_elements,
                                    SearchFilter *searcher, bool &word_complete,
                                    StringList &matches) {
  word_complete = true;
  // Find some way to switch "include support files..."
  SourceFileCompleter completer(interpreter, false, partial_file_name,
                                match_start_point, max_return_elements,
                                matches);

  if (searcher == nullptr) {
    lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
    SearchFilterForUnconstrainedSearches null_searcher(target_sp);
    completer.DoCompletion(&null_searcher);
  } else {
    completer.DoCompletion(searcher);
  }
  return matches.GetSize();
}

typedef struct DiskFilesOrDirectoriesBaton {
  const char *remainder;
  char *partial_name_copy;
  bool only_directories;
  bool *saw_directory;
  StringList *matches;
  char *end_ptr;
  size_t baselen;
} DiskFilesOrDirectoriesBaton;

FileSpec::EnumerateDirectoryResult
DiskFilesOrDirectoriesCallback(void *baton, FileSpec::FileType file_type,
                               const FileSpec &spec) {
  const char *name = spec.GetFilename().AsCString();

  const DiskFilesOrDirectoriesBaton *parameters =
      (DiskFilesOrDirectoriesBaton *)baton;
  char *end_ptr = parameters->end_ptr;
  char *partial_name_copy = parameters->partial_name_copy;
  const char *remainder = parameters->remainder;

  // Omit ".", ".." and any . files if the match string doesn't start with .
  if (name[0] == '.') {
    if (name[1] == '\0')
      return FileSpec::eEnumerateDirectoryResultNext;
    else if (name[1] == '.' && name[2] == '\0')
      return FileSpec::eEnumerateDirectoryResultNext;
    else if (remainder[0] != '.')
      return FileSpec::eEnumerateDirectoryResultNext;
  }

  // If we found a directory, we put a "/" at the end of the name.

  if (remainder[0] == '\0' || strstr(name, remainder) == name) {
    if (strlen(name) + parameters->baselen >= PATH_MAX)
      return FileSpec::eEnumerateDirectoryResultNext;

    strcpy(end_ptr, name);

    bool isa_directory = false;
    if (file_type == FileSpec::eFileTypeDirectory)
      isa_directory = true;
    else if (file_type == FileSpec::eFileTypeSymbolicLink) {
      if (FileSpec(partial_name_copy, false).IsDirectory())
        isa_directory = true;
    }

    if (isa_directory) {
      *parameters->saw_directory = true;
      size_t len = strlen(parameters->partial_name_copy);
      partial_name_copy[len] = '/';
      partial_name_copy[len + 1] = '\0';
    }
    if (parameters->only_directories && !isa_directory)
      return FileSpec::eEnumerateDirectoryResultNext;
    parameters->matches->AppendString(partial_name_copy);
  }

  return FileSpec::eEnumerateDirectoryResultNext;
}

static int DiskFilesOrDirectories(llvm::StringRef partial_file_name,
                                  bool only_directories, bool &saw_directory,
                                  StringList &matches) {
  // I'm going to  use the "glob" function with GLOB_TILDE for user directory
  // expansion.
  // If it is not defined on your host system, you'll need to implement it
  // yourself...

  size_t partial_name_len = partial_file_name.size();

  if (partial_name_len >= PATH_MAX)
    return matches.GetSize();

  // This copy of the string will be cut up into the directory part, and the
  // remainder.  end_ptr below will point to the place of the remainder in this
  // string.  Then when we've resolved the containing directory, and opened it,
  // we'll read the directory contents and overwrite the partial_name_copy
  // starting from end_ptr with each of the matches.  Thus we will preserve the
  // form the user originally typed.

  char partial_name_copy[PATH_MAX];
  memcpy(partial_name_copy, partial_file_name.data(), partial_name_len);
  partial_name_copy[partial_name_len] = '\0';

  // We'll need to save a copy of the remainder for comparison, which we do
  // here.
  char remainder[PATH_MAX];

  // end_ptr will point past the last / in partial_name_copy, or if there is no
  // slash to the beginning of the string.
  char *end_ptr;

  end_ptr = strrchr(partial_name_copy, '/');

  // This will store the resolved form of the containing directory
  llvm::SmallString<64> containing_part;

  if (end_ptr == nullptr) {
    // There's no directory.  If the thing begins with a "~" then this is a bare
    // user name.
    if (*partial_name_copy == '~') {
      // Nothing here but the user name.  We could just put a slash on the end,
      // but for completeness sake we'll resolve the user name and only put a
      // slash
      // on the end if it exists.
      llvm::SmallString<64> resolved_username(partial_name_copy);
      FileSpec::ResolveUsername(resolved_username);

      // Not sure how this would happen, a username longer than PATH_MAX?
      // Still...
      if (resolved_username.size() == 0) {
        // The user name didn't resolve, let's look in the password database for
        // matches.
        // The user name database contains duplicates, and is not in
        // alphabetical order, so
        // we'll use a set to manage that for us.
        FileSpec::ResolvePartialUsername(partial_name_copy, matches);
        if (matches.GetSize() > 0)
          saw_directory = true;
        return matches.GetSize();
      } else {
        // The thing exists, put a '/' on the end, and return it...
        // FIXME: complete user names here:
        partial_name_copy[partial_name_len] = '/';
        partial_name_copy[partial_name_len + 1] = '\0';
        matches.AppendString(partial_name_copy);
        saw_directory = true;
        return matches.GetSize();
      }
    } else {
      // The containing part is the CWD, and the whole string is the remainder.
      containing_part = ".";
      strcpy(remainder, partial_name_copy);
      end_ptr = partial_name_copy;
    }
  } else {
    if (end_ptr == partial_name_copy) {
      // We're completing a file or directory in the root volume.
      containing_part = "/";
    } else {
      containing_part.append(partial_name_copy, end_ptr);
    }
    // Push end_ptr past the final "/" and set remainder.
    end_ptr++;
    strcpy(remainder, end_ptr);
  }

  // Look for a user name in the containing part, and if it's there, resolve it
  // and stick the
  // result back into the containing_part:

  if (*partial_name_copy == '~') {
    FileSpec::ResolveUsername(containing_part);
    // User name doesn't exist, we're not getting any further...
    if (containing_part.empty())
      return matches.GetSize();
  }

  // Okay, containing_part is now the directory we want to open and look for
  // files:

  size_t baselen = end_ptr - partial_name_copy;

  DiskFilesOrDirectoriesBaton parameters;
  parameters.remainder = remainder;
  parameters.partial_name_copy = partial_name_copy;
  parameters.only_directories = only_directories;
  parameters.saw_directory = &saw_directory;
  parameters.matches = &matches;
  parameters.end_ptr = end_ptr;
  parameters.baselen = baselen;

  FileSpec::EnumerateDirectory(containing_part.c_str(), true, true, true,
                               DiskFilesOrDirectoriesCallback, &parameters);

  return matches.GetSize();
}

int CommandCompletions::DiskFiles(CommandInterpreter &interpreter,
                                  llvm::StringRef partial_file_name,
                                  int match_start_point,
                                  int max_return_elements,
                                  SearchFilter *searcher, bool &word_complete,
                                  StringList &matches) {
  int ret_val =
      DiskFilesOrDirectories(partial_file_name, false, word_complete, matches);
  word_complete = !word_complete;
  return ret_val;
}

int CommandCompletions::DiskDirectories(
    CommandInterpreter &interpreter, llvm::StringRef partial_file_name,
    int match_start_point, int max_return_elements, SearchFilter *searcher,
    bool &word_complete, StringList &matches) {
  int ret_val =
      DiskFilesOrDirectories(partial_file_name, true, word_complete, matches);
  word_complete = false;
  return ret_val;
}

int CommandCompletions::Modules(CommandInterpreter &interpreter,
                                llvm::StringRef partial_file_name,
                                int match_start_point, int max_return_elements,
                                SearchFilter *searcher, bool &word_complete,
                                StringList &matches) {
  word_complete = true;
  ModuleCompleter completer(interpreter, partial_file_name, match_start_point,
                            max_return_elements, matches);

  if (searcher == nullptr) {
    lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
    SearchFilterForUnconstrainedSearches null_searcher(target_sp);
    completer.DoCompletion(&null_searcher);
  } else {
    completer.DoCompletion(searcher);
  }
  return matches.GetSize();
}

int CommandCompletions::Symbols(CommandInterpreter &interpreter,
                                llvm::StringRef partial_file_name,
                                int match_start_point, int max_return_elements,
                                SearchFilter *searcher, bool &word_complete,
                                StringList &matches) {
  word_complete = true;
  SymbolCompleter completer(interpreter, partial_file_name, match_start_point,
                            max_return_elements, matches);

  if (searcher == nullptr) {
    lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
    SearchFilterForUnconstrainedSearches null_searcher(target_sp);
    completer.DoCompletion(&null_searcher);
  } else {
    completer.DoCompletion(searcher);
  }
  return matches.GetSize();
}

int CommandCompletions::SettingsNames(
    CommandInterpreter &interpreter, llvm::StringRef partial_setting_name,
    int match_start_point, int max_return_elements, SearchFilter *searcher,
    bool &word_complete, StringList &matches) {
  // Cache the full setting name list
  static StringList g_property_names;
  if (g_property_names.GetSize() == 0) {
    // Generate the full setting name list on demand
    lldb::OptionValuePropertiesSP properties_sp(
        interpreter.GetDebugger().GetValueProperties());
    if (properties_sp) {
      StreamString strm;
      properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName);
      const std::string &str = strm.GetString();
      g_property_names.SplitIntoLines(str.c_str(), str.size());
    }
  }

  size_t exact_matches_idx = SIZE_MAX;
  const size_t num_matches = g_property_names.AutoComplete(
      partial_setting_name, matches, exact_matches_idx);
  word_complete = exact_matches_idx != SIZE_MAX;
  return num_matches;
}

int CommandCompletions::PlatformPluginNames(
    CommandInterpreter &interpreter, llvm::StringRef partial_name,
    int match_start_point, int max_return_elements, SearchFilter *searcher,
    bool &word_complete, lldb_private::StringList &matches) {
  const uint32_t num_matches =
      PluginManager::AutoCompletePlatformName(partial_name, matches);
  word_complete = num_matches == 1;
  return num_matches;
}

int CommandCompletions::ArchitectureNames(
    CommandInterpreter &interpreter, llvm::StringRef partial_name,
    int match_start_point, int max_return_elements, SearchFilter *searcher,
    bool &word_complete, lldb_private::StringList &matches) {
  const uint32_t num_matches = ArchSpec::AutoComplete(partial_name, matches);
  word_complete = num_matches == 1;
  return num_matches;
}

int CommandCompletions::VariablePath(
    CommandInterpreter &interpreter, llvm::StringRef partial_name,
    int match_start_point, int max_return_elements, SearchFilter *searcher,
    bool &word_complete, lldb_private::StringList &matches) {
  return Variable::AutoComplete(interpreter.GetExecutionContext(), partial_name,
                                matches, word_complete);
}

CommandCompletions::Completer::Completer(CommandInterpreter &interpreter,
                                         llvm::StringRef completion_str,
                                         int match_start_point,
                                         int max_return_elements,
                                         StringList &matches)
    : m_interpreter(interpreter), m_completion_str(completion_str),
      m_match_start_point(match_start_point),
      m_max_return_elements(max_return_elements), m_matches(matches) {}

CommandCompletions::Completer::~Completer() = default;

//----------------------------------------------------------------------
// SourceFileCompleter
//----------------------------------------------------------------------

CommandCompletions::SourceFileCompleter::SourceFileCompleter(
    CommandInterpreter &interpreter, bool include_support_files,
    llvm::StringRef completion_str, int match_start_point,
    int max_return_elements, StringList &matches)
    : CommandCompletions::Completer(interpreter, completion_str,
                                    match_start_point, max_return_elements,
                                    matches),
      m_include_support_files(include_support_files), m_matching_files() {
  FileSpec partial_spec(m_completion_str, false);
  m_file_name = partial_spec.GetFilename().GetCString();
  m_dir_name = partial_spec.GetDirectory().GetCString();
}

Searcher::Depth CommandCompletions::SourceFileCompleter::GetDepth() {
  return eDepthCompUnit;
}

Searcher::CallbackReturn
CommandCompletions::SourceFileCompleter::SearchCallback(SearchFilter &filter,
                                                        SymbolContext &context,
                                                        Address *addr,
                                                        bool complete) {
  if (context.comp_unit != nullptr) {
    if (m_include_support_files) {
      FileSpecList supporting_files = context.comp_unit->GetSupportFiles();
      for (size_t sfiles = 0; sfiles < supporting_files.GetSize(); sfiles++) {
        const FileSpec &sfile_spec =
            supporting_files.GetFileSpecAtIndex(sfiles);
        const char *sfile_file_name = sfile_spec.GetFilename().GetCString();
        const char *sfile_dir_name = sfile_spec.GetFilename().GetCString();
        bool match = false;
        if (m_file_name && sfile_file_name &&
            strstr(sfile_file_name, m_file_name) == sfile_file_name)
          match = true;
        if (match && m_dir_name && sfile_dir_name &&
            strstr(sfile_dir_name, m_dir_name) != sfile_dir_name)
          match = false;

        if (match) {
          m_matching_files.AppendIfUnique(sfile_spec);
        }
      }
    } else {
      const char *cur_file_name = context.comp_unit->GetFilename().GetCString();
      const char *cur_dir_name = context.comp_unit->GetDirectory().GetCString();

      bool match = false;
      if (m_file_name && cur_file_name &&
          strstr(cur_file_name, m_file_name) == cur_file_name)
        match = true;

      if (match && m_dir_name && cur_dir_name &&
          strstr(cur_dir_name, m_dir_name) != cur_dir_name)
        match = false;

      if (match) {
        m_matching_files.AppendIfUnique(context.comp_unit);
      }
    }
  }
  return Searcher::eCallbackReturnContinue;
}

size_t
CommandCompletions::SourceFileCompleter::DoCompletion(SearchFilter *filter) {
  filter->Search(*this);
  // Now convert the filelist to completions:
  for (size_t i = 0; i < m_matching_files.GetSize(); i++) {
    m_matches.AppendString(
        m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString());
  }
  return m_matches.GetSize();
}

//----------------------------------------------------------------------
// SymbolCompleter
//----------------------------------------------------------------------

static bool regex_chars(const char comp) {
  return (comp == '[' || comp == ']' || comp == '(' || comp == ')' ||
          comp == '{' || comp == '}' || comp == '+' || comp == '.' ||
          comp == '*' || comp == '|' || comp == '^' || comp == '$' ||
          comp == '\\' || comp == '?');
}

CommandCompletions::SymbolCompleter::SymbolCompleter(
    CommandInterpreter &interpreter, llvm::StringRef completion_str,
    int match_start_point, int max_return_elements, StringList &matches)
    : CommandCompletions::Completer(interpreter, completion_str,
                                    match_start_point, max_return_elements,
                                    matches) {
  std::string regex_str;
  if (!completion_str.empty()) {
    regex_str.append("^");
    regex_str.append(completion_str);
  } else {
    // Match anything since the completion string is empty
    regex_str.append(".");
  }
  std::string::iterator pos =
      find_if(regex_str.begin() + 1, regex_str.end(), regex_chars);
  while (pos < regex_str.end()) {
    pos = regex_str.insert(pos, '\\');
    pos = find_if(pos + 2, regex_str.end(), regex_chars);
  }
  m_regex.Compile(regex_str);
}

Searcher::Depth CommandCompletions::SymbolCompleter::GetDepth() {
  return eDepthModule;
}

Searcher::CallbackReturn CommandCompletions::SymbolCompleter::SearchCallback(
    SearchFilter &filter, SymbolContext &context, Address *addr,
    bool complete) {
  if (context.module_sp) {
    SymbolContextList sc_list;
    const bool include_symbols = true;
    const bool include_inlines = true;
    const bool append = true;
    context.module_sp->FindFunctions(m_regex, include_symbols, include_inlines,
                                     append, sc_list);

    SymbolContext sc;
    // Now add the functions & symbols to the list - only add if unique:
    for (uint32_t i = 0; i < sc_list.GetSize(); i++) {
      if (sc_list.GetContextAtIndex(i, sc)) {
        ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled);
        if (!func_name.IsEmpty())
          m_match_set.insert(func_name);
      }
    }
  }
  return Searcher::eCallbackReturnContinue;
}

size_t CommandCompletions::SymbolCompleter::DoCompletion(SearchFilter *filter) {
  filter->Search(*this);
  collection::iterator pos = m_match_set.begin(), end = m_match_set.end();
  for (pos = m_match_set.begin(); pos != end; pos++)
    m_matches.AppendString((*pos).GetCString());

  return m_matches.GetSize();
}

//----------------------------------------------------------------------
// ModuleCompleter
//----------------------------------------------------------------------
CommandCompletions::ModuleCompleter::ModuleCompleter(
    CommandInterpreter &interpreter, llvm::StringRef completion_str,
    int match_start_point, int max_return_elements, StringList &matches)
    : CommandCompletions::Completer(interpreter, completion_str,
                                    match_start_point, max_return_elements,
                                    matches) {
  FileSpec partial_spec(m_completion_str, false);
  m_file_name = partial_spec.GetFilename().GetCString();
  m_dir_name = partial_spec.GetDirectory().GetCString();
}

Searcher::Depth CommandCompletions::ModuleCompleter::GetDepth() {
  return eDepthModule;
}

Searcher::CallbackReturn CommandCompletions::ModuleCompleter::SearchCallback(
    SearchFilter &filter, SymbolContext &context, Address *addr,
    bool complete) {
  if (context.module_sp) {
    const char *cur_file_name =
        context.module_sp->GetFileSpec().GetFilename().GetCString();
    const char *cur_dir_name =
        context.module_sp->GetFileSpec().GetDirectory().GetCString();

    bool match = false;
    if (m_file_name && cur_file_name &&
        strstr(cur_file_name, m_file_name) == cur_file_name)
      match = true;

    if (match && m_dir_name && cur_dir_name &&
        strstr(cur_dir_name, m_dir_name) != cur_dir_name)
      match = false;

    if (match) {
      m_matches.AppendString(cur_file_name);
    }
  }
  return Searcher::eCallbackReturnContinue;
}

size_t CommandCompletions::ModuleCompleter::DoCompletion(SearchFilter *filter) {
  filter->Search(*this);
  return m_matches.GetSize();
}
