/***************************************************************************
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License (LGPL)   *
 *   as published by the Free Software Foundation; either version 2 of     *
 *   the License, or (at your option) any later version.                   *
 *   for detail see the LICENCE text file.                                 *
 *                                                                         *
 *   FreeCAD 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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with FreeCAD; if not, write to the Free Software        *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
 *   USA                                                                   *
 *                                                                         *
 ***************************************************************************/


#ifndef BASE_EXCEPTION_H
#define BASE_EXCEPTION_H

#include <csignal>
#include <string>
#include "BaseClass.h"
#include "FileInfo.h"


typedef struct _object PyObject;

/* MACROS FOR THROWING EXCEPTIONS */

/// the macros do NOT mark any message for translation
/// If you want to mark text for translation, use the QT_TRANSLATE_NOOP macro
/// with the context "Exceptions" and the right throwing macro from below (the one ending in T)
/// example:
/// THROWMT(Base::ValueError,QT_TRANSLATE_NOOP("Exceptions","The multiplicity cannot be increased beyond the degree of the B-Spline."));
///
/// N.B.: The QT_TRANSLATE_NOOP macro won't translate your string. It will just allow lupdate to identify that string for translation so that
/// if you ask for a translation (and the translator have provided one) at that time it gets translated (e.g. in the UI before showing the message
/// of the exception).

#ifdef _MSC_VER

# define THROW(exception) {exception myexcp; myexcp.setDebugInformation(__FILE__,__LINE__,__FUNCSIG__); throw myexcp;}
# define THROWM(exception, message) {exception myexcp(message); myexcp.setDebugInformation(__FILE__,__LINE__,__FUNCSIG__); throw myexcp;}
# define THROWMF_FILEEXCEPTION(message,filenameorfileinfo) {FileException myexcp(message, filenameorfileinfo); myexcp.setDebugInformation(__FILE__,__LINE__,__FUNCSIG__); throw myexcp;}

# define THROWT(exception) {exception myexcp; myexcp.setDebugInformation(__FILE__,__LINE__,__FUNCSIG__); myexcp.setTranslatable(true); throw myexcp;}
# define THROWMT(exception, message) {exception myexcp(message); myexcp.setDebugInformation(__FILE__,__LINE__,__FUNCSIG__); myexcp.setTranslatable(true); throw myexcp;}
# define THROWMFT_FILEEXCEPTION(message,filenameorfileinfo) {FileException myexcp(message, filenameorfileinfo); myexcp.setDebugInformation(__FILE__,__LINE__,__FUNCSIG__); myexcp.setTranslatable(true); throw myexcp;}

#elif defined(__GNUC__)

# define THROW(exception) {exception myexcp; myexcp.setDebugInformation(__FILE__,__LINE__,__PRETTY_FUNCTION__); throw myexcp;}
# define THROWM(exception, message) {exception myexcp(message); myexcp.setDebugInformation(__FILE__,__LINE__,__PRETTY_FUNCTION__); throw myexcp;}
# define THROWMF_FILEEXCEPTION(message,filenameorfileinfo) {FileException myexcp(message, filenameorfileinfo); myexcp.setDebugInformation(__FILE__,__LINE__,__PRETTY_FUNCTION__); throw myexcp;}

# define THROWT(exception) {exception myexcp; myexcp.setDebugInformation(__FILE__,__LINE__,__PRETTY_FUNCTION__); myexcp.setTranslatable(true); throw myexcp;}
# define THROWMT(exception, message) {exception myexcp(message); myexcp.setDebugInformation(__FILE__,__LINE__,__PRETTY_FUNCTION__); myexcp.setTranslatable(true); throw myexcp;}
# define THROWMFT_FILEEXCEPTION(message,filenameorfileinfo) {FileException myexcp(message, filenameorfileinfo); myexcp.setDebugInformation(__FILE__,__LINE__,__PRETTY_FUNCTION__); myexcp.setTranslatable(true); throw myexcp;}

#else

# define THROW(exception) {exception myexcp; myexcp.setDebugInformation(__FILE__,__LINE__,__func__); throw myexcp;}
# define THROWM(exception, message) {exception myexcp(message); myexcp.setDebugInformation(__FILE__,__LINE__,__func__); throw myexcp;}
# define THROWMF_FILEEXCEPTION(message,filenameorfileinfo) {FileException myexcp(message, filenameorfileinfo); myexcp.setDebugInformation(__FILE__,__LINE__,__func__);  throw myexcp;}

