//===-- LVOptions.cpp -----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This implements the LVOptions class.
//
//===----------------------------------------------------------------------===//

#include "llvm/DebugInfo/LogicalView/Core/LVOptions.h"
#include "llvm/DebugInfo/LogicalView/Core/LVReader.h"
#include "llvm/Support/Errc.h"

using namespace llvm;
using namespace llvm::logicalview;

#define DEBUG_TYPE "Options"

//===----------------------------------------------------------------------===//
// Options extracted from the command line.
//===----------------------------------------------------------------------===//
static LVOptions Options;
LVOptions *LVOptions::getOptions() { return &Options; }
void LVOptions::setOptions(LVOptions *CmdOptions) { Options = *CmdOptions; }

void LVOptions::resolveDependencies() {
  // Attributes that are classified as standard options.
  auto StandardAttributes = [&]() {
    // Set the 'standard' attribute to indicate its associated attributes.
    setAttributeStandard();

    setAttributeBase();
    setAttributeCoverage();
    setAttributeDirectories();
    setAttributeDiscriminator();
    setAttributeFilename();
    setAttributeFiles();
    setAttributeFormat();
    setAttributeLevel();
    setAttributeProducer();
    setAttributePublics();
    setAttributeRange();
    setAttributeReference();
    setAttributeZero();
  };

  // Attributes that are classified as extended options.
  auto ExtendedAttributes = [&]() {
    // Set the 'extended' attribute to indicate its associated attributes.
    setAttributeExtended();

    setAttributeArgument();
    setAttributeDiscarded();
    setAttributeEncoded();
    setAttributeGaps();
    setAttributeGenerated();
    setAttributeGlobal();
    setAttributeInserted();
    setAttributeLinkage();
    setAttributeLocal();
    setAttributeLocation();
    setAttributeOffset();
    setAttributePathname();
    setAttributeQualified();
    setAttributeQualifier();
    setAttributeRegister();
    setAttributeSubrange();
    setAttributeSystem();
    setAttributeTypename();
  };

  // '--Attribute=standard' settings.
  if (getAttributeStandard())
    StandardAttributes();

  // '--Attribute=extended' settings.
  if (getAttributeExtended())
    ExtendedAttributes();

  // '--Attribute=all' settings.
  if (getAttributeAll()) {
    StandardAttributes();
    ExtendedAttributes();
  }

  // '--attribute=pathname' supersedes '--attribute=filename'.
  if (getAttributePathname())
    resetAttributeFilename();

  // Assume '--output=text' as default
  if (!getOutputText() && !getOutputJson())
    setOutputText();

  // '--output=all' settings.
  if (getOutputAll()) {
    setOutputJson();
    setOutputSplit();
    setOutputText();
  }

  // A view split folder was specified.
  if (getOutputFolder().length())
    setOutputSplit();

  // Always use the full pathname with splitted output.
  if (getOutputSplit())
    setAttributePathname();

  // '--print=elements' settings.
  if (getPrintElements()) {
    setPrintInstructions();
    setPrintLines();
    setPrintScopes();
    setPrintSymbols();
    setPrintTypes();
  }

  // '--print=all' settings.
  if (getPrintAll()) {
    setPrintInstructions();
    setPrintLines();
    setPrintScopes();
    setPrintSizes();
    setPrintSymbols();
    setPrintSummary();
    setPrintTypes();
    setPrintWarnings();
  }

  // '--warning=all' settings.
  if (getWarningAll()) {
    setWarningCoverages();
    setWarningLines();
    setWarningLocations();
    setWarningRanges();
  }

  // '--internal=all' settings.
  if (getInternalAll()) {
    setInternalCmdline();
    setInternalID();
    setInternalIntegrity();
    setInternalNone();
    setInternalTag();
  }

  // '--compare=all' settings.
  if (getCompareAll()) {
    setCompareLines();
    setCompareScopes();
    setCompareSymbols();
    setCompareTypes();
  }

  // Compare the scopes if a request for compare symbols, types, lines.
  if (getCompareLines() || getCompareSymbols() || getCompareTypes())
    setCompareScopes();

  // Generic request for comparison.
  if (getCompareScopes())
    setCompareExecute();

  // Print any logical line (debug or instruction).
  if (getPrintInstructions() || getPrintLines())
    setPrintAnyLine();

  // Print any logical element (line, scope, symbol or type).
  if (getPrintAnyLine() || getPrintScopes() || getPrintSymbols() ||
      getPrintTypes())
    setPrintAnyElement();

  // Print 'sizes' or 'summary'.
  if (getPrintSizes() && getPrintSummary())
    setPrintSizesSummary();

  // Generic request for printing.
  if (getPrintAll() || getPrintAnyElement() || getPrintSizesSummary() ||
      getPrintWarnings())
    setPrintExecute();

  // '--reports=all' settings.
  if (getReportAll()) {
    setReportChildren();
    setReportList();
    setReportParents();
    setReportView();
  }

  // '--report=view' is a shortcut for '--report=parents,children'.
  if (getReportView()) {
    setReportChildren();
    setReportParents();
  }

  // The report will include: Parents or Children.
  if (getReportParents() || getReportChildren() || getReportView())
    setReportAnyView();

  // The report will include: List or Parents or Children.
  if (getReportList() || getReportAnyView())
    setReportExecute();

  // If a view or element comparison has been requested, the following options
  // must be set, in order to get a correct compare:
  // 1) Sort the CUs, to get a fast compare.
  // 2) Encode template instantiations, so the names include template
  //    parameter information.
  // 3) Include qualified types.
  // 4) Include any inserted abstract references.
  // 5) For added/missing elements add the '+' or '-' tags.
  if (getCompareExecute()) {
    resetPrintExecute();
    setComparePrint();
    setSortMode(LVSortMode::Line);
    setAttributeAdded();
    setAttributeArgument();
    setAttributeEncoded();
    setAttributeInserted();
    setAttributeMissing();
    setAttributeQualified();
  }

  // Enable formatting for printing (indentation, print children).
  setPrintFormatting();

  // These attributes are dependent on the capture of location information.
  if (getAttributeCoverage() || getAttributeGaps() || getAttributeRegister())
    setAttributeLocation();

  // Location information is only relevant when printing symbols.
  if (!getPrintSymbols()) {
    resetAttributeCoverage();
    resetAttributeGaps();
    resetAttributeLocation();
    resetAttributeRegister();
  }

  // Quick check for printing any element source information.
  if (getAttributeFilename() || getAttributePathname())
    setAttributeAnySource();

  // Quick check for printing any location information.
  if (getAttributeLocation() || getAttributeRange())
    setAttributeAnyLocation();

  if (getAttributeRange() || getPrintAnyLine())
    setGeneralCollectRanges();

  calculateIndentationSize();

  // Print collected command line options.
  LLVM_DEBUG({ dump(); });
}

