/*****************************************************************************
 *
 * $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/Data.h"

#include "MsrVariable.h"
#include "MsrException.h"
#include "MsrProtocolHandler.h"

#include <cstring>
#include <string>

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

using namespace MSRProto;

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

MSRProto::Variable::Variable(
        ProtocolHandler *msrHandler,
        std::ostream *os,
        const char *name,
        const char *alias,
        unsigned int index,
        unsigned int rnum,
        unsigned int cnum,
        const char *orientationString,
        const char *datatypeString,
        unsigned int protocolDatawidth,
        double sampleTime,
        const char *unit):
    PdCom::Variable(
            msrHandler->getProcess(),
            name, alias,
            genDataType(datatypeString),
            genDimension(datatypeString, orientationString, rnum, cnum),
            sampleTime),
    msrHandler(msrHandler), os(os), index(index), unit(unit),
    rnum(rnum), cnum(cnum), datawidth(Data::getTypeWidth(type)),
    transpose_skip(getTransposeSkip(orientationString, cnum))
{
    size_t dimensions = dimension.size();
    size_t elementCount = dimension.getElementCount();
    std::ostringstream errmsg;

    if (orientationString and strcmp(orientationString, "SCALAR")) {
        /* Orientation was supplied by the ProtocolHandler.
         * Check it for plausibility */
        if (!strcmp(orientationString, "VECTOR")) {
            if (dimensions != 1 or elementCount < 2) {
                errmsg << "MSR Data Orientation 'VECTOR' "
                    "is expected to have exactly one dimension; found "
                    << dimensions << " dimensions with "
                    << dimension.getElementCount()
                    << " elements";
            }
        }
        else if ((!strncmp(orientationString, "MATRIX_COL_MAJOR", 16)
                    or !strcmp(orientationString, "MATRIX_ROW_MAJOR"))) {
            if (dimensions != 2) {
                errmsg << "MSR Data Orientation '"
                    << orientationString
                    << "' is expected to have 2 dimensions; found "
                    << dimensions << " dimensions.";
            }
            else if (!strcmp(orientationString, "MATRIX_COL_MAJOR_ND")
                    and !transpose_skip) {
                errmsg << "MSR Data Orientation '"
                    << orientationString
                    << "' is expected to have a transpose_skip > 0";
            }
        }
        else {
            errmsg << "MSR Data Orientation '"
                << orientationString << "' is unknown.";
        }
    }
    else if (dimensions != 1 or elementCount != 1) {
        // Scalar;
        if (!orientationString) {
            errmsg << "MSR Data Orientation is not specified, "
                << "so a scalar is assumed.";
        }
        else {
            errmsg << "MSR Data Orientation 'SCALAR' is expected to have "
                "one dimension with one element.";
        }
        errmsg << " However, " << dimensions << " dimensions were found "
            "with " << elementCount << " elements";
    }

    if (datawidth != protocolDatawidth) {
        errmsg << "MSR Protocol Data reports a datasize of "
            << protocolDatawidth << " bytes, which does not match that of "
            << datatypeString << " having " << datawidth << " bytes.";
    }

    if (errmsg.str().length()) {
        throw Exception(path, errmsg);
    }

}

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

MSRProto::Variable::~Variable()
{
};

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

PdCom::Data::Type Variable::genDataType(const char* dt)
{
    struct {
        const char *name;
        PdCom::Data::Type type;
    } *map, msr_dtypemap[] = {
        { "TDBL",    PdCom::Data::double_T  },
        { "TINT",    PdCom::Data::sint32_T  },
        { "TUINT",   PdCom::Data::uint32_T  },
        { "TCHAR",   PdCom::Data::sint8_T   },
        { "TUCHAR",  PdCom::Data::uint8_T   },
        { "TSHORT",  PdCom::Data::sint16_T  },
        { "TUSHORT", PdCom::Data::uint16_T  },
        { "TLINT",   PdCom::Data::sint64_T  },
        { "TULINT",  PdCom::Data::uint64_T  },
        { "TFLT",    PdCom::Data::single_T  },

        { NULL,}
    };

    for (map = msr_dtypemap; map->name; map++) {
        if (!strncmp(dt, map->name, strlen(map->name))) {
            return map->type;
        }
    }
    std::ostringstream errmsg;
    errmsg << "MSR reported an unknown data type '" << dt
        << "'";
    throw Exception(errmsg.str());
}

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

PdCom::Data::Dimension Variable::genDimension(const char* dt,
        const char* orientation, int rnum, int cnum)
{

    if (!rnum or !cnum) {
        std::ostringstream errmsg;
        errmsg << "MSR reported a column or row to have zero elements.";
        throw Exception(errmsg.str());
    }

    PdCom::Data::Dimension dim;
    if ((rnum == 1 or cnum == 1)
            and (!orientation or strncmp(orientation,"MATRIX",6)))
        dim.push_back(std::max(rnum,cnum));
    else {
        dim.push_back(rnum);
        dim.push_back(cnum);
    }

    return dim;
}

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

size_t Variable::getTransposeSkip(const char* dt, size_t cnum)
{
    return dt and !strncmp(dt, "MATRIX_COL_MAJOR", 16) ? (cnum - 1) : 0;
}

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