/* 
 * Scythe Statistical Library Copyright (C) 2000-2002 Andrew D. Martin
 * and Kevin M. Quinn; 2002-present Andrew D. Martin, Kevin M. Quinn,
 * and Daniel Pemstein.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify under the terms of the GNU General Public License as
 * published by Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.  See the text files
 * COPYING and LICENSE, distributed with this source code, for further
 * information.
 * --------------------------------------------------------------------
 *  scythestat/error.h
 */
 
/*! \file error.h 
 *
 * \brief Definitions of Scythe exception classes.
 *
 * This file contains the class definitions for
 * scythe::scythe_exception and its children.  These exception classes
 * describe all of the error conditions generated by Scythe library
 * routines.
 *
 * Furthermore, error.h contains a series of macro definitions that
 * regulate the inclusion of the library's error checking code in
 * compiled code.  These macros are controlled by the compiler flag
 * SCYTHE_DEBUG and define four levels of scythe debug
 * info, SCYTHE_DEBUG = 0, 1, 2, or 3.  The library uses these macros to
 * specify the debug level of thrown exceptions.  If we are at level
 * three, all throws are expanded into actual code, at level 2 only
 * SCYTHE_THROW_10 AND SCYTHE_THROW_20 calls are expanded, and so on.
 * Scythe developers should balance exception importance and
 * efficiency costs when making exception level choices.  For example,
 * bounds checking in matrices is done at level three primarily
 * because the added branch results in high performance penalties and
 * out-of-bounds errors shouldn't occur in well-written code, while
 * conformance checks in matrix multiplication are level 1 because the
 * checks result in little overhead relative to the cost of matrix
 * multiplication and conformation errors are easy to introduce by
 * accident.  At level 0, the library performs virtually no error
 * checking.
 *
 * While the various SCYTHE_THROW, SCYTHE_CHECK, and SCYTHE_WARN
 * macros will only typically be used by library developers, users
 * should make extensive use the tiered error reporting in Scythe by
 * setting the compiler flag SCYTHE_DEBUG.  If not explicitly set by
 * the user, the SCYTHE_DEBUG level is automatically set to 3.
 */

#ifndef SCYTHE_ERROR_H
#define SCYTHE_ERROR_H

#include <exception>
#include <string>
#include <sstream>
#include <iostream>
#include <vector>
#include <cstring>

#ifdef SCYTHE_RPACK
#include <string.h>           // needed to use strcpy_s
#include <R.h>           // needed to use Rprintf()
#include <R_ext/Utils.h> // needed to allow user interrupts
#endif

/*! @cond */
#ifdef SCYTHE_DEBUG_LIB
#define SCYTHE_DEBUG_MSG(MSG)                             \
{ std::cout << "SCYTHE_DEBUG_LIB: " << MSG << std::endl; }
#else
#define SCYTHE_DEBUG_MSG(MSG)
#endif
/*! @endcond */

#define SCYTHE_THROW(EXCEP,MSG)                           \
	{                                                       \
		std::stringstream _SCYTHE_DEBUG_ss;                   \
		_SCYTHE_DEBUG_ss << MSG;                              \
		throw EXCEP(__FILE__, __func__, __LINE__,             \
				_SCYTHE_DEBUG_ss.str());                          \
	}

#define SCYTHE_CHECK(CHECK,EXCEP,MSG)                     \
{                                                         \
	if (CHECK)                                              \
		SCYTHE_THROW(EXCEP,MSG)                               \
}

#define SCYTHE_WARN_RPACK(MSG)                                        \
  {                                                                   \
		std::stringstream _SCYTHE_WARN_ss;                                \
    _SCYTHE_WARN_ss << "WARNING in " << __FILE__ << ", "              \
      << __func__ << ", " << __LINE__ << ": "                         \
      << MSG << "\n";                                                 \
    Rprintf("%s", _SCYTHE_WARN_ss.str().c_str());                         \
  }

#define SCYTHE_WARN_STD(MSG)                                          \
  std::cerr << "WARNING in " << __FILE__ << ", "                      \
    << __func__ << ", " << __LINE__ << ": "                           \
    << MSG << "\n";

#ifdef SCYTHE_RPACK
#define SCYTHE_WARN SCYTHE_WARN_RPACK
#else
#define SCYTHE_WARN SCYTHE_WARN_STD
#endif

#define SCYTHE_CHECK_WARN(CHECK,MSG)                                  \
  {                                                                   \
  if (CHECK)                                                          \
    SCYTHE_WARN(MSG)                                                  \
  }

/*! @cond */
#ifndef SCYTHE_DEBUG
#define SCYTHE_DEBUG 3
#endif
/*! @endcond */

#if SCYTHE_DEBUG > 0
#define SCYTHE_CHECK_10(CHECK,EXCEP,MSG) SCYTHE_CHECK(CHECK,EXCEP,MSG)
#else
#define SCYTHE_CHECK_10(CHECK, EXCEP, MSG)
#endif