void LVOptions::calculateIndentationSize() {
#ifndef NDEBUG
  if (getInternalID()) {
    std::string String = hexSquareString(0);
    IndentationSize += String.length();
  }
#endif
  if (getCompareExecute() && (getAttributeAdded() || getAttributeMissing()))
    ++IndentationSize;
  if (getAttributeOffset()) {
    std::string String = hexSquareString(0);
    IndentationSize += String.length();
  }
  if (getAttributeLevel()) {
    std::stringstream Stream;
    Stream.str(std::string());
    Stream << "[" << std::setfill('0') << std::setw(3) << 0 << "]";
    IndentationSize += Stream.tellp();
  }
  if (getAttributeGlobal())
    ++IndentationSize;
}

// Print the current values for all the options, after the dependencies
// has been resolved.
void LVOptions::print(raw_ostream &OS) const {
  // --attribute
  OS << "** Attributes **\n"
     << "All:           " << getAttributeAll() << ", "
     << "Argument:      " << getAttributeArgument() << ", "
     << "Base:          " << getAttributeBase() << ", "
     << "Coverage:      " << getAttributeCoverage() << "\n"
     << "Directories:   " << getAttributeDirectories() << ", "
     << "Discarded:     " << getAttributeDiscarded() << ", "
     << "Discriminator: " << getAttributeDiscriminator() << ", "
     << "Encoded:       " << getAttributeEncoded() << "\n"
     << "Extended:      " << getAttributeExtended() << ", "
     << "Filename:      " << getAttributeFilename() << ", "
     << "Files:         " << getAttributeFiles() << ", "
     << "Format:        " << getAttributeFormat() << "\n"
     << "Gaps:          " << getAttributeGaps() << ", "
     << "Generated:     " << getAttributeGenerated() << ", "
     << "Global:        " << getAttributeGlobal() << ", "
     << "Inserted:      " << getAttributeInserted() << "\n"
     << "Level:         " << getAttributeLevel() << ", "
     << "Linkage:       " << getAttributeLinkage() << ", "
     << "Local:         " << getAttributeLocal() << ", "
     << "Location:      " << getAttributeLocation() << "\n"
     << "Offset:        " << getAttributeOffset() << ", "
     << "Pathname:      " << getAttributePathname() << ", "
     << "Producer:      " << getAttributeProducer() << ", "
     << "Publics:       " << getAttributePublics() << "\n"
     << "Qualified:     " << getAttributeQualified() << ", "
     << "Qualifier:     " << getAttributeQualifier() << ", "
     << "Range:         " << getAttributeRange() << ", "
     << "Reference:     " << getAttributeReference() << "\n"
     << "Register:      " << getAttributeRegister() << ", "
     << "Standard:      " << getAttributeStandard() << ", "
     << "Subrange:      " << getAttributeSubrange() << ", "
     << "System:        " << getAttributeSystem() << "\n"
     << "Typename:      " << getAttributeTypename() << ", "
     << "Underlying:    " << getAttributeUnderlying() << ", "
     << "Zero:          " << getAttributeZero() << "\n";
  OS << "Added:         " << getAttributeAdded() << ", "
     << "AnyLocation:   " << getAttributeAnyLocation() << ", "
     << "AnySource:     " << getAttributeAnySource() << ", "
     << "Missing:       " << getAttributeMissing() << "\n"
     << "\n";

  // --compare
  OS << "** Compare **\n"
     << "All:     " << getCompareAll() << ", "
     << "Lines:   " << getCompareLines() << ", "
     << "Scopes:  " << getCompareScopes() << ", "
     << "Symbols: " << getCompareSymbols() << ", "
     << "Types:   " << getCompareTypes() << "\n";
  OS << "Context: " << getCompareContext() << ", "
     << "Execute: " << getCompareExecute() << ", "
     << "Print:   " << getComparePrint() << "\n"
     << "\n";

  // --print
  OS << "** Print **\n"
     << "All:          " << getPrintAll() << ", "
     << "Elements:     " << getPrintElements() << ", "
     << "Instructions: " << getPrintInstructions() << ", "
     << "Lines:        " << getPrintLines() << "\n"
     << "Scopes:       " << getPrintScopes() << ", "
     << "Sizes:        " << getPrintSizes() << ", "
     << "Summary:      " << getPrintSummary() << ", "
     << "Symbols:      " << getPrintSymbols() << "\n"
     << "Types:        " << getPrintTypes() << ", "
     << "Warnings:     " << getPrintWarnings() << "\n";
  OS << "AnyElemeny:   " << getPrintAnyElement() << ", "
     << "AnyLine:      " << getPrintAnyLine() << ", "
     << "Execute:      " << getPrintExecute() << ", "
     << "Formatting:   " << getPrintFormatting() << "\n"
     << "Offset:       " << getPrintOffset() << ", "
     << "SizesSummary: " << getPrintSizesSummary() << "\n"
     << "\n";

  // --report
  OS << "** Report **\n"
     << "All:      " << getReportAll() << ", "
     << "Children: " << getReportChildren() << ", "
     << "List:     " << getReportList() << ", "
     << "Parents:  " << getReportParents() << ", "
     << "View:     " << getReportView() << "\n";
  OS << "AnyView:  " << getReportAnyView() << ", "
     << "Execute:  " << getReportExecute() << "\n"
     << "\n";

  // --select
  OS << "** Select **\n"
     << "IgnoreCase:     " << getSelectIgnoreCase() << ", "
     << "UseRegex:       " << getSelectUseRegex() << ", "
     << "Execute:        " << getSelectExecute() << ", "
     << "GenericKind:    " << getSelectGenericKind() << "\n"
     << "GenericPattern: " << getSelectGenericPattern() << ", "
     << "OffsetPattern:  " << getSelectOffsetPattern() << "\n"
     << "\n";

  // --warning
  OS << "** Warning **\n"
     << "All:       " << getWarningAll() << ", "
     << "Coverage:  " << getWarningCoverages() << ", "
     << "Lines:     " << getWarningLines() << ", "
     << "Locations: " << getWarningLocations() << ", "
     << "Ranges:    " << getWarningRanges() << "\n"
     << "\n";

  // --internal
  OS << "** Internal **\n"
     << "All:       " << Options.getInternalAll() << ", "
     << "Cmdline:   " << Options.getInternalCmdline() << ", "
     << "ID:        " << Options.getInternalID() << ", "
     << "Integrity: " << Options.getInternalIntegrity() << ", "
     << "None:      " << Options.getInternalNone() << "\n"
     << "Tag:       " << Options.getInternalTag() << "\n"
     << "\n";
}