# define THROWT(exception) {exception myexcp; myexcp.setDebugInformation(__FILE__,__LINE__,__func__); myexcp.setTranslatable(true); throw myexcp;}
# define THROWMT(exception, message) {exception myexcp(message); myexcp.setDebugInformation(__FILE__,__LINE__,__func__); myexcp.setTranslatable(true); throw myexcp;}
# define THROWMFT_FILEEXCEPTION(message,filenameorfileinfo) {FileException myexcp(message, filenameorfileinfo); myexcp.setDebugInformation(__FILE__,__LINE__,__func__); myexcp.setTranslatable(true); throw myexcp;}


#endif

#define FC_THROWM(_exception,_msg) do {\
    std::stringstream ss;\
    ss << _msg;\
    THROWM(_exception,ss.str().c_str());\
}while(0)

namespace Base
{

class BaseExport Exception : public BaseClass
{
  TYPESYSTEM_HEADER();
public:

  virtual ~Exception() throw() {}

  Exception &operator=(const Exception &inst);

  virtual const char* what() const throw();

  /// Reports exception. It includes a mechanism to only report an exception once.
  virtual void ReportException () const;

  inline void setMessage(const char * sMessage);
  inline void setMessage(const std::string& sMessage);
  // what may differ from the message given by the user in
  // derived classes
  inline std::string getMessage() const;
  inline std::string getFile() const;
  inline int getLine() const;
  inline std::string getFunction() const;
  inline bool getTranslatable() const;
  inline bool getReported() const { return _isReported; }

  /// setter methods for including debug information
  /// intended to use via macro for autofilling of debugging information
  inline void setDebugInformation(const std::string & file, const int line, const std::string & function);

  inline void setTranslatable(bool translatable);

  inline void setReported(bool reported) { _isReported = reported; }

  /// returns a Python dictionary containing the exception data
  virtual PyObject * getPyObject();
  /// returns sets the exception data from a Python dictionary
  virtual void setPyObject( PyObject * pydict);

  /// returns the corresponding python exception type
  virtual PyObject * getPyExceptionType() const;
  /// Sets the Python error indicator and an error message
  virtual void setPyException() const;

protected:
 /* sMessage may be:
  * - a UI compliant string susceptible to being translated and shown to the user in the UI
  * - a very technical message not intended to be translated or shown to the user in the UI
  * The preferred way of throwing an exception is using the macros above.
  * This way, the file, line, and function are automatically inserted. */
  Exception(const char * sMessage);
  Exception(const std::string& sMessage);
  Exception();
  Exception(const Exception &inst);

protected:
  std::string _sErrMsg;
  std::string _file;
  int _line;
  std::string _function;
  bool _isTranslatable;
  mutable bool _isReported;
};


/**
 * The AbortException is thrown if a pending operation was aborted.
 * @author Werner Mayer
 */
class BaseExport AbortException : public Exception
{
  TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
  /// Construction
  AbortException(const char * sMessage);
  /// Construction
  AbortException();

  /// Destruction
  virtual ~AbortException() throw() {}
  /// Description of the exception
  const char* what() const throw() override;
  /// returns the corresponding python exception type
  PyObject * getPyExceptionType() const override;
};

/**
 * The XMLBaseException can be used to indicate any kind of XML related errors.
 * @author Werner Mayer
 */
class BaseExport XMLBaseException : public Exception
{
public:
  /// Construction
  XMLBaseException();
  XMLBaseException(const char * sMessage);
  XMLBaseException(const std::string& sMessage);

  /// Destruction
  virtual ~XMLBaseException() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The XMLParseException is thrown if parsing an XML failed.
 * @author Werner Mayer
 */
class BaseExport XMLParseException : public XMLBaseException
{
public:
  /// Construction
  XMLParseException(const char * sMessage);
  /// Construction
  XMLParseException(const std::string& sMessage);
  /// Construction
  XMLParseException();

  /// Destruction
  virtual ~XMLParseException() throw() {}
  /// Description of the exception
  const char* what() const throw() override;
  PyObject * getPyExceptionType() const override;
};

/**
 * The XMLAttributeError is thrown if a requested attribute doesn't exist.
 * @author Werner Mayer
 */
class BaseExport XMLAttributeError : public XMLBaseException
{
public:
  /// Construction
  XMLAttributeError(const char * sMessage);
  /// Construction
  XMLAttributeError(const std::string& sMessage);
  /// Construction
  XMLAttributeError();