#if SCYTHE_DEBUG > 1
#define SCYTHE_CHECK_20(CHECK,EXCEP,MSG) SCYTHE_CHECK(CHECK,EXCEP,MSG)
#else
#define SCYTHE_CHECK_20(CHECK, EXCEP, MSG)
#endif

#if SCYTHE_DEBUG > 2
#define SCYTHE_CHECK_30(CHECK,EXCEP,MSG) SCYTHE_CHECK(CHECK,EXCEP,MSG)
#else
#define SCYTHE_CHECK_30(CHECK, EXCEP, MSG)
#endif

#if SCYTHE_DEBUG > 0 
#define SCYTHE_THROW_10(EXCEP,MSG) SCYTHE_THROW(EXCEP,MSG)
#else
#define SCYTHE_THROW_10(EXCEP,MSG)
#endif

#if SCYTHE_DEBUG > 1 
#define SCYTHE_THROW_20(EXCEP,MSG) SCYTHE_THROW(EXCEP,MSG)
#else
#define SCYTHE_THROW_20(EXCEP,MSG)
#endif

#if SCYTHE_DEBUG > 2 
#define SCYTHE_THROW_30(EXCEP,MSG) SCYTHE_THROW(EXCEP,MSG)
#else
#define SCYTHE_THROW_30(EXCEP,MSG)
#endif

namespace scythe
{
	/* Forward declaration for serr */
	class scythe_exception;

  /**** This file-local variable holds the output of the last
   * scythe_exception constructed.
   ****/
#ifdef __MINGW32__
  static scythe_exception *serr;
#else
  namespace
  {
    scythe_exception *serr;
  }
#endif

  /**** A replacement for the default terminate handler.  This outputs
   * the string held in serr before calling abort, thereby notifying
   * the user of why the program crashed.
   ****/
  inline void scythe_terminate ();

  /**** The scythe exception abstract base class ****/
  /*!
   * \brief The Scythe exception abstract base class.
   *
   * The is the base class in Scythe's error handling class tree.
   * This class extends std::exception and provides fields for
   * information about the exception, including where the exception
   * occurred in the library and a message describing the error.
   */
  class scythe_exception:public std::exception
  {
  public:
    scythe_exception (const std::string & head,
                      const std::string & file,
                      const std::string & function,
                      const unsigned int &line,
                      const std::string & message = "",
                      const bool & halt = false) throw ()
      : exception (),
        head_ (head),
        file_ (file),
        function_ (function),
        line_ (line),
        message_ (message),
				call_files_ (),
				call_funcs_ (),
				call_lines_ ()
    {
      std::ostringstream os;
      os << head_ << " in " << file_ << ", " << function_ << ", "
        << line_ << ": " << message_ << "!\n\n";

			serr = this;
      std::set_terminate (scythe_terminate);
      if (halt) {
#ifdef SCYTHE_RPACK
        // error("Aborting Scythe C++ execution");
#else
        std::terminate ();
#endif
      }
    }

    scythe_exception (const scythe_exception & e) throw ()
      : exception (),
        head_ (e.head_),
        file_ (e.file_),
        function_ (e.function_),
        line_ (e.line_),
        message_ (e.message_),
				call_files_ (e.call_files_),
				call_funcs_ (e.call_funcs_),
				call_lines_ (e.call_lines_)
    {
    }

    scythe_exception & operator= (const scythe_exception & e) throw ()
    {
      head_ = e.head_;
      file_ = e.file_;
      function_ = e.function_;
      line_ = e.line_;
      message_ = e.message_;

      return *this;
    }

    virtual ~ scythe_exception () throw ()
    {
    }

    /* This function is only called from scythe_terminate, and only
     * once, so this memory leak is not an issue.  We can't just return
     * os.str().c_str() because that is a dangling pointer after the
     * function returns...
     * TODO: Deal with memory leak issue that might affect R packages
     */
    virtual const char *what () const throw ()
    {
      std::ostringstream os;
			for (int i = call_files_.size() - 1; i > -1; ++i) {
				os << "Called from " << call_files_[i] << ", "
					<< call_funcs_[i] << ", " << call_lines_[i] << std::endl;
			}
      os << head_ << " in " << file_ << ", " << function_ << ", "
        << line_ << ": " << message_ << "!";
      char *retval = new char[os.str().length()];
      std::strcpy(retval, os.str().c_str());
      // strcpy_s(retval, os.str().length(), os.str().c_str());
      // Jan 12, 2024, replaced std::strcpy(retval, os.str().c_str());
      return retval;
    }

    virtual std::string message () const throw ()
    {
      return message_;
    }

