/*****************************************************************************
 *
 * $Id$
 *
 * Copyright (C) 2009 - 2012  Richard Hacker <lerich@gmx.net>
 *                            Florian Pose <fp@igh-essen.com>
 *
 * This file is part of the PdCom library.
 *
 * The PdCom library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The PdCom library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the PdCom Library. If not, see <http://www.gnu.org/licenses/>.
 *
 ****************************************************************************/

#include "pdcom/Variable.h"
#include "pdcom/Process.h"
#include "pdcom/Subscriber.h"

#include "ProtocolHandler.h"

#define DEBUG 0

#if DEBUG
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
#endif

using namespace PdCom;

/****************************************************************************/

// Static members

Time Variable::nullTime;

/****************************************************************************/

Variable::Variable(
        Process *process,
        const std::string &path,
        const std::string &alias,
        const Data::Type& type,
        const Data::Dimension& dim,
        double samplePeriod
        ):
    Data(type, dim),
    process(process),
    path(path),
    alias(alias),
    samplePeriod(samplePeriod),
    readable(true),
    writeable(false),
    read(read_convert[type]),
    write(write_convert[type]),
    read_noscale(read_convert_noscale[type]),
    write_noscale(write_convert_noscale[type])
{
    if (!dimension.getElementCount() or !dimension.size())
        throw Data::Dimension::ZeroDimensionException();
    process->newVariable(this);

    mtime = nullTime;
}

/****************************************************************************/

Variable::~Variable()
{
    process->rmVariable(this);
}

/****************************************************************************/

void Variable::cancelSubscribers()
{
#if DEBUG
    cout << __func__ << "() for " << path << endl;
#endif
    for (DecimationSubscribers::iterator it = decimationSubscribers.begin();
            it != decimationSubscribers.end(); it++) {
        if (it->first == -1)
            continue;

        for (Subscribers::iterator sit = it->second.begin();
                sit != it->second.end(); sit++) {
            (*sit)->notifyDelete(this);
            decimationSubscribers[-1].erase(*sit);
        }
    }
    for (Subscribers::iterator it = decimationSubscribers[-1].begin();
            it != decimationSubscribers[-1].end(); it++) {
            (*it)->notifyDelete(this);
    }
#if DEBUG
    cout << __func__ << "() finished" << endl;
#endif
}

/****************************************************************************/

void Variable::poll(Subscriber* vs)
{
#if DEBUG
    cout << __func__ << "()" << vs << endl;
#endif
    if (decimationSubscribers.empty()) {
        allocateMemory();
    }

    decimationSubscribers[-1].insert(vs);
    pollVariable();
}

/****************************************************************************/

void Variable::subscribe(Subscriber* vs, double interval)
{
#if DEBUG
    cout << __func__ << "()" << vs << " interval " << interval << endl;
#endif
    // Put the variable subscription on the list. Note that it MUST be
    // excluded that a subscription adds itself twice! The subscription
    // object must take care of it.

    if (interval < 0)
        throw VariableException("Negative intervals in subscribe not allowed");

    if (decimationSubscribers.empty()) {
        allocateMemory();
    }

    removeStreamSubscribers(vs);

    unsigned int decimation = addTransmissionInterval(interval);
    subscriberMap[vs] = decimation;
    decimationSubscribers[decimation].insert(vs);
}

/****************************************************************************/
void Variable::unsubscribe(Subscriber* vs)
{
#if DEBUG
    cout << __func__ << "()" << vs
        << " decimation " << endl;
#endif
    removeStreamSubscribers(vs);
    decimationSubscribers[-1].erase(vs);
}

/****************************************************************************/
void Variable::removeStreamSubscribers(Subscriber* vs)
{
#if DEBUG
    cout << __func__ << "()" << vs << endl;
#endif
    if (subscriberMap.find(vs) == subscriberMap.end())
        return;

    unsigned int decimation = subscriberMap[vs];
#if DEBUG
    cout << __func__ << "()" << vs
        << " decimation " << decimation << endl;
#endif

    decimationSubscribers[decimation].erase(vs);

    if (decimationSubscribers[decimation].empty()) {
        rmTransmissionDecimation(decimation);
    }
}