  /// Destruction
  virtual ~XMLAttributeError() throw() {}
  /// Description of the exception
  const char* what() const throw() override;
  PyObject * getPyExceptionType() const override;
};

/** File exception handling class
 * This class is specialized to go with exception thrown in case of File IO Problems.
 * @author Juergen Riegel
 */
class BaseExport FileException : public Exception
{
public:
  /// With massage and file name
  FileException(const char * sMessage, const char * sFileName=nullptr);
  /// With massage and file name
  FileException(const char * sMessage, const FileInfo& File);
  /// standard construction
  FileException();
  /// Construction
  FileException(const FileException &inst);
  /// Destruction
  virtual ~FileException() throw() {}
  /// Assignment operator
  FileException &operator=(const FileException &inst);
  /// Description of the exception
  const char* what() const throw() override;
  /// Report generation
  void ReportException () const override;
  /// Get file name for use with translatable message
  std::string getFileName() const;
  /// returns a Python dictionary containing the exception data
  PyObject * getPyObject() override;
  /// returns sets the exception data from a Python dictionary
  void setPyObject( PyObject * pydict) override;

  PyObject * getPyExceptionType() const override;
protected:
  FileInfo file;
  // necessary   for what() legacy behaviour as it returns a buffer that
  // can not be of a temporary object to be destroyed at end of what()
  std::string _sErrMsgAndFileName;
  void setFileName(const char * sFileName=nullptr);
};

/**
 * The FileSystemError can be used to indicate errors on file system
 * e.g. if renaming of a file failed.
 * @author Werner Mayer
 */
class BaseExport FileSystemError : public Exception
{
public:
  /// Construction
  FileSystemError();
  FileSystemError(const char * sMessage);
  FileSystemError(const std::string& sMessage);
  /// Destruction
  virtual ~FileSystemError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The BadFormatError can be used to indicate errors in a data structure.
 * @author Werner Mayer
 */
class BaseExport BadFormatError : public Exception
{
public:
  /// Construction
  BadFormatError();
  BadFormatError(const char * sMessage);
  BadFormatError(const std::string& sMessage);
  /// Destruction
  virtual ~BadFormatError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The MemoryException is thrown if not enough memory can be allocated.
 * @author Werner Mayer
 */
#if defined (__GNUC__)
// It seems that the calling instance of our new handler expects a bad_alloc exception
class BaseExport MemoryException : public Exception, virtual public std::bad_alloc
#else
class BaseExport MemoryException : public Exception
#endif
{
public:
  /// Construction
  MemoryException();
  /// Construction
  MemoryException(const MemoryException &inst);
  /// Destruction
  virtual ~MemoryException() throw() {}
  /// Assignment operator
  MemoryException &operator=(const MemoryException &inst);
#if defined (__GNUC__)
  /// Description of the exception
  const char* what() const throw() override;
#endif
  PyObject * getPyExceptionType() const override;
};

/**
 * The AccessViolation can be used in an own signal handler.
 * @author Werner Mayer
 */
class BaseExport AccessViolation : public Exception
{
public:
  /// Construction
  AccessViolation();
  AccessViolation(const char * sMessage);
  AccessViolation(const std::string& sMessage);
  /// Destruction
  virtual ~AccessViolation() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The AbnormalProgramTermination can be used in an own signal handler.
 * @author Werner Mayer
 */
class BaseExport AbnormalProgramTermination : public Exception
{
public:
  /// Construction
  AbnormalProgramTermination();
  /// Construction
  AbnormalProgramTermination(const char * sMessage);
  AbnormalProgramTermination(const std::string& sMessage);
  /// Destruction
  virtual ~AbnormalProgramTermination() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The UnknownProgramOption can be used to indicate an unknown program option.
 * @author Werner Mayer
 */
class BaseExport UnknownProgramOption : public Exception
{
public:
  /// Construction
  UnknownProgramOption();
  UnknownProgramOption(const char * sMessage);
  UnknownProgramOption(const std::string& sMessage);
  /// Destruction
  virtual ~UnknownProgramOption() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The ProgramInformation can be used to show information about the program.
 * @author Werner Mayer
 */
class BaseExport ProgramInformation : public Exception
{
public:
  /// Construction
  ProgramInformation();
  ProgramInformation(const char * sMessage);
  ProgramInformation(const std::string& sMessage);