		virtual void add_caller (const std::string &file,
			const std::string &function, const unsigned int &line) throw ()
		{

			/* This if allows one to catch and rethrow an error in the same
			 * function w/out messing things up.  Nice to keep try-catch
			 * blocks to a minimum
			 */

			if (file != file_ && function != function_) {
				call_files_.push_back(file);
				call_funcs_.push_back(function);
				call_lines_.push_back(line);
			}
		}

  private:
    std::string head_;
    std::string file_;
    std::string function_;
    unsigned int line_;
    std::string message_;
		std::vector<std::string> call_files_;
		std::vector<std::string> call_funcs_;
		std::vector<unsigned int> call_lines_;
  };


  /**** Exception class types, added as needed ****/

  /*! 
   * \brief Memory allocation error.
   *
   *  Library members throw this exception in response to insufficient
   *  memory conditions, such as when one attempts to create a Matrix
   *  object that is bigger than available memory.
   */
  class scythe_alloc_error:public scythe_exception
  {
  public:
    scythe_alloc_error (const std::string & file,
                        const std::string & function,
                        const unsigned int &line,
                        const std::string & message = "",
                        const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE_ALLOCATION_ERROR", file, function,
          line, message, halt)
    {
    }
  };

  /*! 
   * \brief Invalid function argument.
   *
   * Library members throw this exception when callers pass incorrect
   * arguments to a function, such as when one calls the factorial
   * method with an argument less than 0.
   */
  class scythe_invalid_arg:public scythe_exception
  {
  public:
    scythe_invalid_arg (const std::string & file,
                        const std::string & function,
                        const unsigned int &line,
                        const std::string & message = "",
                        const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE_INVALID ARGUMENT", file, function,
          line, message, halt)
    {
    }
  };

  /*! 
   * \brief File i/o error.
   *
   * Library members throw this exception when errors occur during
   * file reading, writing, or creation, such as when one passes an
   * invalid file name to the Matrix class's save method.
   */
  class scythe_file_error:public scythe_exception
  {
  public:
    scythe_file_error(const std::string & file,
                       const std::string & function,
                       const unsigned int &line,
                       const std::string & message = "",
                       const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE FILE ERROR", file, function, line, 
          message, halt)
    {
    }
  };

  /*! \brief Matrix conformation error.
   *
   * Library members throw this exception when a caller passes
   * non-conforming matrices (matrices of incompatible dimensions) to
   * a routine, such as when one attempt two row vectors.
   */
  class scythe_conformation_error:public scythe_exception
  {
  public:
    scythe_conformation_error(const std::string & file,
                               const std::string & function,
                               const unsigned int &line,
                               const std::string & message = "",
                               const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE CONFORMATION ERROR", file, function, 
          line, message, halt)
    {
    }
  };

  /*! \brief Matrix dimension error.
   *
   * Library members throw this exception when a caller passes a
   * Matrix of the wrong size or shape to a routine.  For example,
   * trying to take the Cholesky decomposition of a non-square Matrix
   * causes this error.
   */

  class scythe_dimension_error:public scythe_exception
  {
  public:
    scythe_dimension_error (const std::string & file,
                            const std::string & function,
                            const unsigned int &line,
                            const std::string & message = "",
                            const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE DIMENSION ERROR", file, function,
          line, message, halt)
    {
    }
  };

  /*! \brief Null Matrix error.
   *
   * Library members throw this exception when a caller passes a null
   * Matrix to a routine when it expects a non-null argument.  For
   * example, taking the inverse of a null Matrix is impossible,
   * resulting in this exception.
   */
  class scythe_null_error:public scythe_exception
  {
  public:
    scythe_null_error(const std::string & file,
                       const std::string & function,
                       const unsigned int &line,
                       const std::string & message = "",
                       const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE NULL ERROR", file, function, line,
          message, halt)
    {
    }
  };

  /*! \brief Matrix type error.
   *
   * Library members throw this exception when a caller passes a
   * Matrix that does not satisfy some required property to a routine.
   * For example, Cholesky decomposition is designed to work on
   * positive definite matrices; trying to perform Cholesky
   * decomposition on a Matrix that does not satisfy this requirement
   * causes this exception.
   */
  class scythe_type_error:public scythe_exception
  {
  public:
    scythe_type_error(const std::string & file,
                       const std::string & function,
                       const unsigned int &line,
                       const std::string & message = "",
                       const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE TYPE ERROR", file, function, line,
          message, halt)
    {
    }
  };

  /*! \brief Element out of bounds error.
   *
   * Library members throw this exception when a caller attempts to
   * access an element outside the bounds of a data structure, such as
   * when one tries to access the 1000th element of a 200-element
   * Matrix.
   */
  class scythe_bounds_error:public scythe_exception
  {
  public:
    scythe_bounds_error(const std::string & file,
                               const std::string & function,
                               const unsigned int &line,
                               const std::string & message = "",
                               const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE BOUNDS ERROR", file, function,
          line, message, halt)
    {
    }
  };