//===----------------------------------------------------------------------===//
// Logical element selection using patterns.
//===----------------------------------------------------------------------===//
LVPatterns *LVPatterns::getPatterns() {
  static LVPatterns Patterns;
  return &Patterns;
}

Error LVPatterns::createMatchEntry(LVMatchInfo &Filters, StringRef Pattern,
                                   bool IgnoreCase, bool UseRegex) {
  LVMatch Match;
  // Process pattern as regular expression.
  if (UseRegex) {
    Match.Pattern = std::string(Pattern);
    if (Pattern.size()) {
      Match.RE = std::make_shared<Regex>(Pattern, IgnoreCase ? Regex::IgnoreCase
                                                             : Regex::NoFlags);
      std::string Error;
      if (!Match.RE->isValid(Error))
        return createStringError(errc::invalid_argument,
                                 "Error in regular expression: %s",
                                 Error.c_str());

      Match.Mode = LVMatchMode::Regex;
      Filters.push_back(Match);
      return Error::success();
    }
  }

  // Process pattern as an exact string match, depending on the case.
  Match.Pattern = std::string(Pattern);
  if (Match.Pattern.size()) {
    Match.Mode = IgnoreCase ? LVMatchMode::NoCase : LVMatchMode::Match;
    Filters.push_back(Match);
  }

  return Error::success();
}

