//
//  Copyright (C) 2009-2012 Artyom Beilis (Tonkikh)
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOSTER_BACKTRACE_H
#define BOOSTER_BACKTRACE_H

#include <booster/config.h>
#include <stdexcept>
#include <typeinfo>
#include <string>
#include <vector>
#include <iosfwd>

namespace booster {

    ///
    /// \brief Namespace that holds basic operations
    /// for implementing stack trace
    ///
    namespace stack_trace {
        ///
        /// \brief Record stack frame
        ///
        /// Records at most \a size stack frames returning the pointers to the running
        /// code into \a addresses vector that should have at least size places
        ///
        /// returns that number of actually recorded frames
        /// 
        BOOSTER_API int trace(void **addresses,int size);
        ///
        /// \brief Print stack trace
        ///
        /// Writes stack trace recorded \ref trace function of \a size size to the output stream
        ///
        BOOSTER_API void write_symbols(void *const *addresses,int size,std::ostream &);
        ///
        /// \brief Get stack trace information about a single address recorded
        ///
        BOOSTER_API std::string get_symbol(void *address);
        ///
        /// \brief Get stack trace information about multiple address recorded
        ///
        BOOSTER_API std::string get_symbols(void * const *address,int size);
    } // stack_trace

    ///
    /// \brief the class that records the stack trace when it is created,
    ///
    /// It is a base class for all exceptions that record stack trace
    ///

    class backtrace {
    public:
       
        ///
        /// The default number of recorded frames 
        ///
        static size_t const default_stack_size = 32;

        ///
        /// Create stack trace recording at most \a frames_no stack frames
        ///
        backtrace(size_t frames_no = default_stack_size) 
        {
            if(frames_no == 0)
                return;
            frames_.resize(frames_no,0);
            int size = stack_trace::trace(&frames_.front(),frames_no);
            frames_.resize(size);
        }

        virtual ~backtrace() throw()
        {
        }

        ///
        /// Get the actual number of recorded stack frames
        ///
        size_t stack_size() const
        {
            return frames_.size();
        }

        ///
        /// Get the returned address for the stack frame number \a frame_no 
        ///
        void *return_address(unsigned frame_no) const
        {
            if(frame_no < stack_size())
                return frames_[frame_no];
            return 0;
        }

        ///
        /// Print the stack trace frame for the frame \a frame_no to the stream \a out
        ///
        void trace_line(unsigned frame_no,std::ostream &out) const
        {
            if(frame_no < frames_.size())
                stack_trace::write_symbols(&frames_[frame_no],1,out);
        }

        ///
        /// Get a readable stack trace frame for the frame \a frame_no
        ///
        std::string trace_line(unsigned frame_no) const
        {
            if(frame_no < frames_.size())
                return stack_trace::get_symbol(frames_[frame_no]);
            return std::string();
        }

        ///
        /// Get full stack trace as a string
        ///
        std::string trace() const
        {
            if(frames_.empty())
                return std::string();
            return stack_trace::get_symbols(&frames_.front(),frames_.size());
        }

        ///
        /// Print full stack trace to a stream \a out
        ///
        void trace(std::ostream &out) const
        {
            if(frames_.empty())
                return;
            stack_trace::write_symbols(&frames_.front(),frames_.size(),out);
        }
    
    private:
        std::vector<void *> frames_;
    };

    ///
    /// \brief Same as std::exception but records stack trace
    ///
    class exception : public std::exception, public backtrace {
    public:
    };
    
    ///
    /// \brief Same as std::bad_cast but records stack trace
    ///
    class bad_cast : public std::bad_cast, public backtrace {
    public:
    };

    ///
    /// \brief Same as std::runtime_error but records stack trace
    ///
    class runtime_error: public std::runtime_error, public backtrace {
    public:
        explicit runtime_error(std::string const &s) : std::runtime_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::range_error but records stack trace
    ///
    class range_error: public std::range_error, public backtrace {
    public:
        explicit range_error(std::string const &s) : std::range_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::overflow_error but records stack trace
    ///
    class overflow_error: public std::overflow_error, public backtrace {
    public:
        explicit overflow_error(std::string const &s) : std::overflow_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::underflow_error but records stack trace
    ///
    class underflow_error: public std::underflow_error, public backtrace {
    public:
        explicit underflow_error(std::string const &s) : std::underflow_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::logic_error but records stack trace
    ///
    class logic_error: public std::logic_error, public backtrace {
    public:
        explicit logic_error(std::string const &s) : std::logic_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::domain_error but records stack trace
    ///
    class domain_error: public std::domain_error, public backtrace {
    public:
        explicit domain_error(std::string const &s) : std::domain_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::length_error but records stack trace
    ///
    class length_error: public std::length_error, public backtrace {
    public:
        explicit length_error(std::string const &s) : std::length_error(s) 
        {
        }
    };

    ///
    /// \brief Same as std::invalid_argument but records stack trace
    ///
    class invalid_argument : public std::invalid_argument, public backtrace {
    public:
        explicit invalid_argument(std::string const &s) : std::invalid_argument(s)
        {
        }
    };
    
    ///
    /// \brief Same as std::out_of_range but records stack trace
    ///
    class out_of_range : public std::out_of_range, public backtrace {
    public:
        explicit out_of_range(std::string const &s) : std::out_of_range(s)
        {
        }
    };

    /// \cond INTERNAL
    namespace details {
        class trace_manip {
        public:
            trace_manip(backtrace const *tr) :
                tr_(tr)
            {
            }
            std::ostream &write(std::ostream &out) const
            {
                if(tr_)
                    tr_->trace(out);
                return out;
            }
        private:
            backtrace const *tr_;
        };

        inline std::ostream &operator<<(std::ostream &out,details::trace_manip const &t)
        {
            return t.write(out);
        }
    }
    /// \endcond

    ///
    /// \brief manipulator that print stack trace for the exception \a e if it is derived from backtrace.
    ///
    /// For example:
    ///
    /// \code
    ///
    /// catch(std::exception const &e) {
    ///   std::cerr << e.what() << std::endl;
    ///   std::cerr << booster::trace(e);
    /// }
    ///
    /// \endcode
    ///
    template<typename E>
    details::trace_manip trace(E const &e)
    {
        backtrace const *tr = dynamic_cast<backtrace const *>(&e);
        return details::trace_manip(tr);
    }


} // booster

#endif
// vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4

