// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2013  The 'ac++' developers (see aspectc.org)
//                                                                
// This program is free software;  you can redistribute it and/or 
// modify it under the terms of the GNU General Public License as 
// published by the Free Software Foundation; either version 2 of 
// the License, or (at your option) any later version.            
//                                                                
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.                   
//                                                                
// You should have received a copy of the GNU General Public      
// License along with this program; if not, write to the Free     
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 


#ifndef __ClangErrorStream_h__
#define __ClangErrorStream_h__

#include "clang/Frontend/CompilerInstance.h"

#include <iostream>
#include <sstream>

enum ClangErrorSeverity { sev_none, sev_message, sev_warning, sev_error, sev_fatal };
enum ClangErrorStreamEnd { endMessage };

class ClangErrorStream {
  clang::CompilerInstance *ci_;
  ClangErrorSeverity max_severity_;
  ClangErrorSeverity curr_severity_;
  std::ostringstream msg_;
  unsigned msg_ids_[5]; // carefull: this must be the number of enumerators in ClangErrorSeverity
  clang::FullSourceLoc full_source_loc_;
public:
  ClangErrorStream () : ci_(nullptr), max_severity_(sev_none), curr_severity_(sev_none) {}

  void set_compiler_instance (clang::CompilerInstance *ci) {
    ci_ = ci;
    auto &diag = ci->getDiagnostics();
    msg_ids_[sev_none] = diag.getCustomDiagID(clang::DiagnosticsEngine::Remark, "%0");
    msg_ids_[sev_message] = diag.getCustomDiagID(clang::DiagnosticsEngine::Note, "%0");
    msg_ids_[sev_warning] = diag.getCustomDiagID(clang::DiagnosticsEngine::Warning, "%0");
    msg_ids_[sev_error] = diag.getCustomDiagID(clang::DiagnosticsEngine::Error, "%0");
    msg_ids_[sev_fatal] = diag.getCustomDiagID(clang::DiagnosticsEngine::Fatal, "%0");
  }
  clang::CompilerInstance &get_compiler_instance () const { return *ci_; }

  ClangErrorStream &operator << (clang::SourceLocation l) {
    assert (ci_ && "No CompilerInstance assigned, but SourceLocation used!");
    clang::SourceManager &sm = ci_->getSourceManager();
    full_source_loc_ = clang::FullSourceLoc(l, sm);
    return *this;
  }

  ClangErrorStream &operator << (clang::FullSourceLoc fl) {
    assert (ci_ && "No CompilerInstance assigned, but FullSourceLoc used!");
    full_source_loc_ = fl;
    return *this;
  }

  template<typename T> ClangErrorStream &operator << (T obj) {
      msg_ << obj;
      return (ClangErrorStream&)*this;
  }

  ClangErrorSeverity severity() const { return max_severity_; }
  ClangErrorSeverity set_severity(ClangErrorSeverity new_sev) {
    ClangErrorSeverity res = max_severity_;
    max_severity_ = new_sev;
    return res;
  }

  ClangErrorStream &operator << (ClangErrorSeverity severity) {
    curr_severity_ = severity;
    if (severity > max_severity_)
        max_severity_ = severity;
    return *this;
  }

  ClangErrorStream &operator << (ClangErrorStreamEnd obj) {
    if (ci_) {
      // if we are already in a context with Clang diagnostics, use it
      auto &diag = ci_->getDiagnostics();
      // save, set, and restore the source manager of the diag engine to fit with the source location
      auto &mgr = diag.getSourceManager();
      diag.setSourceManager(&const_cast<clang::SourceManager&>(full_source_loc_.getManager()));
      diag.Report(full_source_loc_, msg_ids_[curr_severity_]) << msg_.str();
      diag.setSourceManager(&mgr);
    }
    else {
      // otherwise fall back to 'cerr'
      static const char *sev[] = { "none", "message", "warning", "error", "fatal" };
      if (curr_severity_ > sev_none)
        std::cerr << sev[curr_severity_] << ": ";
      std::cerr << msg_.str() << std::endl;
    }
    msg_.str(""); msg_.clear();
    curr_severity_ = sev_none;
    full_source_loc_ = clang::FullSourceLoc();
    return *this;
  }

};

#endif // __ClangErrorStream_h__
