//===- Diagnostic.cpp -----------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/LD/Diagnostic.h>
#include <llvm/Support/ErrorHandling.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/ADT/Twine.h>
#include <ctype.h>
#include <algorithm>

using namespace mcld;

//===----------------------------------------------------------------------===//
//  Diagnostic
Diagnostic::Diagnostic(DiagnosticEngine& pEngine)
  : m_Engine(pEngine) {
}

Diagnostic::~Diagnostic()
{
}

// format - format this diagnostic into string, subsituting the formal
// arguments. The result is appended at on the pOutStr.
void Diagnostic::format(std::string& pOutStr) const
{
  // we've not implemented DWARF LOC messages yet. So, keep pIsLoC false 
  llvm::StringRef desc = m_Engine.infoMap().getDescription(getID(), false);

  format(desc.begin(), desc.end(), pOutStr);
}

const char* Diagnostic::findMatch(char pVal,
                                  const char* pBegin, const char* pEnd ) const
{
  unsigned int depth = 0;
  for (; pBegin != pEnd; ++pBegin) {
    if (0 == depth && *pBegin == pVal)
      return pBegin;
    if (0 != depth && *pBegin == '}')
      --depth;

    if ('%' == *pBegin) {
      ++pBegin;
      if (pBegin == pEnd)
        break;

      if (!isdigit(*pBegin) && !ispunct(*pBegin)) {
        ++pBegin;
        while (pBegin != pEnd && !isdigit(*pBegin) && *pBegin != '{')
          ++pBegin;

        if (pBegin == pEnd)
          break;
        if ('{' == *pBegin)
          ++depth;
      }
    }
  } // end of for
  return pEnd;
}

// format - format the given formal string, subsituting the formal
// arguments. The result is appended at on the pOutStr.
void Diagnostic::format(const char* pBegin, const char* pEnd,
                        std::string& pOutStr) const
{
  const char* cur_char = pBegin;
  while (cur_char != pEnd) {
    if ('%' != *cur_char) {
      const char* new_end = std::find(cur_char, pEnd, '%');
      pOutStr.append(cur_char, new_end);
      cur_char = new_end;
      continue;
    }
    else if (ispunct(cur_char[1])) {
      pOutStr.push_back(cur_char[1]);  // %% -> %.
      cur_char += 2;
      continue;
    }

    // skip the %.
    ++cur_char;

    const char* modifier = NULL;
    size_t modifier_len = 0;

    // we get a modifier
    if (!isdigit(*cur_char)) {
      modifier = cur_char;
      while (*cur_char == '-' || (*cur_char >= 'a' && *cur_char <= 'z'))
        ++cur_char;
      modifier_len = cur_char - modifier;

      // we get an argument
      if ('{' == *cur_char) {
        ++cur_char; // skip '{'
        cur_char = findMatch('}', cur_char, pEnd);

        if (cur_char == pEnd) {
          // DIAG's format error
          llvm::report_fatal_error(llvm::Twine("Mismatched {} in the diagnostic: ") +
                                   llvm::Twine(getID()));
        }

        ++cur_char; // skip '}'
      }
    }
    if (!isdigit(*cur_char)) {
      llvm::report_fatal_error(llvm::Twine("In diagnostic: ") +
                               llvm::Twine(getID()) + llvm::Twine(": ") +
                               llvm::Twine(pBegin) +
                               llvm::Twine("\nNo given arugment number:\n"));
    }

    unsigned int arg_no = *cur_char - '0';
    ++cur_char; // skip argument number

    DiagnosticEngine::ArgumentKind kind = getArgKind(arg_no);
    switch (kind) {
      case DiagnosticEngine::ak_std_string: {
        if (0 != modifier_len) {
          llvm::report_fatal_error(llvm::Twine("In diagnostic: ") +
                                   llvm::Twine(getID()) +
                                   llvm::Twine(": ") + llvm::Twine(pBegin) +
                                   llvm::Twine("\nNo modifiers for strings yet\n"));
        }
        const std::string& str = getArgStdStr(arg_no);
        pOutStr.append(str.begin(), str.end());
        break;
      }
      case DiagnosticEngine::ak_c_string: {
        if (0 != modifier_len) {
          llvm::report_fatal_error(llvm::Twine("In diagnostic: ") +
                                   llvm::Twine(getID()) +
                                   llvm::Twine(": ") + llvm::Twine(pBegin) +
                                   llvm::Twine("\nNo modifiers for strings yet\n"));
        }
        const char* str = getArgCStr(arg_no);
        if (NULL == str)
          str = "(null)";
        pOutStr.append(str);
        break;
      }
      case DiagnosticEngine::ak_sint: {
        int val = getArgSInt(arg_no);
        llvm::raw_string_ostream(pOutStr) << val;
        break;
      }
      case DiagnosticEngine::ak_uint: {
        unsigned int val = getArgUInt(arg_no);
        llvm::raw_string_ostream(pOutStr) << val;
        break;
      }
      case DiagnosticEngine::ak_bool: {
        bool val = getArgBool(arg_no);
        if (val)
          pOutStr.append("true");
        else
          pOutStr.append("false");
        break;
      }
    } // end of switch
  } // end of while
}