#ifndef INCLUDED_BOBCAT_EXCEPTION_
#define INCLUDED_BOBCAT_EXCEPTION_

#include <string>
#include <sstream>
#include <exception>
#include <thread>
#include <iosfwd>

namespace FBB
{

extern thread_local int g_errno;

class Exception: public std::exception
{
    template <typename Type>
    friend Exception &&operator<<(Exception &&tmp, Type const &value);

    std::string d_what;

    public:
        enum
        {
            STRERROR_BUFSIZE = 100
        };

        enum Protection
        {
            ANY,
            EQUAL
        };

        Exception();                                        // exception.f
        explicit Exception(int errnoValue);                 // exception1.cc

        ~Exception() noexcept(true) override;

        static size_t protection(std::string const &name, size_t protect,
                          Protection type = EQUAL);

        template <typename StreamType>
        static StreamType factory(std::string const &name);     // factory1.f

        template <typename StreamType>
        static StreamType factory(int errnoValue,               // factory2.f
                                  std::string const &name);

        template <typename StreamType>
        static StreamType factory(std::string const &name,      // factory3.f
                                std::ios::openmode mode);

        template <typename StreamType>
        static StreamType factory(int errnoValue,               // factory4.f
                                std::string const &name,
                                std::ios::openmode mode);

        template <typename StreamType>
        static StreamType factory(std::string const &name,      // factory5.f
                                std::ios::openmode mode1,
                                std::ios::openmode mode2);

        template <typename StreamType>
        static StreamType factory(int errnoValue,               // factory6.f
                                std::string const &name,
                                std::ios::openmode mode1,
                                std::ios::openmode mode2);

        template <typename StreamType>
        static void open(StreamType &stream,                    // open1.f
                         std::string const &name);

        template <typename StreamType>
        static void open(int errnoValue, StreamType &stream,    // open2.f
                         std::string const &name);

        template <typename StreamType>
        static void open(StreamType &stream,                    // open3.f
                            std::string const &name, std::ios::openmode mode);

        template <typename StreamType>
        static void open(int errnoValue, StreamType &stream,    // open4.f
                            std::string const &name, std::ios::openmode mode);

        template <typename StreamType>
        static void open(StreamType &stream,                    // open5.f
                         std::string const &name,
                         std::ios::openmode mode1, std::ios::openmode mode2);

        template <typename StreamType>
        static void open(int errnoValue, StreamType &stream,    // open6.f
                         std::string const &name,
                         std::ios::openmode mode1, std::ios::openmode mode2);

    private:
        char const *what() const noexcept(true) override;
};

inline Exception::Exception()
{
    g_errno = 0;
}
template <typename StreamType>
void Exception::open(StreamType &stream, std::string const &name)
{
    if (stream.is_open())
        stream.close();

    stream.open(name);
    if (!stream)
        throw Exception{} << "Can't open `" << name << '\'';
}
template <typename StreamType>
void Exception::open(int errnoValue, StreamType &stream,
                     std::string const &name)
{
    if (stream.is_open())
        stream.close();

    stream.open(name);
    if (!stream)
        throw Exception{errnoValue} << "Can't open `" << name << '\'';
}
template <typename StreamType>
void Exception::open(StreamType &stream, std::string const &name,
                                         std::ios::openmode mode)
{
    if (stream.is_open())
        stream.close();

    stream.open(name, mode);
    if (!stream)
        throw Exception{} << "Can't open `" << name << '\'';
}
template <typename StreamType>
void Exception::open(int errnoValue, StreamType &stream,
                        std::string const &name, std::ios::openmode mode)
{
    if (stream.is_open())
        stream.close();

    stream.open(name, mode);
    if (!stream)
        throw Exception{ errnoValue } << "Can't open `" << name << '\'';
}
template <typename StreamType>
void Exception::open(StreamType &stream, std::string const &name,
                     std::ios::openmode mode1, std::ios::openmode mode2)
{
    if (stream.is_open())
        stream.close();

    stream.open(name, mode1);
    if (!stream)
    {
        stream.clear();
        stream.open(name, mode2);
    }

    if (!stream)
        throw Exception{} << "Can't open `" << name << '\'';
}
template <typename StreamType>
void Exception::open(int errnoValue, StreamType &stream,
                     std::string const &name,
                     std::ios::openmode mode1, std::ios::openmode mode2)
{
    if (stream.is_open())
        stream.close();

    stream.open(name, mode1);
    if (!stream)
    {
        stream.clear();
        stream.open(name, mode2);
    }

    if (!stream)
        throw Exception{errnoValue} << "Can't open `" << name << '\'';
}

template <typename StreamType>
StreamType Exception::factory(std::string const &name)
{
    StreamType stream{ name };

    if (!stream)
        throw Exception{} << "Can't open `" << name << '\'';

    return stream;
}
template <typename StreamType>
StreamType Exception::factory(int errnoValue, std::string const &name)
{
    StreamType stream{ name };

    if (!stream)
        throw Exception{errnoValue} << "Can't open `" << name << '\'';

    return stream;
}
template <typename StreamType>
StreamType Exception::factory(std::string const &name,
                             std::ios::openmode mode)
{
    StreamType stream{ name, mode };

    if (!stream)
        throw Exception{} << "Can't open `" << name << '\'';

    return stream;
}
template <typename StreamType>
StreamType Exception::factory(int errnoValue, std::string const &name,
                             std::ios::openmode mode)
{
    StreamType stream{ name, mode };

    if (!stream)
        throw Exception{errnoValue} << "Can't open `" << name << '\'';

    return stream;
}
template <typename StreamType>
StreamType Exception::factory(std::string const &name,
                              std::ios::openmode mode1,
                              std::ios::openmode mode2)
{
    StreamType ret{ name, mode1 };

    if (not ret)
    {
        ret.clear();
        ret.open(name, mode2);
    }

    if (!ret)
        throw Exception{} << "Can't open `" << name << '\'';

    return ret;
}
template <typename StreamType>
StreamType Exception::factory(int errnoValue, std::string const &name,
                              std::ios::openmode mode1,
                              std::ios::openmode mode2)
{
    StreamType ret{ name, mode1 };

    if (not ret)
    {
        ret.clear();
        ret.open(name, mode2);
    }

    if (!ret)
        throw Exception{errnoValue} << "Can't open `" << name << '\'';

    return ret;
}

    // Free functions

std::ostream &errnodescr(std::ostream &out);

template <typename Type>
inline Exception &&operator<<(Exception &&tmp, Type const &value)
{
    std::ostringstream out;
    out << value;
    tmp.d_what += out.str();

    return std::move(tmp);
}

} // FBB


#endif
