/*===-- CIndexDiagnostics.cpp - Diagnostics C Interface ---------*- C++ -*-===*\
|*                                                                            *|
|*                     The LLVM Compiler Infrastructure                       *|
|*                                                                            *|
|* This file is distributed under the University of Illinois Open Source      *|
|* License. See LICENSE.TXT for details.                                      *|
|*                                                                            *|
|*===----------------------------------------------------------------------===*|
|*                                                                            *|
|* Implements the diagnostic functions of the Clang C interface.              *|
|*                                                                            *|
\*===----------------------------------------------------------------------===*/
#include "CIndexDiagnostic.h"
#include "CIndexer.h"
#include "CXTranslationUnit.h"
#include "CXSourceLocation.h"
#include "CXString.h"

#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"

using namespace clang;
using namespace clang::cxloc;
using namespace clang::cxstring;
using namespace llvm;

//-----------------------------------------------------------------------------
// C Interface Routines
//-----------------------------------------------------------------------------
extern "C" {

unsigned clang_getNumDiagnostics(CXTranslationUnit Unit) {
  ASTUnit *CXXUnit = static_cast<ASTUnit *>(Unit->TUData);
  return CXXUnit? CXXUnit->stored_diag_size() : 0;
}

CXDiagnostic clang_getDiagnostic(CXTranslationUnit Unit, unsigned Index) {
  ASTUnit *CXXUnit = static_cast<ASTUnit *>(Unit->TUData);
  if (!CXXUnit || Index >= CXXUnit->stored_diag_size())
    return 0;

  return new CXStoredDiagnostic(CXXUnit->stored_diag_begin()[Index],
                                CXXUnit->getASTContext().getLangOptions());
}

void clang_disposeDiagnostic(CXDiagnostic Diagnostic) {
  CXStoredDiagnostic *Stored = static_cast<CXStoredDiagnostic *>(Diagnostic);
  delete Stored;
}

CXString clang_formatDiagnostic(CXDiagnostic Diagnostic, unsigned Options) {
  if (!Diagnostic)
    return createCXString("");

  CXDiagnosticSeverity Severity = clang_getDiagnosticSeverity(Diagnostic);

  llvm::SmallString<256> Str;
  llvm::raw_svector_ostream Out(Str);
  
  if (Options & CXDiagnostic_DisplaySourceLocation) {
    // Print source location (file:line), along with optional column
    // and source ranges.
    CXFile File;
    unsigned Line, Column;
    clang_getSpellingLocation(clang_getDiagnosticLocation(Diagnostic),
                              &File, &Line, &Column, 0);
    if (File) {
      CXString FName = clang_getFileName(File);
      Out << clang_getCString(FName) << ":" << Line << ":";
      clang_disposeString(FName);
      if (Options & CXDiagnostic_DisplayColumn)
        Out << Column << ":";

      if (Options & CXDiagnostic_DisplaySourceRanges) {
        unsigned N = clang_getDiagnosticNumRanges(Diagnostic);
        bool PrintedRange = false;
        for (unsigned I = 0; I != N; ++I) {
          CXFile StartFile, EndFile;
          CXSourceRange Range = clang_getDiagnosticRange(Diagnostic, I);
          
          unsigned StartLine, StartColumn, EndLine, EndColumn;
          clang_getSpellingLocation(clang_getRangeStart(Range),
                                    &StartFile, &StartLine, &StartColumn,
                                    0);
          clang_getSpellingLocation(clang_getRangeEnd(Range),
                                    &EndFile, &EndLine, &EndColumn, 0);
          
          if (StartFile != EndFile || StartFile != File)
            continue;
          
          Out << "{" << StartLine << ":" << StartColumn << "-"
              << EndLine << ":" << EndColumn << "}";
          PrintedRange = true;
        }
        if (PrintedRange)
          Out << ":";
      }
      
      Out << " ";
    }
  }

  /* Print warning/error/etc. */
  switch (Severity) {
  case CXDiagnostic_Ignored: assert(0 && "impossible"); break;
  case CXDiagnostic_Note: Out << "note: "; break;
  case CXDiagnostic_Warning: Out << "warning: "; break;
  case CXDiagnostic_Error: Out << "error: "; break;
  case CXDiagnostic_Fatal: Out << "fatal error: "; break;
  }

  CXString Text = clang_getDiagnosticSpelling(Diagnostic);
  if (clang_getCString(Text))
    Out << clang_getCString(Text);
  else
    Out << "<no diagnostic text>";
  clang_disposeString(Text);
  
  if (Options & (CXDiagnostic_DisplayOption | CXDiagnostic_DisplayCategoryId |
                 CXDiagnostic_DisplayCategoryName)) {
    bool NeedBracket = true;
    bool NeedComma = false;

    if (Options & CXDiagnostic_DisplayOption) {
      CXString OptionName = clang_getDiagnosticOption(Diagnostic, 0);
      if (const char *OptionText = clang_getCString(OptionName)) {
        if (OptionText[0]) {
          Out << " [" << OptionText;
          NeedBracket = false;
          NeedComma = true;
        }
      }
      clang_disposeString(OptionName);
    }
    
    if (Options & (CXDiagnostic_DisplayCategoryId | 
                   CXDiagnostic_DisplayCategoryName)) {
      if (unsigned CategoryID = clang_getDiagnosticCategory(Diagnostic)) {
        if (Options & CXDiagnostic_DisplayCategoryId) {
          if (NeedBracket)
            Out << " [";
          if (NeedComma)
            Out << ", ";
          Out << CategoryID;
          NeedBracket = false;
          NeedComma = true;
        }
        
        if (Options & CXDiagnostic_DisplayCategoryName) {
          CXString CategoryName = clang_getDiagnosticCategoryName(CategoryID);
          if (NeedBracket)
            Out << " [";
          if (NeedComma)
            Out << ", ";
          Out << clang_getCString(CategoryName);
          NeedBracket = false;
          NeedComma = true;
          clang_disposeString(CategoryName);
        }
      }
    }
    
    if (!NeedBracket)
      Out << "]";
  }
  
  return createCXString(Out.str(), true);
}

unsigned clang_defaultDiagnosticDisplayOptions() {
  return CXDiagnostic_DisplaySourceLocation | CXDiagnostic_DisplayColumn |
         CXDiagnostic_DisplayOption;
}

enum CXDiagnosticSeverity clang_getDiagnosticSeverity(CXDiagnostic Diag) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag)
    return CXDiagnostic_Ignored;

  switch (StoredDiag->Diag.getLevel()) {
  case Diagnostic::Ignored: return CXDiagnostic_Ignored;
  case Diagnostic::Note:    return CXDiagnostic_Note;
  case Diagnostic::Warning: return CXDiagnostic_Warning;
  case Diagnostic::Error:   return CXDiagnostic_Error;
  case Diagnostic::Fatal:   return CXDiagnostic_Fatal;
  }

  llvm_unreachable("Invalid diagnostic level");
  return CXDiagnostic_Ignored;
}

