/*****************************************************************************
 *
 * $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 <string.h> // memcpy()

#include "pdcom/Exception.h"

#include "MsrChannel.h"
#include "MsrProtocolHandler.h"

#include <algorithm>    // min()
#include <sstream> // time parsing

using namespace MSRProto;

#define DEBUG 0
#define DEBUG_BASE64 0

#if DEBUG || DEBUG_BASE64
#include <iostream>
#include <iomanip>
using std::cout;
using std::cerr;
using std::endl;
using std::hex;
using std::setfill;
using std::setw;
#endif

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

unsigned int euclid_gcd( unsigned int a, unsigned int b)
/* From http://www.nist.gov/dads/HTML/euclidalgo.html */
{
    if (b)
        return euclid_gcd(b, a % b);
    else
        return a;
}

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

unsigned int binary_gcd(unsigned int u, unsigned int v)
/* from http://en.wikipedia.org/wiki/Binary_GCD_algorithm */
{
    int shift;

    /* GCD(0,x) := x */
    if (u == 0 || v == 0)
        return u | v;

    /* Let shift := lg K, where K is the greatest power of 2
     *        dividing both u and v. */
    for (shift = 0; ((u | v) & 1) == 0; ++shift) {
        u >>= 1;
        v >>= 1;
    }

    while ((u & 1) == 0)
        u >>= 1;

    /* From here on, u is always odd. */
    do {
        while ((v & 1) == 0)  /* Loop X */
            v >>= 1;

        /* Now u and v are both odd, so diff(u, v) is even.
         *            Let u = min(u, v), v = diff(u, v)/2. */
        if (u <= v) {
            v -= u;
        } else {
            int diff = u - v;
            u = v;
            v = diff;
        }
        v >>= 1;
    } while (v != 0);

    return u << shift;
}

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

int ext_euklid_gcd(int a, int b, int& x, int& y)
    /* From http://www.tekpool.com/?p=56
     * ax + by = GCD(a,b)
     * Search for: Greatest Common Divisor
     */
{
    int prevX, prevY;
    int gcd;

    if(b > a)
    {
        return ext_euklid_gcd(b,a,y,x);
    }

    if(b == 0)
    {
        x=1;
        y=0;
        return a;
    }

    gcd = ext_euklid_gcd(b, a%b, prevX, prevY);
    x = prevY;
    y = prevX - int(a/b) * prevY;
    return gcd;
}

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

unsigned char Channel::base64ToChr[256] = {0,};

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

Channel::Channel(
        ProtocolHandler *protocolHandler,
        std::ostream* os,
        const char *name,
        const char *alias,
        unsigned int index,
        unsigned int rnum,
        unsigned int cnum,
        const char *orientation,
        const char *datatype,
        unsigned int datasize,
        unsigned int bufsize,
        double freq,
        const char *unit):
    Variable(protocolHandler, os, name, alias, index, rnum, cnum,
            orientation, datatype, datasize, 1.0 / freq, ""),
    msr_channel_bufsize(bufsize)
{
#if DEBUG
    cerr << freq << " " << samplePeriod << " " << path << endl;
#endif

    reduction = 0;
    receiveCount = 0;
    eventTransmission = false;
    eventChannels = msrHandler->hasFeature("eventchannels");
    pollActive = false;

    // Use base64ToChr[0] to indicate whether the character map is
    // initialised.
    if (!base64ToChr[0]) {
        unsigned char base64chr[] =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            "abcdefghijklmnopqrstuvwxyz"
            "0123456789+/";

        // Generate character map to convert from base64 to integers
        memset(base64ToChr, 0, sizeof(base64ToChr));
        for (unsigned char i = 0; base64chr[i]; i++)
            base64ToChr[base64chr[i]] = i;
        base64ToChr[(unsigned char)'-'] = 62;
        base64ToChr[(unsigned char)'_'] = 63;

        base64ToChr[0] = 1;
    }
}

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

Channel::~Channel()
{
    if (eventTransmission or decimations.size()) {
        eventTransmission = false;
        decimations.clear();
        updateTransmission();
    }
}

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