void LVPatterns::addGenericPatterns(StringSet<> &Patterns) {
  addPatterns(Patterns, GenericMatchInfo);
  if (GenericMatchInfo.size()) {
    options().setSelectGenericPattern();
    options().setSelectExecute();
  }
}

void LVPatterns::addOffsetPatterns(const LVOffsetSet &Patterns) {
  for (const LVOffset &Entry : Patterns)
    OffsetMatchInfo.push_back(Entry);
  if (OffsetMatchInfo.size()) {
    options().setSelectOffsetPattern();
    options().setSelectExecute();
  }
}

void LVPatterns::addPatterns(StringSet<> &Patterns, LVMatchInfo &Filters) {
  bool IgnoreCase = options().getSelectIgnoreCase();
  bool UseRegex = options().getSelectUseRegex();
  for (const StringSet<>::value_type &Entry : Patterns) {
    StringRef Pattern = Entry.first();
    if (Error Err = createMatchEntry(Filters, Pattern, IgnoreCase, UseRegex))
      consumeError(std::move(Err));
  }

  LLVM_DEBUG({
    dbgs() << "\nPattern Information:\n";
    for (LVMatch &Match : Filters)
      dbgs() << "Mode: "
             << (Match.Mode == LVMatchMode::Match ? "Match" : "Regex")
             << " Pattern: '" << Match.Pattern << "'\n";
  });
}

