/*****
 * errormsg.h
 * Andy Hammerlindl 2002/06/17
 *
 * Used in all phases of the compiler to give error messages.
 *****/

#ifndef ERRORMSG_H
#define ERRORMSG_H

#include <iostream>
#include <exception>
#include "common.h"
#include "settings.h"
#include "symbolmaps.h"

using std::ostream;

struct handled_error : std::exception {}; // Exception to process next file.
struct interrupted : std::exception {};   // Exception to interrupt execution.
struct quit : std::exception {};          // Exception to quit current operation.
struct EofException : std::exception {};           // Exception to exit interactive mode.

class fileinfo : public gc {
  string filename;
  size_t lineNum;

public:
  fileinfo(string filename, size_t lineNum=1)
    : filename(filename), lineNum(lineNum) {}

  size_t line() const
  {
    return lineNum;
  }

  string name() const {
    return filename;
  }

  // The filename without the directory and without the '.asy' suffix.
  // Note that this assumes name are separated by a forward slash.
  string moduleName() const {
    size_t start = filename.rfind('/');
    if (start == filename.npos)
      start = 0;
    else
      // Step over slash.
      ++start;

    size_t end = filename.rfind(".asy");
    if (end != filename.size() - 4)
      end = filename.size();

    return filename.substr(start, end-start);
  }

  // Specifies a newline symbol at the character position given.
  void newline() {
    ++lineNum;
  }

};

inline bool operator == (const fileinfo& a, const fileinfo& b)
{
  return a.line() == b.line() && a.name() == b.name();
}

class position : public gc {
  fileinfo *file;
  size_t line;
  size_t column;

public:
  void init(fileinfo *f, Int p) {
    file = f;
    if (file) {
      line = file->line();
      column = p;
    } else {
      line = column = 0;
    }
  }

  string filename() const {
    return file ? file->name() : "";
  }

  size_t Line() const {
    return line;
  }

  size_t Column() const {
    return column;
  }

  position shift(unsigned int offset) const {
    position P=*this;
    P.line -= offset;
    return P;
  }

  std::pair<size_t,size_t>LineColumn() const {
    return std::pair<size_t,size_t>(line,column);
  }

  bool match(const string& s) {
    return file && file->name() == s;
  }

  bool match(size_t l) {
    return line == l;
  }

  bool matchColumn(size_t c) {
    return column == c;
  }

  bool operator! () const
  {
    return (file == 0);
  }

  friend ostream& operator << (ostream& out, const position& pos);

  typedef std::pair<size_t, size_t> posInFile;
  typedef std::pair<std::string, posInFile> filePos;

  explicit operator AsymptoteLsp::filePos()
  {
    return std::make_pair((std::string) file->name().c_str(),LineColumn());
  }

  void print(ostream& out) const
  {
    if (file) {
      out << file->name() << ":" << line << "." << column;
    }
  }

  // Write out just the module name and line number.
  void printTerse(ostream& out) const
  {
    if (file) {
      out << file->moduleName() << ":" << line;
    }
  }
};

extern position nullPos;

struct nullPosInitializer {
  nullPosInitializer() {nullPos.init(NULL,0);}
};

inline bool operator == (const position& a, const position& b)
{
  return a.Line() == b.Line() && a.Column() == b.Column() &&
    a.filename() == b.filename();
}

string warning(string s);

enum class ErrorMode
{
  SUPPRESS,// Suppress warnings and errors.
  NORMAL,
  FORCE,// Like normal mode, but ignores attempts to change the mode.
};

class errorstream {
  ostream& out;
  bool anyErrors;
  bool anyWarnings;
  bool floating;        // Was a message output without a terminating newline?

  // Is there an error that warrants the asy process to return 1 instead of 0?
  bool anyStatusErrors;

  ErrorMode mode;
  void setMode(ErrorMode newMode)
  {
    if (mode != ErrorMode::FORCE)
      mode= newMode;
  }

public:
  static bool interrupt; // Is there a pending interrupt?

  using traceback_t = mem::list<position> ;
  traceback_t traceback;

  errorstream(ostream& out = cerr)
    : out(out), anyErrors(false), anyWarnings(false), floating(false),
      anyStatusErrors(false), mode(ErrorMode::NORMAL) {}


  void clear();

  void message(position pos, const string& s);

  void Interrupt(bool b) {
    interrupt=b;
  }

  // An error is encountered, not in the user's code, but in the way the
  // compiler works!  This may be augmented in the future with a message
  // to contact the compiler writers.
  void compiler();
  void compiler(position pos);

  // An error encountered when running compiled code.  This method does
  // not stop the executable, but the executable should be stopped
  // shortly after calling this method.
  void runtime(position pos);

  // Errors encountered when compiling making it impossible to run the code.
  void error(position pos);

  // Indicate potential problems in the code, but the code is still usable.
  void warning(position pos);
  void warning(position pos, string s);

  // Single a fatal error and execute the main process.
  void fatal(position pos);

  // Print out position in code to aid debugging.
  void trace(position pos);

  // Sends stuff to out to print.
  // NOTE: May later make it do automatic line breaking for long messages.
  template<class T>
  errorstream& operator << (const T& x) {
    if (mode != ErrorMode::SUPPRESS) {
      flush(out);
      out << x;
    }
    return *this;
  }

  // Reporting errors to the stream may be incomplete.  This draws the
  // appropriate newlines or file excerpts that may be needed at the end.
  void sync(bool reportTraceback=false);

  void cont();

  bool errors() const {
    return anyErrors;
  }

  bool warnings() const {
    return anyWarnings || errors();
  }

  void statusError() {
    anyStatusErrors=true;
  }

  // Returns true if no errors have occurred that should be reported by the
  // return value of the process.
  bool processStatus() const {
    return !anyStatusErrors;
  }

  bool isSuppressed() const {
    return mode == ErrorMode::SUPPRESS;
  }

  class ModeGuard
  {
    errorstream& es;
    ErrorMode oldMode;

  public:
    ModeGuard(errorstream& es, ErrorMode newMode) : es(es), oldMode(es.mode)
    {
      es.setMode(newMode);
    }
    ~ModeGuard() { es.setMode(oldMode); }

    ModeGuard(const ModeGuard&) = delete;
    ModeGuard& operator=(const ModeGuard&) = delete;
    ModeGuard(ModeGuard&&) = delete;
    ModeGuard& operator=(ModeGuard&&) = delete;
  };

  ModeGuard modeGuard(ErrorMode newMode) { return ModeGuard(*this, newMode); }
};

extern errorstream em;
void outOfMemory();

GC_DECLARE_PTRFREE(nullPosInitializer);

#endif