unsigned int Channel::addTransmissionInterval(double interval)
{
    if (interval and interval < samplePeriod) {
        std::stringstream err;
        err << "Subscription interval too small (interval=" << interval
            << ", samplePeriod=" << samplePeriod << ")!";
        throw PdCom::VariableException(err.str());
    }

    unsigned int requestedDecimation =
        (unsigned int) (interval / samplePeriod + 0.5);

#if DEBUG
    cout << __func__ << "() " << interval << endl;
#endif

    // ts >= 0 here, since we are registering streamed variables
    if (decimations.size() or eventTransmission) {
        // The stream is already active
        // Check that stream and event based transmissions are not mixed
        if (!(requestedDecimation xor eventTransmission)) {
            // Cannot mix event and stream transmission in MSR
            throw PdCom::VariableException("MSR Protocol does not support "
                    "event and stream based transmission of a Channel "
                    "simultaneously.");
        }

        if (!requestedDecimation) {
            // Event transmission has already been requested from MSR
            return requestedDecimation;
        }
    }

    // eventTransmission == true or requestedDecimation > 0

    if (requestedDecimation) { // stream-based transmission requested
        if (decimations.find(requestedDecimation) == decimations.end()) {
            // This decimation does not exist, so need to register a new one
            decimations.insert(requestedDecimation);
            maxDecimation =
                *std::max_element(decimations.begin(), decimations.end());
            updateTransmission();
        }
    } else { // event-based transmission requested
        if (!eventChannels) {
            throw PdCom::VariableException(
                    "Error trying to register event type subscription: "
                    "Process does not support \"event\" feature.");
        }

        // Activate event transmission
        eventTransmission = true;
        updateTransmission();
    }

    return requestedDecimation;
}

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

void Channel::rmTransmissionDecimation(int decimation)
{
#if DEBUG
    cout << __func__ << "() " << decimation << endl;
#endif

    if (decimation < 0) {
        // Polled variables don't require further notice
        pollActive = false;
        return;
    }
    else if (eventTransmission) {
        eventTransmission = false;
    }
    else {
        // Subscription was deleted
        decimations.erase(decimation);
    }
    updateTransmission();
}

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

void Channel::pollVariable()
{
#if DEBUG
    cerr << "Channel::" << __func__ << "() "
        << " " << eventTransmission << endl;
#endif

    if (!pollActive) {
        *os << "<rk index=\"" << index << "\" />\n";
        pollActive = true;
    }
}

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

void Channel::updateTransmission()
{
#if DEBUG
    cerr << "Channel::updateTransmission() "
        << pollActive << " " << eventTransmission << endl;
#endif

    if (eventTransmission) {
        // Activate event transmission
        *os << "<xsad channels=\"" << index
            << "\" event=\"1"
            << "\" coding=\"" << "Base64"
            << "\" />\n";
        return;
    }
    else if (decimations.empty()) {
        *os << "<xsod channels=\"" << index << "\" />\n";
        reduction = 0;
        return;
    }

    // Find a reduction that is common to all decimations, i.e.
    // greatest common divisor
    unsigned int oldReduction = reduction;
    reduction = 0;
    for (Decimations::iterator it = decimations.begin();
            it != decimations.end(); it++) {

        reduction = binary_gcd(reduction, *it);

        if (reduction == 1)
            break;
    }

    // reduction cannot be larger than msr_channel_bufsize
    reduction = std::min(reduction, msr_channel_bufsize);

    // Return if nothing has changed
    if (oldReduction and oldReduction == reduction) {
#if DEBUG
        cerr << "transmission update unnecessary." << endl;
#endif
        return;
    }

    // Calculate a blocksize such that the transmission is 25 Hz,
    // keeping in mind not to exceed the variable's block size in the buddy
    blocksize = int(1.0 / double(samplePeriod) / reduction / 25.0);
    blocksize = std::min(msr_channel_bufsize / reduction, blocksize);
    if (!blocksize)
        blocksize = 1;

    *os << "<xsad channels=\"" << index
        << "\" reduction=\"" << reduction
        << "\" blocksize=\"" << blocksize
        << "\" coding=\"" << "Base64"
        << "\" />\n";

    receiveCount = 0;

#if DEBUG
    cerr << "updating transmission: reduction: " << reduction
        << " blocksize: " << blocksize << endl;
#endif
}

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