  /// Destruction
  virtual ~ProgramInformation() throw() {}
};

/**
 * The TypeError can be used to indicate the usage of a wrong type.
 * @author Werner Mayer
 */
class BaseExport TypeError : public Exception
{
public:
  /// Construction
  TypeError();
  TypeError(const char * sMessage);
  TypeError(const std::string& sMessage);
  /// Destruction
  virtual ~TypeError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The ValueError can be used to indicate the usage of a wrong value.
 * @author Werner Mayer
 */
class BaseExport ValueError : public Exception
{
public:
  /// Construction
  ValueError();
  ValueError(const char * sMessage);
  ValueError(const std::string& sMessage);
  /// Destruction
  virtual ~ValueError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The IndexError can be used when a sequence subscript is out of range.
 * @author Werner Mayer
 */
class BaseExport IndexError : public Exception
{
public:
  /// Construction
  IndexError();
  IndexError(const char * sMessage);
  IndexError(const std::string& sMessage);
  /// Destruction
  virtual ~IndexError() throw() {}
  PyObject * getPyExceptionType() const override;
};

class BaseExport NameError : public Exception
{
public:
  /// Construction
  NameError();
  NameError(const char * sMessage);
  NameError(const std::string& sMessage);
  /// Destruction
  virtual ~NameError() throw() {}
  PyObject * getPyExceptionType() const override;
};

class BaseExport ImportError : public Exception
{
public:
  /// Construction
  ImportError();
  ImportError(const char * sMessage);
  ImportError(const std::string& sMessage);
  /// Destruction
  virtual ~ImportError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The AttributeError can be used to indicate the usage of a wrong value.
 * @author Werner Mayer
 */
class BaseExport AttributeError : public Exception
{
public:
  /// Construction
  AttributeError();
  AttributeError(const char * sMessage);
  AttributeError(const std::string& sMessage);
  /// Destruction
  virtual ~AttributeError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The RuntimeError can be used to indicate an unknown exception at runtime.
 * @author Werner Mayer
 */
class BaseExport RuntimeError : public Exception
{
public:
  /// Construction
  RuntimeError();
  RuntimeError(const char * sMessage);
  RuntimeError(const std::string& sMessage);
  /// Destruction
  virtual ~RuntimeError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The BadGraphError can be used to indicate that a graph is e.g. not a DAG.
 * @author Werner Mayer
 */
class BaseExport BadGraphError : public RuntimeError
{
public:
  /// Construction
  BadGraphError();
  BadGraphError(const char * sMessage);
  BadGraphError(const std::string& sMessage);
  /// Destruction
  virtual ~BadGraphError() throw() {}
  PyObject * getPyExceptionType() const;
};

/**
 * The NotImplementedError can be used to indicate that an invoked function is not implemented.
 * @author Werner Mayer
 */
class BaseExport NotImplementedError : public Exception
{
public:
  /// Construction
  NotImplementedError();
  NotImplementedError(const char * sMessage);
  NotImplementedError(const std::string& sMessage);
  /// Destruction
  virtual ~NotImplementedError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The ZeroDivisionError can be used to indicate a division by zero.
 * @author Werner Mayer
 */
class BaseExport ZeroDivisionError : public Exception
{
public:
  /// Construction
  ZeroDivisionError();
  ZeroDivisionError(const char * sMessage);
  ZeroDivisionError(const std::string& sMessage);
  /// Destruction
  virtual ~ZeroDivisionError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The ReferenceError can be used to indicate a reference counter has the wrong value.
 * @author Werner Mayer
 */
class BaseExport ReferenceError : public Exception
{
public:
  /// Construction
  ReferenceError();
  ReferenceError(const char * sMessage);
  ReferenceError(const std::string& sMessage);
  /// Destruction
  virtual ~ReferenceError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The ExpressionError can be used to indicate erroneous.input
 * to the expression engine.
 * @author Werner Mayer
 */
class BaseExport ExpressionError : public Exception
{
public:
  /// Construction
  ExpressionError();
  ExpressionError(const char * sMessage);
  ExpressionError(const std::string& sMessage);
  /// Destruction
  virtual ~ExpressionError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The ParserError can be used to indicate the parsing error.
 * @author Werner Mayer
 */
class BaseExport ParserError : public Exception
{
public:
  /// Construction
  ParserError();
  ParserError(const char * sMessage);
  ParserError(const std::string& sMessage);
  /// Destruction
  virtual ~ParserError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The UnicodeError can be used to indicate unicode encoding/decoding error.
 * @author Werner Mayer
 */
class BaseExport UnicodeError : public Exception
{
public:
  /// Construction
  UnicodeError();
  UnicodeError(const char * sMessage);
  UnicodeError(const std::string& sMessage);
  /// Destruction
  virtual ~UnicodeError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The OverflowError can be used to indicate overflows of numbers.
 * @author Werner Mayer
 */
class BaseExport OverflowError : public Exception
{
public:
  /// Construction
  OverflowError();
  OverflowError(const char * sMessage);
  OverflowError(const std::string& sMessage);
  /// Destruction
  virtual ~OverflowError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The UnderflowError can be used to indicate underflows of numbers.
 * @author Werner Mayer
 */
class BaseExport UnderflowError : public Exception
{
public:
  /// Construction
  UnderflowError();
  UnderflowError(const char * sMessage);
  UnderflowError(const std::string& sMessage);
  /// Destruction
  virtual ~UnderflowError() throw() {}
  PyObject * getPyExceptionType() const override;
};

/**
 * The UnitsMismatchError can be used to indicate that quantities with different units are used.
 * @author Werner Mayer
 */
class BaseExport UnitsMismatchError : public Exception
{
public:
  /// Construction
  UnitsMismatchError();
  UnitsMismatchError(const char * sMessage);
  UnitsMismatchError(const std::string& sMessage);
  /// Destruction
  virtual ~UnitsMismatchError() throw() {}
  PyObject * getPyExceptionType() const override;
};

