#ifndef INCLUDED_BINOPSFREE_
#define INCLUDED_BINOPSFREE_

#include <utility>
#include <istream>
#include <ostream>

namespace FBB
{

template <typename Derived>
class BinopsBase
{
    template <typename Der>
    friend std::ostream &operator<<(std::ostream &out,
                                    BinopsBase<Der> const &rhs);

    template <typename Der>
    friend std::istream &operator>>(std::istream &in, BinopsBase<Der> &rhs);

    void eWrap(std::istream &in);
    void iWrap(std::ostream &out) const;
};

template <typename Derived>
constexpr Derived &der(BinopsBase<Derived> &rhs)
{
    return static_cast<Derived &>(rhs);
}

template <typename Derived>
constexpr Derived const &der(BinopsBase<Derived> const &rhs)
{
    return static_cast<Derived const &>(rhs);
}

template <typename Derived>
constexpr Derived &&der(BinopsBase<Derived> &&rhs)
{
    return static_cast<Derived &&>(rhs);
}


template <typename Derived, typename Rhs>
Derived &operator*=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} *= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator*(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) *= rhs;
}

template <typename Derived, typename Rhs>
Derived operator*(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} *= rhs;
}

template <typename Derived, typename Rhs>
Derived &operator/=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} /= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator/(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) /= rhs;
}

template <typename Derived, typename Rhs>
Derived operator/(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} /= rhs;
}

template <typename Derived, typename Rhs>
Derived &operator%=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} %= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator%(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) %= rhs;
}

template <typename Derived, typename Rhs>
Derived operator%(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} %= rhs;
}

template <class Derived, typename Rhs>
Derived &operator+=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} += rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <class Derived, typename Rhs>
Derived operator+(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) += rhs;
}

template <class Derived, typename Rhs>
Derived operator+(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} += rhs;
}

template <typename Derived, typename Rhs>
Derived &operator-=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} -= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator-(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) -= rhs;
}

template <typename Derived, typename Rhs>
Derived operator-(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} -= rhs;
}

template <class Derived, typename Rhs>
Derived &operator<<=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} <<= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <class Derived, typename Rhs>
Derived operator<<(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) <<= rhs;
}

template <class Derived, typename Rhs>
Derived operator<<(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} <<= rhs;
}

template <typename Derived, typename Rhs>
Derived &operator>>=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} >>= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator>>(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) >>= rhs;
}

template <typename Derived, typename Rhs>
Derived operator>>(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} >>= rhs;
}

template <typename Derived, typename Rhs>
Derived &operator&=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} &= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator&(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) &= rhs;
}

template <typename Derived, typename Rhs>
Derived operator&(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} &= rhs;
}

template <typename Derived, typename Rhs>
Derived &operator|=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} |= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator|(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) |= rhs;
}

template <typename Derived, typename Rhs>
Derived operator|(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} |= rhs;
}

template <typename Derived, typename Rhs>
Derived &operator^=(BinopsBase<Derived> &lhs, Rhs const &rhs)
{
    Derived tmp{ Derived{der(lhs)} ^= rhs };
    tmp.swap(der(lhs));
    return der(lhs);
}

template <typename Derived, typename Rhs>
Derived operator^(BinopsBase<Derived> &&lhs, Rhs const &rhs)
{
    return der(std::move(lhs)) ^= rhs;
}

template <typename Derived, typename Rhs>
Derived operator^(BinopsBase<Derived> const &lhs, Rhs const &rhs)
{
    return Derived{der(lhs)} ^= rhs;
}

template <typename Derived>
inline void BinopsBase<Derived>::eWrap(std::istream &in)
{
    der(*this).extract(in);
}

template <typename Derived>
inline std::istream &operator>>(std::istream &in, BinopsBase<Derived> &rhs)
{
    rhs.eWrap(in);
    return in;
}
template <typename Derived>
inline void BinopsBase<Derived>::iWrap(std::ostream &out) const
{
    der(*this).insert(out);
}

template <typename Derived>
inline std::ostream &operator<<(std::ostream &out, BinopsBase<Derived> const &rhs)
{
    rhs.iWrap(out);
    return out;
}

} // FBB

#endif