CXSourceLocation clang_getDiagnosticLocation(CXDiagnostic Diag) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag || StoredDiag->Diag.getLocation().isInvalid())
    return clang_getNullLocation();

  return translateSourceLocation(StoredDiag->Diag.getLocation().getManager(),
                                 StoredDiag->LangOpts,
                                 StoredDiag->Diag.getLocation());
}

CXString clang_getDiagnosticSpelling(CXDiagnostic Diag) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag)
    return createCXString("");

  return createCXString(StoredDiag->Diag.getMessage(), false);
}

CXString clang_getDiagnosticOption(CXDiagnostic Diag, CXString *Disable) {
  if (Disable)
    *Disable = createCXString("");
  
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag)
    return createCXString("");
  
  unsigned ID = StoredDiag->Diag.getID();
  llvm::StringRef Option = DiagnosticIDs::getWarningOptionForDiag(ID);
  if (!Option.empty()) {
    if (Disable)
      *Disable = createCXString((llvm::Twine("-Wno-") + Option).str());
    return createCXString((llvm::Twine("-W") + Option).str());
  }
  
  if (ID == diag::fatal_too_many_errors) {
    if (Disable)
      *Disable = createCXString("-ferror-limit=0");
    return createCXString("-ferror-limit=");
  }
  
  bool EnabledByDefault;
  if (DiagnosticIDs::isBuiltinExtensionDiag(ID, EnabledByDefault) &&
      !EnabledByDefault)
    return createCXString("-pedantic");

  return createCXString("");
}

unsigned clang_getDiagnosticCategory(CXDiagnostic Diag) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag)
    return 0;

  return DiagnosticIDs::getCategoryNumberForDiag(StoredDiag->Diag.getID());
}
  
CXString clang_getDiagnosticCategoryName(unsigned Category) {
  return createCXString(DiagnosticIDs::getCategoryNameFromID(Category));
}
  
unsigned clang_getDiagnosticNumRanges(CXDiagnostic Diag) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag || StoredDiag->Diag.getLocation().isInvalid())
    return 0;

  return StoredDiag->Diag.range_size();
}

CXSourceRange clang_getDiagnosticRange(CXDiagnostic Diag, unsigned Range) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag || Range >= StoredDiag->Diag.range_size() ||
      StoredDiag->Diag.getLocation().isInvalid())
    return clang_getNullRange();

  return translateSourceRange(StoredDiag->Diag.getLocation().getManager(),
                              StoredDiag->LangOpts,
                              StoredDiag->Diag.range_begin()[Range]);
}

unsigned clang_getDiagnosticNumFixIts(CXDiagnostic Diag) {
  CXStoredDiagnostic *StoredDiag = static_cast<CXStoredDiagnostic *>(Diag);
  if (!StoredDiag)
    return 0;

  return StoredDiag->Diag.fixit_size();
}

CXString clang_getDiagnosticFixIt(CXDiagnostic Diagnostic, unsigned FixIt,
                                  CXSourceRange *ReplacementRange) {
  CXStoredDiagnostic *StoredDiag
    = static_cast<CXStoredDiagnostic *>(Diagnostic);
  if (!StoredDiag || FixIt >= StoredDiag->Diag.fixit_size() ||
      StoredDiag->Diag.getLocation().isInvalid()) {
    if (ReplacementRange)
      *ReplacementRange = clang_getNullRange();

    return createCXString("");
  }

  const FixItHint &Hint = StoredDiag->Diag.fixit_begin()[FixIt];
  if (ReplacementRange) {
    // Create a range that covers the entire replacement (or
    // removal) range, adjusting the end of the range to point to
    // the end of the token.
    *ReplacementRange
        = translateSourceRange(StoredDiag->Diag.getLocation().getManager(),
                                StoredDiag->LangOpts,
                                Hint.RemoveRange);
  }

  return createCXString(Hint.CodeToInsert);
}

} // end extern "C"