 /* The CADKernelError can be used to indicate an exception originating in the CAD Kernel
 * allowing to propagate the error messages of, for example, OCC Standard_Failure exception to
 * the FreeCAD application without making the FreeCAD application depend on OCC.
 * @author Abdullah Tahiri
 */
class BaseExport CADKernelError : public Exception
{
public:
    /// Construction
    CADKernelError();
    CADKernelError(const char * sMessage);
    CADKernelError(const std::string& sMessage);
    /// Destruction
    virtual ~CADKernelError() throw() {}
    PyObject * getPyExceptionType() const;
};

/* The RestoreError can be used to try to do a best recovery effort when an error during restoring
 * occurs. The best recovery effort may be to ignore the element altogether or to insert a placeholder
 * depending on where the actual element being restored is used.
 *
 * For example, if it is part of an array (e.g. PropertyList) and the order in the array is relevant, it
 * is better to have a placeholder than to fail to restore the whole array.
 */
class BaseExport RestoreError : public Exception
{
public:
    /// Construction
    RestoreError();
    RestoreError(const char * sMessage);
    RestoreError(const std::string& sMessage);
    /// Destruction
    virtual ~RestoreError() throw() {}
    PyObject * getPyExceptionType() const override;
};



inline void Exception::setMessage(const char * sMessage)
{
  _sErrMsg = sMessage;
}

inline void Exception::setMessage(const std::string& sMessage)
{
  _sErrMsg = sMessage;
}

inline std::string Exception::getMessage() const
{
    return _sErrMsg;
}

inline std::string Exception::getFile() const
{
    return _file;
}

inline int Exception::getLine() const
{
    return _line;
}

inline std::string Exception::getFunction() const
{
    return _function;
}

inline bool Exception::getTranslatable() const
{
    return _isTranslatable;
}

inline void Exception::setDebugInformation(const std::string & file, const int line, const std::string & function)
{
    _file = file;
    _line = line;
    _function = function;
}

inline void Exception::setTranslatable(bool translatable)
{
    _isTranslatable = translatable;
}

#if defined(__GNUC__) && defined (FC_OS_LINUX)
class SignalException
{
public:
    SignalException();
    ~SignalException();

private:
    static void throw_signal(int signum);

private:
    struct sigaction new_action, old_action;
    bool ok;
};
#endif

} //namespace Base

#endif // BASE_EXCEPTION_H
