//===--- StandaloneDiagnostic.h - Serializable Diagnostic ------------- ---===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "clang/Frontend/StandaloneDiagnostic.h"
#include "clang/Lex/Lexer.h"

namespace clang {

StandaloneDiagnostic::SourceOffsetRange::SourceOffsetRange(
    CharSourceRange Range, const SourceManager &SrcMgr,
    const LangOptions &LangOpts) {
  const auto FileRange = Lexer::makeFileCharRange(Range, SrcMgr, LangOpts);
  Begin = SrcMgr.getFileOffset(FileRange.getBegin());
  End = SrcMgr.getFileOffset(FileRange.getEnd());
}

StandaloneDiagnostic::StandaloneFixIt::StandaloneFixIt(
    const SourceManager &SrcMgr, const LangOptions &LangOpts,
    const FixItHint &FixIt)
    : RemoveRange(FixIt.RemoveRange, SrcMgr, LangOpts),
      InsertFromRange(FixIt.InsertFromRange, SrcMgr, LangOpts),
      CodeToInsert(FixIt.CodeToInsert),
      BeforePreviousInsertions(FixIt.BeforePreviousInsertions) {}

StandaloneDiagnostic::StandaloneDiagnostic(const LangOptions &LangOpts,
                                           const StoredDiagnostic &InDiag)
    : Level(InDiag.getLevel()), ID(InDiag.getID()),
      Message(InDiag.getMessage()) {
  const FullSourceLoc &FullLoc = InDiag.getLocation();
  // This is not an invalid diagnostic; invalid SourceLocations are used to
  // represent diagnostics without a specific SourceLocation.
  if (FullLoc.isInvalid())
    return;

  const auto &SrcMgr = FullLoc.getManager();
  FileKind = SrcMgr.getFileCharacteristic(static_cast<SourceLocation>(FullLoc));
  const auto FileLoc = SrcMgr.getFileLoc(static_cast<SourceLocation>(FullLoc));
  FileOffset = SrcMgr.getFileOffset(FileLoc);
  Filename = SrcMgr.getFilename(FileLoc);
  assert(!Filename.empty() && "diagnostic with location has no source file?");

  Ranges.reserve(InDiag.getRanges().size());
  for (const auto &Range : InDiag.getRanges())
    Ranges.emplace_back(Range, SrcMgr, LangOpts);

  FixIts.reserve(InDiag.getFixIts().size());
  for (const auto &FixIt : InDiag.getFixIts())
    FixIts.emplace_back(SrcMgr, LangOpts, FixIt);
}

StoredDiagnostic
translateStandaloneDiag(FileManager &FileMgr, SourceManager &SrcMgr,
                        const StandaloneDiagnostic &StandaloneDiag,
                        llvm::StringMap<SourceLocation> &SrcLocCache) {
  const auto FileRef = FileMgr.getOptionalFileRef(StandaloneDiag.Filename);
  if (!FileRef)
    return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID,
                            StandaloneDiag.Message);

  // Try to get FileLoc from cache first
  SourceLocation FileLoc;
  auto It = SrcLocCache.find(StandaloneDiag.Filename);
  if (It != SrcLocCache.end()) {
    FileLoc = It->getValue();
  }

  // Cache miss - compute and cache the location
  if (FileLoc.isInvalid()) {
    const auto FileID =
        SrcMgr.getOrCreateFileID(*FileRef, StandaloneDiag.FileKind);
    FileLoc = SrcMgr.getLocForStartOfFile(FileID);

    if (FileLoc.isInvalid())
      return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID,
                              StandaloneDiag.Message);

    SrcLocCache[StandaloneDiag.Filename] = FileLoc;
  }

  const auto DiagLoc = FileLoc.getLocWithOffset(StandaloneDiag.FileOffset);
  const FullSourceLoc Loc(DiagLoc, SrcMgr);

  auto ConvertOffsetRange =
      [&](const StandaloneDiagnostic::SourceOffsetRange &Range) {
        return CharSourceRange(
            SourceRange(FileLoc.getLocWithOffset(Range.Begin),
                        FileLoc.getLocWithOffset(Range.End)),
            /*IsTokenRange*/ false);
      };

  SmallVector<CharSourceRange, 4> TranslatedRanges;
  TranslatedRanges.reserve(StandaloneDiag.Ranges.size());
  transform(StandaloneDiag.Ranges, std::back_inserter(TranslatedRanges),
            ConvertOffsetRange);

  SmallVector<FixItHint, 2> TranslatedFixIts;
  TranslatedFixIts.reserve(StandaloneDiag.FixIts.size());
  for (const auto &FixIt : StandaloneDiag.FixIts) {
    FixItHint TranslatedFixIt;
    TranslatedFixIt.CodeToInsert = FixIt.CodeToInsert;
    TranslatedFixIt.RemoveRange = ConvertOffsetRange(FixIt.RemoveRange);
    TranslatedFixIt.InsertFromRange = ConvertOffsetRange(FixIt.InsertFromRange);
    TranslatedFixIt.BeforePreviousInsertions = FixIt.BeforePreviousInsertions;
    TranslatedFixIts.push_back(std::move(TranslatedFixIt));
  }

  return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID,
                          StandaloneDiag.Message, Loc, TranslatedRanges,
                          TranslatedFixIts);
}

} // namespace clang
