// -*- C++ -*-
// Exception.h: Exceptions used by Asap
//
// Copyright (C) 2001-2011 Jakob Schiotz and Center for Individual
// Nanoparticle Functionality, Department of Physics, Technical
// University of Denmark.  Email: schiotz@fysik.dtu.dk
//
// This file is part of Asap version 3.
// Asap is released under the GNU Lesser Public License (LGPL) version 3.
// However, the parts of Asap distributed within the OpenKIM project
// (including this file) are also released under the Common Development
// and Distribution License (CDDL) version 1.0.
//
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// version 3 as published by the Free Software Foundation.  Permission
// to use other versions of the GNU Lesser General Public License may
// granted by Jakob Schiotz or the head of department of the
// Department of Physics, Technical University of Denmark, as
// described in section 14 of the GNU General Public License.
//
// 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
// and the GNU Lesser Public License along with this program.  If not,
// see <http://www.gnu.org/licenses/>.


// DESIGN NOTE:
// ============
//
// Error conditions in Asap are usually handled by throwing an AsapError
// exception.  This exception is caught by the Python Interface methods,
// and converted to a Python exception.  If errors are generated by Python
// in Python calls from Asap, the Python error condition is left in place,
// and the C++ code throws an AsapPythonError.  This error is also caught
// by the Python Interface methods, but in this case the preexisting Python
// error condition is left unchanged, and the error condition is reported
// back to Python.  This results in slightly strange tracebacks, as the
// path through the C++ code will be missing.  A subclass of AsapPythonError
// is AsapNotImplementedError, which is thrown without a corresponding
// Python error, and converted into Python's NotImplementedError.  Finally,
// assert statements can be changed to emit an AssertionFailed error that is
// also turned into a Python AssertionFailed error.  This latter behaviour
// is normally disabled, and is totally incompatible with OpenMP.
//
// OpenMP note:  OpenMP place so severe restrictions on throwing exceptions
// inside parallel sections that exceptions become almost useless, even if
// parallelism is disabled at runtime.  Asap handles this by replacing the
// throw statement with a THROW() macro inside OpenMP enabled regions.  If
// compiled without OpenMP support, this macro just expands to the throw
// statement.  If OpenMP is enabled, it instead expands to a helper function
// that just stores the exception in a global variable.  Subsequent function
// calls are then supposed to return immediately (thanks to the
// RETURNIFASAPERROR macro in the beginning of the functions) until control
// leaves the parallel section, and the exception is thrown by the
// PROPAGATEASAPERROR macro.  These latter macros are empty if OpenMP is
// not enabled at compile time.


#ifndef _EXCEPTION_H
#define _EXCEPTION_H

#include "Asap.h"
#include <sstream>
#include <iostream>
#ifndef ASAPASSERT
#include <assert.h>
#include <exception>
#endif // ASAPASSERT

using std::stringstream;
using std::string;

#ifndef _NOEXCEPT
#define _NOEXCEPT throw()
#endif

namespace ASAPSPACE {

// this AsapError class can be used as follows:
//
// throw AsapError("Error");
//
// throw AsapError("Error ") << n + 7 << " while opening file: " << filename;

class ASAP_PUBLIC AsapErrorBase : std::exception
{
public:
  virtual ~AsapErrorBase() _NOEXCEPT {};
};

class ASAP_PUBLIC AsapError : public AsapErrorBase
{
public:
  AsapError(const char *m);
  AsapError(const AsapError& ex);
  virtual ~AsapError() _NOEXCEPT {};
  template<class T> AsapError& operator<<(const T& x) 
    {
      message << x;
      return *this;
    }
  string GetMessage() const;
private:
  stringstream message;
};

/// An exception for indicating that a Python error has occurred and
/// should be propagated.
class ASAP_PUBLIC AsapPythonError : public AsapErrorBase
{
public:
  AsapPythonError() {};
  virtual ~AsapPythonError() _NOEXCEPT {};
};

#ifdef ASAP_FOR_KIM
// This definition is used when an ASAP potential is exported to OpenKIM,
// where there is no Python.
class ASAP_PUBLIC AsapNotImplementedError : public AsapError
{
public:
  AsapNotImplementedError(const char *m) : AsapError(m) {};
};
#else // ASAP_FOR_KIM
// The NORMAL version is here !
/// An exception mapping to a Python NotImplementedError
class ASAP_PUBLIC AsapNotImplementedError : public AsapPythonError
{
public:
  AsapNotImplementedError(const char *m);
};
#endif //ASAP_FOR_KIM

class ASAP_PUBLIC AssertionFailed : public AsapErrorBase
{
public:
  AssertionFailed(const char *expression, const char *file, int line,
		  const char *func = NULL);
  AssertionFailed(const AssertionFailed& ex);
  ~AssertionFailed() _NOEXCEPT {};
  string GetMessage() const;
private:
  stringstream message;
};

/// A helper define
#define NOTHREADSERROR throw AsapError("Threads not supported. ") << __FILE__ << ":" << __LINE__

/// Defining our own assert macro that throws an exception
#ifdef ASAPASSERT
#ifdef __GNUC__
#define ASSERT(EX) if (!(EX)) \
    throw AssertionFailed(#EX, __FILE__, __LINE__, __PRETTY_FUNCTION__)
#else // __GNUC__
#define ASSERT(EX) if (!(EX)) \
    throw AssertionFailed(#EX, __FILE__, __LINE__, __func__)
#endif // __GNUC__
#else // ASAPASSERT
#define ASSERT(EX) assert(EX)
#endif // ASAPASSERT

#ifdef SLOWASSERT
// We need the semicolon below, although it looks wrong, to allow SASSERT to vanish altogether.
#define SASSERT(x) ASSERT(x);
#else  // SLOWASSERT
#define SASSERT(x)
#endif // SLOWASSERT


// Define macros and helper functions for throwing exceptions within OpenMP constructs.
#ifdef _OPENMP

extern AsapErrorBase *AsapGlobalException;
void AsapThrowHelper(const AsapError &err);
void AsapThrowHelper(const AsapPythonError &err);

#define THROW(x) AsapThrowHelper(x)
#define THROW_RETURN(x) {AsapThrowHelper(x); return;}
#define CHECKNOASAPERROR ASSERT(AsapGlobalException == NULL)
#define RETURNIFASAPERROR if (AsapGlobalException != NULL) return
#define RETURNIFASAPERROR2(x) if (AsapGlobalException != NULL) return (x)
#define PROPAGATEASAPERROR if (AsapGlobalException != NULL) { \
    AsapError *err = dynamic_cast<AsapError*>(AsapGlobalException); \
    AsapPythonError *perr = dynamic_cast<AsapPythonError*>(AsapGlobalException); \
    AsapGlobalException = NULL; \
    std::cerr << "Propagating ASAP error [" << __FILE__ << ":" << __LINE__ << "] ..." << std::endl; \
    if (err != NULL) throw *err; \
    if (perr != NULL) throw *perr; \
    throw AsapError("Error throwing exception."); }

#else // _OPENMP

#define THROW(x) throw x
#define THROW_RETURN(x) throw x
#define CHECKNOASAPERROR
#define RETURNIFASAPERROR
#define RETURNIFASAPERROR2(x)
#define PROPAGATEASAPERROR

#endif // _OPENMP

} // end namespace

#endif // _EXCEPTION_H
