#ifndef PACPUS_EVENT_H
#define PACPUS_EVENT_H

#include <algorithm>
#include <Pacpus/kernel/road_time.h>
#include <Pacpus/kernel/pacpus.h>
#include <QDataStream>
#include <QEvent>

namespace pacpus {

// registerEventType // TODO

// FIXME: move to another header file
enum PacpusEventType {
    TYPED_EVENT = 1000,
    GENERIC_EVENT = 2000,
    GENERIC_EVENT2 = 2002,
    GENERIC_EVENT3 = 2003,
    CONFIG_EVENT = 3000
};

class PACPUSLIB_API PacpusEvent
    : public QEvent
{
    Q_GADGET // permits to use signals and slots (using Q_SIGNAL, Q_SLOT) without inheriting from QObject

public:
    PacpusEvent(PacpusEventType type, road_time_t t = road_time(), road_timerange_t tr = 0)
        : QEvent(QEvent::Type(type))
        , m_time(t)
        , m_timerange(tr)
    {}

    virtual ~PacpusEvent()
    {}

    // TODO: should we make it virtual pure ???
    virtual QDataStream& streamOut(QDataStream& out)
    {
        return out;
    }

    // TODO: should we make it virtual pure ???
    virtual QDataStream& streamIn(QDataStream& in)
    {
        return in;
    }

    road_time_t & time()
    {
        return m_time;
    }

    const road_time_t & time() const
    {
        return m_time;
    }

    road_timerange_t & timerange()
    {
        return m_timerange;
    }

    const road_timerange_t & timerange() const
    {
        return m_timerange;
    }
protected:
    road_time_t m_time;
    road_timerange_t m_timerange;
};

template <typename T>
class /*PACPUSLIB_API*/ TypedData
{
public:
    TypedData(const T& data)
    : m_data(data)
    {
    }

    ~TypedData()
    {
    }

    T & data()
    {
        return m_data;
    }

    const T & data() const
    {
        return m_data;
    }

    size_t size() const
    {
        return sizeof(T);
    }

private:
    T m_data;
};

template<class T>
class /*PACPUSLIB_API*/ PacpusTypedEvent
    : public PacpusEvent
    , public TypedData<T>
{
public:
    // FIXME: why we need `data = T()` ???
    PacpusTypedEvent(PacpusEventType type, const T & data = T(), road_time_t t = road_time(), road_timerange_t tr = 0 )
        : PacpusEvent(type, t, tr)
        , TypedData<T>(data)
    {}

    ~PacpusTypedEvent()
    {}

    QDataStream& streamOut(QDataStream& out)
    {
        // FIXME Stream Data errors
        return out << (quint64) time() << timerange() /*<< m_data*/;
    }

    QDataStream& streamIn(QDataStream& in)
    {
        return in >> (quint64&) time() >> timerange() /*>> m_data*/;
    }
};

class PACPUSLIB_API GenericData
{
public:
    GenericData(const char* data, size_t size)
        : m_data(NULL)
        , m_dataSize(size)
    {
        m_data = new char[m_dataSize];
        ::std::copy(data, data + m_dataSize, // source
#ifdef _MSC_VER
            ::stdext::checked_array_iterator<char *>(m_data, m_dataSize) // destination
#else
            m_data // destination
#endif
            );
    }

    ~GenericData()
    {
        delete[] m_data;
    }

    char * data()
    {
        return m_data;
    }
    
    const char * data() const
    {
        return m_data;
    }

    size_t size() const
    {
        return m_dataSize;
    }

private:
    char* m_data;
    size_t m_dataSize;
};

class PACPUSLIB_API PacpusGenericEvent
    : public PacpusEvent
    , public GenericData
{
public:
    PacpusGenericEvent(PacpusEventType type, const char* data, size_t size)
        : PacpusEvent(type)
        , GenericData(data, size)
    {
    }

    virtual ~PacpusGenericEvent()
    {
    }
};

} // namespace pacpus

PACPUSLIB_API inline QDataStream& operator<< (QDataStream& out, pacpus::PacpusEvent& ev)
{
    /*return ev.streamOut(out);*/
    return out;
}

PACPUSLIB_API inline QDataStream& operator>> (QDataStream& in, pacpus::PacpusEvent& ev)
{
    /*return ev.streamIn(in);*/
    return in;
}

#endif // PACPUS_EVENT_H
