#include <iostream>
#include <stdexcept>

// HasConditions is a class with a logical invariant but for whatever reason sometimes we want to let
// users muck around, but only if they make sure the invariant holds afterwards.
// To do this, an instance of class HCAccessor provides access to the innwards of a HasConditions and
// checks that the HasConditions class invariant holds when the HCAccessor drops out of scope.

class HCAccessor;

class HasConditions
{
    // class "mostly-invariant"
    // 7 < payload_ <42
    int payload_;

    bool valid() const
    {
        if (!(7 < payload_) || !(payload_ < 42))
            return false;
        else
            return true;
    }

public:
    HasConditions(const int payload)
        : payload_(payload)
    {
        if (!valid())
        {
            // This exception is not thrown
            std::cout << "Trying to construct a HasConditions with invalid payload" << std::endl;
            throw std::runtime_error("can't construct");
        }
    }

    friend class HCAccessor;
};

class HCAccessor
{
    HasConditions& hc_;

public:
    HCAccessor(HasConditions& hc)
        : hc_(hc)
    {}

    HCAccessor(HCAccessor& other)
        : hc_(other.hc_)
    {}

    ~HCAccessor()
    {
        // conventional wisdom is to not throw from a destructor but this class
        // doesn't manage resources, doesn't have any clean up to do, and it's
        // only job is to possible throw an exception
        if (!hc_.valid())
        {
            // This exception will be thrown just once.
            std::cout << "HCAccessor leaving scope with HasConditions in invalid state. PANIC!" << std::endl;
            throw std::runtime_error("you broke it!");
        }
    }

    void payload(const int newval)
    {
        hc_.payload_ = newval;
    }

    int payload() const
    {
        return hc_.payload_;
    }
};


void play_nice()
{
    HasConditions hc{30};
    HCAccessor hca(hc);

    hca.payload(-5);
    hca.payload(100);
    hca.payload(30);
}

void break_it()
{
    HasConditions hc{30};
    HCAccessor hca(hc);

    hca.payload(-5);
}

void wrap_play_nice()
{
    std::cout << "calling play_nice()" << std::endl;
    try
    {
        play_nice();
    }
    catch (...)
    {
        std::cout << "it broke";
        return;
    }
    std::cout << "all is well" << std::endl;
    return;
}

void wrap_break_it()
{
    std::cout << "calling break_it()" << std::endl;
    try
    {
        // exception thrown from here causes call to std::terminate()
        std::cout << "calling break_it()" << std::endl;
        break_it();
        // the function never returns
        std::cout << "returned from break_it()" << std::endl;
    }
    catch (...)
    {
        std::cout << "it broke";
        return;
    }
    std::cout << "all is well" << std::endl;
    return;
}


int main()
{
    try
    {
        throw std::runtime_error("sanity check: does try-catch work at all?");
    }
    catch (const std::runtime_error& re)
    {
        std::cout << re.what() << std::endl;
        std::cout << "yes it does, swallowing..." << std::endl;
    }

    wrap_play_nice();
    wrap_break_it();
}