/****************************************************************************/
void Variable::notifySubscribers(unsigned int decimation)
{
    Subscribers& s = decimationSubscribers[decimation];

    // Iterate over the subscribers informing them of the new value
    for (Subscribers::iterator it = s.begin(); it != s.end(); it++) {
        (*it)->notify(this);
    }
}

/****************************************************************************/
void Variable::notifyPoll()
{
    for (Subscribers::iterator it = decimationSubscribers[-1].begin();
            it != decimationSubscribers[-1].end(); it++) {
        (*it)->notify(this);
    }

    decimationSubscribers[-1].clear();
}

/****************************************************************************/
void Variable::valueChanged(const char*, size_t)
{
}

/*****************************************************************************
 ****************************************************************************/
void Variable::getValue(double* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[double_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[double_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(float* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[single_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[single_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(uint8_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[uint8_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[uint8_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(int8_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[sint8_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[sint8_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(uint16_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[uint16_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[uint16_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(int16_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[sint16_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[sint16_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(uint32_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[uint32_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[uint32_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(int32_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[sint32_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[sint32_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(uint64_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[uint64_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[uint64_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(int64_t* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[sint64_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[sint64_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(bool* dst, size_t len, const Scale* scale,
        const Dimension* idx) const
{
    if (scale)
        read[bool_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, scale);
    else
        read_noscale[bool_T](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst, len, 0);
}

/****************************************************************************/
void Variable::getValue(Data& dst, const Scale* scale, const Dimension* idx) const
{
#if 0
    cout << __func__ << "(): value is " << *(double*)src.getDataPtr(idx) << endl;
#endif
    if (scale)
        read[dst.type](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst.dataPtr, dst.dimension.getElementCount(), scale);
    else
        read_noscale[dst.type](dataPtr + (idx ? dimension.getOffset(idx) : 0),
                dst.dataPtr, dst.dimension.getElementCount(), 0);
}

/*****************************************************************************
 ****************************************************************************/
void Variable::setValue(const double* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const float* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const uint8_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const int8_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const uint16_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const int16_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const uint32_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const int32_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const uint64_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const int64_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const bool* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    if (process->readOnly or !writeable)
        return;

    valueChanged(pushValue(src, len, scale, idx), len);
}

/****************************************************************************/
void Variable::setValue(const Data& src, const Scale* scale, const Dimension* idx)
{
#if DEBUG
    cout << __func__ << "(): value is " << *(double*)src.getDataPtr(idx) << endl;
#endif

    if (process->readOnly or !writeable)
        return;

    char* start = dataPtr + dimension.getOffset(idx);
    size_t len = src.dimension.getElementCount();

    if (scale)
        write[src.type](src.dataPtr, start, len, scale);
    else
        write_noscale[src.type](src.dataPtr, start, len, 0);

    valueChanged(start, len);
}

/*****************************************************************************
 ****************************************************************************/
char* Variable::pushValue(const double* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[double_T](src, start, len, scale);
    else
        write_noscale[double_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const float* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[single_T](src, start, len, scale);
    else
        write_noscale[single_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const uint8_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[uint8_T](src, start, len, scale);
    else
        write_noscale[uint8_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const int8_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[sint8_T](src, start, len, scale);
    else
        write_noscale[sint8_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const uint16_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[uint16_T](src, start, len, scale);
    else
        write_noscale[uint16_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const int16_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[sint16_T](src, start, len, scale);
    else
        write_noscale[sint16_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const uint32_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[uint32_T](src, start, len, scale);
    else
        write_noscale[uint32_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const int32_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[sint32_T](src, start, len, scale);
    else
        write_noscale[sint32_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const uint64_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[uint64_T](src, start, len, scale);
    else
        write_noscale[uint64_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const int64_t* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[sint64_T](src, start, len, scale);
    else
        write_noscale[sint64_T](src, start, len, 0);

    return start;
}

/****************************************************************************/
char* Variable::pushValue(const bool* src, size_t len, const Scale* scale,
        const Dimension* idx)
{
    char* start = dataPtr + (idx ? dimension.getOffset(idx) : 0);

    if (scale)
        write[bool_T](src, start, len, scale);
    else
        write_noscale[bool_T](src, start, len, 0);

    return start;
}