void LVPatterns::addElement(LVElement *Element) {
  // Mark any element that matches a given pattern.
  Element->setIsMatched();
  options().setSelectExecute();
  if (options().getReportList())
    getReaderCompileUnit()->addMatched(Element);
  if (options().getReportAnyView()) {
    getReaderCompileUnit()->addMatched(Element->getIsScope()
                                           ? static_cast<LVScope *>(Element)
                                           : Element->getParentScope());
    // Mark element as matched.
    if (!Element->getIsScope())
      Element->setHasPattern();
  }
}

void LVPatterns::updateReportOptions() {
  if (ElementRequest.size() || LineRequest.size() || ScopeRequest.size() ||
      SymbolRequest.size() || TypeRequest.size()) {
    options().setSelectGenericKind();
    options().setSelectExecute();
  }

  // If we have selected requests and there are no specified report options,
  // assume the 'details' option.
  if (options().getSelectExecute() && !options().getReportExecute()) {
    options().setReportExecute();
    options().setReportList();
  }
}

// Match a general pattern.
bool LVPatterns::matchPattern(StringRef Input, const LVMatchInfo &MatchInfo) {
  bool Matched = false;
  // Do not match an empty 'Input'.
  if (Input.empty())
    return Matched;
  // Traverse all match specifications.
  for (const LVMatch &Match : MatchInfo) {
    switch (Match.Mode) {
    case LVMatchMode::Match:
      Matched = Input.equals(Match.Pattern);
      break;
    case LVMatchMode::NoCase:
      Matched = Input.equals_insensitive(Match.Pattern);
      break;
    case LVMatchMode::Regex:
      Matched = Match.RE->match(Input);
      break;
    default:
      break;
    }
    // Return if we have a match.
    if (Matched)
      return true;
  }
  return Matched;
}

bool LVPatterns::printElement(const LVLine *Line) const {
  return (options().getPrintLines() && Line->getIsLineDebug()) ||
         (options().getPrintInstructions() && Line->getIsLineAssembler());
}

bool LVPatterns::printObject(const LVLocation *Location) const {
  if (options().getAttributeAll())
    return true;
  bool DoPrint = options().getAttributeAnyLocation();
  // Consider the case of filler locations.
  if (DoPrint && Location && Location->getIsGapEntry())
    DoPrint = options().getAttributeGaps();
  return DoPrint;
}

bool LVPatterns::printElement(const LVScope *Scope) const {
  // A scope will be printed depending on the following rules:
  // - Request to print scopes.
  // - Request to print any of its children.
  // - If the scope is Root or CompileUnit:
  //     Request to print summary, sizes or warnings.
  return options().getPrintScopes() ||
         (options().getPrintSymbols() && Scope->getHasSymbols()) ||
         (options().getPrintAnyLine() && Scope->getHasLines()) ||
         (options().getPrintTypes() && Scope->getHasTypes()) ||
         ((options().getPrintSizesSummary() || options().getPrintWarnings()) &&
          (Scope->getIsRoot() || Scope->getIsCompileUnit()));
}

bool LVPatterns::printElement(const LVSymbol *Symbol) const {
  // Print compiler generated symbols only if command line option.
  if (Symbol->getIsArtificial())
    return options().getAttributeGenerated() && options().getPrintSymbols();
  return options().getPrintSymbols();
}

bool LVPatterns::printElement(const LVType *Type) const {
  // Print array subranges only if print types is requested.
  if (Type->getIsSubrange())
    return options().getAttributeSubrange() && options().getPrintTypes();
  return options().getPrintTypes();
}

void LVPatterns::print(raw_ostream &OS) const {
  OS << "LVPatterns\n";
  LLVM_DEBUG(dbgs() << "Print Patterns\n");
}