void Channel::newValues(const std::string& asc_time, const char *buf)
{
    PdCom::Time dt(samplePeriod * double(reduction));
    PdCom::Time timeOfLast, diffToFirst;
    std::stringstream str;
    double t;
    unsigned int decodedSize = calcBase64DecodedSize(buf);
    unsigned int numValues = decodedSize / memSize;

    if (decodedSize != numValues * memSize) {
        throw PdCom::VariableException("Invalid Base64 string received.");
    }

    str.imbue(std::locale("C")); // use '.' as a decimal separator!
    str << asc_time;
    str >> t;

    /* Important: asc_time is the time of the *last* value in the block! */
    timeOfLast = t;
    diffToFirst = (numValues - 1) * (double) dt;
    /* mtime is defined in class Variable */
    mtime = timeOfLast - diffToFirst;

#if DEBUG
    cerr << __func__ << "() processing values. mtime=" << mtime.str()
        << " bufsize=" << strlen(buf)
        << " values=" << numValues
        << " t=" << std::fixed << t
        << " buf=" << buf << endl;
#endif

    initBase64Src(buf);
    while (numValues--) {
        readBase64Value();

        // Notify
        receiveCount += reduction;

#if DEBUG
        cerr << "mtime=" << mtime.str() << " receiveCount=" << receiveCount
            << endl;
#endif

        for (Decimations::iterator it = decimations.begin();
                it != decimations.end(); it++) {
            if (receiveCount % *it)
                continue;

#if DEBUG
            cerr << "notifying " << *it << endl;
#endif

            notifySubscribers(*it);
        }

        mtime += dt;
        if (receiveCount >= maxDecimation) {
            receiveCount = 0;
        }
    }
}

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

void Channel::newEvent(const std::string &asc_time, const char *buf)
{
    std::stringstream str;
    double t;

#if DEBUG
    cerr << "newEvent() for index " << index << endl;
#endif

    str.imbue(std::locale("C")); // use '.' as a decimal separator!
    str << asc_time;
    str >> t;
    mtime = t;

    if (calcBase64DecodedSize(buf) == memSize) {
        initBase64Src(buf);
        readBase64Value();

        // Notify
        notifySubscribers(0);
    }
}

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

void Channel::newPoll(const char *buf)
{
    double value[elementCount];
    std::stringstream str;
    char sep;

#if DEBUG
    cerr << "Processing newPoll (" << elementCount << " values): "
        << buf << " ";
#endif

    str.imbue(std::locale("C")); // use '.' as a decimal separator!
    str << buf;

    for (size_t i = 0; i < elementCount; i++) {
        if (i)
            str >> sep;

        str >> value[i];
    }

#if DEBUG
    cerr << endl;
#endif

    Variable::pushValue(value, elementCount);
    notifyPoll();
    pollActive = false;
}

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

void Channel::initBase64Src(const char *src)
{
    base64Str = src;
    base64ChrPos = 0;
    base64Value = 0; // FIXME unused variable, kept for binary compatibility
}

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

bool Channel::readBase64Value()
{
    unsigned int bufPos = 0;
    const unsigned char *p = (unsigned char *) base64Str;

#if DEBUG_BASE64
    cerr << __func__ << " " << base64Str << " memSize=" << memSize << endl;
#endif

    do {
#if DEBUG_BASE64
        cerr << "base64ChrPos " << base64ChrPos
            << " diff="
            << (size_t) ((const char *) p - (const char *) base64Str)
            << " transpose_skip=" << transpose_skip
            << " bufPos=" << bufPos
            << " memSize=" << memSize
            << endl;
#endif

        switch (base64ChrPos++) {
            case 0:
                dataPtr[bufPos++] =
                    (base64ToChr[*p] << 2) | (base64ToChr[*(p + 1)] >> 4);
                p++;
                break;
            case 1:
                dataPtr[bufPos++] =
                    (base64ToChr[*p] << 4) | (base64ToChr[*(p + 1)] >> 2);
                p++;
                break;
            case 2:
                dataPtr[bufPos++] =
                    (base64ToChr[*p] << 6) | (base64ToChr[*(p + 1)]);
                p += 2;
                base64ChrPos = 0;
                break;
            default:
                break;
        }

#if DEBUG_BASE64
        {
            unsigned int i;
            cerr << "output from decode:" << hex;
            for (i = 0; i < memSize; i++)
                cerr << " " << setfill ('0') << setw(2)
                    << (unsigned int) ((const uint8_t *) dataPtr)[i];
            cerr << endl;
        }
#endif

        if (transpose_skip) {
            if (!(bufPos % datawidth)) {
                bufPos += transpose_skip * datawidth;
            }

            if (bufPos == memSize + transpose_skip * datawidth) {
                bufPos = 0;
            }
            else if (bufPos >= memSize) {
                bufPos -= memSize - datawidth;
            }
        }
        else if (bufPos >= memSize) {
            bufPos = 0;
        }
    } while (bufPos);

    base64Str = (char *) p;

    return true;
}

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

unsigned int Channel::calcBase64DecodedSize(const char *buf) const
{
    unsigned int len = strlen(buf);

    /* length must be greater zero and multiple of 4 */
    if (!len or len & 0x03) {
        throw PdCom::VariableException("Invalid Base64 string.");
    }

    return len / 4 * 3
        - (buf[len - 1] == '=')
        - (buf[len - 2] == '=');
}

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