  /*! \brief Numerical convergence error.
   *
   * Library members throw this exception when a numerical algorithm
   * fails to converge to a stable value.  For example, the BFGS
   * optimization routine throws this exception when it cannot locate
   * the minimum of a function to a given tolerance.
   */
  class scythe_convergence_error:public scythe_exception
  {
  public:
    scythe_convergence_error (const std::string & file,
                              const std::string & function,
                              const unsigned int &line,
                              const std::string & message = "",
                              const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE CONVERGENCE ERROR", file, function,
          line, message, halt)
    {
    }
  };

  /*! \brief Numerical underflow or overflow error.
   *
   * Library members throw this exception when the result of a
   * calculation, assignment, or other operation is to small or large
   * for the data type holding the value.  For example, passing
   * certain values to the gammafn function can result in underflow or
   * overflow conditions in the resulting calculations.
   */
  class scythe_range_error:public scythe_exception
  {
  public:
    scythe_range_error (const std::string & file,
                        const std::string & function,
                        const unsigned int &line,
                        const std::string & message = "",
                        const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE RANGE ERROR", file, function, line,
          message, halt)
    {
    }
  };

  /*! \brief Numerical precision error.
   *
   * Library members throw this exception when a routine cannot
   * complete a computation effectively and will sacrifice reasonable
   * precision as a consequence.  For example, passing a value too
   * close to a negative integer to the gammafn function renders the
   * function incapable of returning an accurate result and thus
   * generates this exception.
   */
  class scythe_precision_error:public scythe_exception
  {
  public:
    scythe_precision_error (const std::string & file,
                            const std::string & function,
                            const unsigned int &line,
                            const std::string & message = "",
                            const bool & halt = false) throw ()
      : scythe_exception ("SCYTHE PRECISION ERROR", file, function,
          line, message, halt)
    {
    }
  };
  
  /*! \brief Random number seed error.
   *
   * Library members throw this exception when a random number
   * generator is provided with an illegitimate starting seed value.
   * For example, the lecuyer class requires seeds within a certain
   * range to operate properly and will throw this exception when
   * seeded with a number outside of that range.
   */
  class scythe_randseed_error:public scythe_exception
  {
  public:
    scythe_randseed_error(const std::string & file,
                      		const std::string & function,
                      		const unsigned int &line,
                      		const std::string & message = "",
                      		const bool & halt = false) throw ()
			: scythe_exception ("SCYTHE RANDOM SEED ERROR", file, function,
					line, message, halt)
    {
    }
  };

  /*! \brief Matrix style error.
   *
   * Library members throw this exception when they are asked to
   * operate on a Matrix of the incorrect style.  Some routines
   * require specifically a concrete Matrix or view to work correctly.
   * For example, only views may reference other matrices; invoking
   * the reference function on a concrete Matrix will generate this
   * exception.
   */
  class scythe_style_error:public scythe_exception
	{
		public:
			scythe_style_error(const std::string& file,
					const std::string& function,
					const unsigned int& line,
					const std::string& message = "",
					const bool& halt = false) throw ()
				:	scythe_exception("SCYTHE STYLE ERROR", file, function,
						line, message, halt)
			{}
	};

  /*! \brief LAPACK Internal Error
   *
   * Library members throw this exception when an underlying LAPACK or
   * BLAS routine indicates that an internal error has occurred.
   * 
   */
  class scythe_lapack_internal_error:public scythe_exception
	{
		public:
			scythe_lapack_internal_error(const std::string& file,
					const std::string& function,
					const unsigned int& line,
					const std::string& message = "",
					const bool& halt = false) throw ()
				:	scythe_exception("SCYTHE LAPACK/BLAS INTERNAL  ERROR", file, 
            function, line, message, halt)
			{}
	};

  /*! \brief Unexpected call to default error.
   *
   * This error should not occur.  If it occurs in your code, please
   * contact the Scythe developers to report the problem.
   * 
   */
  class scythe_unexpected_default_error:public scythe_exception
	{
		public:
			scythe_unexpected_default_error(const std::string& file,
					const std::string& function,
					const unsigned int& line,
					const std::string& message = "",
					const bool& halt = false) throw ()
				:	scythe_exception("SCYTHE UNEXPECTED DEFAULT ERROR", file, 
            function, line, message, halt)
      {}
	};

  // The definition of our terminate handler described above
  inline void scythe_terminate ()
  {
#ifdef SCYTHE_RPACK
    Rprintf("%s", serr->what());
    // error("Aborting Scythe C++ execution");
#else
    std::cerr << serr->what() << std::endl;
    std::cerr << std::endl;
    abort ();
#endif
  }

}        // end namspace SCYTHE

#endif /* SCYTHE_ERROR_H */
