#pragma once

#include <sstream>
#include <Poco/SharedPtr.h>
#include <Poco/Data/Binding.h>
#include <Poco/Data/Session.h>
#include <Poco/Data/Statement.h>
#include "hostid_partition_util.h"

namespace NWebmaster {

//
// Stream manipulaton and string converter
class hostId {
public:
    explicit hostId(long long i) : id(i) {}

    std::string operator()(const std::string &str) {
        return HostIdPartitionUtil::instance()->partitionedSQL(str, id);
    }

private:
    long long id;
};

//
// Proxy-class. Collect data, then convert it (by Converter) and send result in Stream.
// I use sharedptr because we must support copy-constructor, and must gurantie that data
// will converted-sended just once.
template<class Stream, class Converter>
class internalMyStream {
public:

    internalMyStream(Stream &s, Converter c) : impl(new Implementation(s, c)) {
    }

    //
    // Collect data
    //

    template<class T>
    internalMyStream &operator << (const T &t) {
        impl->stream() << t;
        return *this;
    }

    //
    // Poco-specific fix triggers
    //

    template<class T>
    Stream &operator << (Poco::Data::Binding<T> *t) {
        return (impl->fix() << t);
    }

    Stream &operator << (Poco::Data::AbstractBindingVec &t) {
        return (impl->fix() << t);
    }

    template<class T>
    Stream &operator, (const T &t) {
        return (impl->fix(), t);
    }

private:

    class Implementation {
    public:

        Implementation(Stream &s, Converter c) : output(s), conv(c) {
        }

        ~Implementation() {
            if (!fixed) {
                // Just once
                output << conv(data.str());
            }
        }

        std::ostream &stream() {
            return data;
        }

        Stream &fix() {
            fixed = true;
            return (output << conv(data.str()));
        }

    private:
        Stream &output;
        Converter conv;
        std::stringstream data;
        bool fixed;
    };

    Poco::SharedPtr<Implementation> impl;
};

//
// Pococ::Data::Session-specific version. Return type of operator << for Session is not Session&, but Statement.
template<class Converter>
class internalMyStream<Poco::Data::Session, Converter> {
public:

    internalMyStream(Poco::Data::Session &s, Converter c) : impl(new Implementation(s, c)) {
    }

    //
    // Collect data
    //

    template<class T>
    internalMyStream &operator<<(const T &t) {
        impl->stream() << t;
        return *this;
    }

    //
    // Poco-specific fix triggers
    //

    template<class T>
    Poco::Data::Statement operator<<(Poco::Data::Binding<T> *t) {
        return (impl->fix() << t);
    }

    Poco::Data::Statement operator<<(Poco::Data::AbstractBindingVec &t) {
        return (impl->fix() << t);
    }

    template<class T>
    Poco::Data::Statement operator, (const T &t) {
        return (impl->fix(), t);
    }

private:

    class Implementation {
    public:

        Implementation(Poco::Data::Session &s, Converter c) : output(s), conv(c) {
        }

        ~Implementation() {
            if (!fixed) {
                // Just once
                output << conv(data.str());
            }
        }

        std::ostream &stream() {
            return data;
        }

        Poco::Data::Statement fix() {
            fixed = true;
            return (output << conv(data.str()));
        }

    private:
        Poco::Data::Session &output;
        Converter conv;
        std::stringstream data;
        bool fixed;
    };

    Poco::SharedPtr<Implementation> impl;
};

NWebmaster::internalMyStream<Poco::Data::Statement, NWebmaster::hostId> operator<<(Poco::Data::Statement &s, const NWebmaster::hostId &h);
NWebmaster::internalMyStream<Poco::Data::Session, NWebmaster::hostId> operator<<(Poco::Data::Session &s, const NWebmaster::hostId &h);

} //namespace NWebmaster
