/**********************************************************************************************
   Copyright (C) 2015 Ivo Kronenberg <>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

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

#include "gis/fit/decoder/CFitFieldBuilder.h"
#include "gis/fit/decoder/CFitFieldDataState.h"
#include "gis/fit/defs/CFitBaseType.h"
#include "gis/fit/defs/CFitProfileLookup.h"
#include "gis/fit/defs/fit_const.h"
#include "gis/fit/defs/fit_enums.h"
#include "gis/fit/defs/fit_fields.h"

void CFitFieldDataState::reset()
{
    fieldDataIndex = 0;
    fieldIndex = 0;
    devFieldIndex = 0;
}


decode_state_e CFitFieldDataState::process(quint8 &dataByte)
{
    CFitMessage& mesg = *latestMessage();
    CFitDefinitionMessage* defMesg = definition(mesg.getLocalMesgNr());

    // add the read byte to the data array
    fieldData[fieldDataIndex++] = dataByte;

    handleFitField();
    bool allFieldRead =  fieldIndex >= defMesg->getNrOfFields();
    if(allFieldRead)
    {
        handleDevField();
    }
    bool allDevFielRead = devFieldIndex >= defMesg->getNrOfDevFields();

    if (allFieldRead && allDevFielRead)
    {
        // Now that the entire message is decoded we may evaluate subfields and expand components
        CFitFieldBuilder::evaluateSubfieldsAndExpandComponents(mesg);

        devProfile(mesg);

        reset();
        FITDEBUG(2, qDebug() << mesg.messageInfo())
        // after all fields read, go to next record header
        return eDecoderStateRecord;
    }

    // there are more fields to read for the current message
    return eDecoderStateFieldData;
}


bool CFitFieldDataState::handleFitField()
{
    CFitMessage& mesg = *latestMessage();
    CFitDefinitionMessage* defMesg = definition(mesg.getLocalMesgNr());

    if (fieldIndex < defMesg->getNrOfFields())
    {
        const CFitFieldDefinition& fieldDef = defMesg->getFieldByIndex(fieldIndex);
        if (fieldDataIndex >= fieldDef.getSize())
        {
            // all bytes are read for current field

            // new field with data
            CFitField f = CFitFieldBuilder::buildField(fieldDef, fieldData, mesg);
            mesg.addField(f);

            // The special case time record.
            // timestamp has always the same value for all enums. it does not matter against which we're comparing.
            if (fieldDef.getDefNr() == eRecordTimestamp)
            {
                setTimestamp(f.getValue().toUInt());
            }

            // new field follows, reset
            fieldDataIndex = 0;
            fieldIndex++;
        }
    }
    return fieldIndex >= defMesg->getNrOfFields();
}

bool CFitFieldDataState::handleDevField()
{
    CFitMessage& mesg = *latestMessage();
    CFitDefinitionMessage* defMesg = definition(mesg.getLocalMesgNr());

    if (devFieldIndex < defMesg->getNrOfDevFields())
    {
        const CFitFieldDefinition& fieldDef = defMesg->getDevFieldByIndex(devFieldIndex);
        if (fieldDataIndex >= fieldDef.getSize())
        {
            // handling developer data for mapping the field data to its definitions:
            // part 2, reading field data and attach dynamic profile
            CFitFieldProfile* fieldProfile = devFieldProfile(fieldDef.getDefNr());
            if (fieldProfile->getBaseType().nr() == eBaseTypeNrInvalid)
            {
                // test if profile exists
                throw tr("Missing field definition for development field.");
            }

            CFitField f = CFitFieldBuilder::buildField(*fieldProfile, fieldDef, fieldData, mesg);
            mesg.addField(f);

            // new field follows, reset
            fieldDataIndex = 0;
            devFieldIndex++;
        }
    }
    return devFieldIndex >= defMesg->getNrOfDevFields();
}

void CFitFieldDataState::devProfile(CFitMessage& mesg)
{
    // handling developer data for mapping the field data to its definitions:
    // part 1, dynamic profiles creation
    if (mesg.getGlobalMesgNr() == eMesgNumDeveloperDataId)
    {
        clearDevFieldProfiles();
    }
    if (mesg.getGlobalMesgNr() == eMesgNumFieldDescription)
    {
        CFitFieldProfile devFieldProfile = buildDevFieldProfile(mesg);
        FITDEBUG(2, qDebug() << devFieldProfile.fieldProfileInfo());
        addDevFieldProfile(devFieldProfile);
    }
}

CFitFieldProfile CFitFieldDataState::buildDevFieldProfile(CFitMessage& mesg)
{
    QString fieldName;
    quint8 fieldDefNr = 0;
    quint8 devDataIdx = 0;
    quint8 baseType = eBaseTypeNrInvalid;
    qreal scale = 0;
    quint8 array = 0;
    QString components;
    qint16 offset = 0;
    QString units;
    QString bits;
    QString accumulate;
    quint8 baseUnitId = 0;
    quint8 natvieMesgNum = 0;
    quint8 nativeFieldNum = 0;

    for (const CFitField field : mesg.getFields())
    {
        if (field.isValidValue())
        {
            switch (field.getFieldDefNr())
            {
            case eFieldDescriptionDeveloperDataIndex:
                devDataIdx = (quint8) field.getValue().toUInt();
                break;

            case eFieldDescriptionFieldDefinitionNumber:
                fieldDefNr = (quint8) field.getValue().toUInt();
                break;

            case eFieldDescriptionFitBaseTypeId:
                baseType = (quint8) field.getValue().toUInt();     // enum
                break;

            case eFieldDescriptionFieldName:
                fieldName = field.getValue().toString();
                break;

            case eFieldDescriptionArray:
                array = (quint8) field.getValue().toUInt();
                break;

            case eFieldDescriptionComponents:
                components = field.getValue().toString();
                break;

            case eFieldDescriptionScale:
                scale = (qreal) field.getValue().toDouble();
                break;

            case eFieldDescriptionOffset:
                offset = (qint16) field.getValue().toInt();
                break;

            case eFieldDescriptionUnits:
                units = field.getValue().toString();
                break;

            case eFieldDescriptionBits:
                bits = field.getValue().toString();
                break;

            case eFieldDescriptionAccumulate:
                accumulate = field.getValue().toString();
                break;

            case eFieldDescriptionFitBaseUnitId:
                baseUnitId = (quint8) field.getValue().toUInt();     // enum
                break;

            case eFieldDescriptionNativeMesgNum:
                natvieMesgNum = (quint8) field.getValue().toUInt();     // enum
                break;

            case eFieldDescriptionNativeFieldNum:
                nativeFieldNum = (quint8) field.getValue().toUInt();
                break;

            default:
                throw tr("FIT decoding error: invalid field def nr %1 while creating dev field profile.")
                      .arg(field.getFieldDefNr());
                break;
            }
        }
    }

    Q_UNUSED(devDataIdx)
    Q_UNUSED(array)
    Q_UNUSED(baseUnitId)

    if (natvieMesgNum && nativeFieldNum)
    {
        const CFitFieldProfile* nativeFieldProfile = CFitProfileLookup::getFieldForProfile(natvieMesgNum, nativeFieldNum);
        if (nativeFieldProfile->getBaseType().nr() == eBaseTypeNrInvalid)
        {
            qWarning() << "DEV field" << fieldName << " field profile for mesg num" << natvieMesgNum << "and field num" << nativeFieldNum << "does not exist.";
        }
        if (nativeFieldProfile->getUnits() != units)
        {
            qWarning() << "DEV field" << fieldName << "units" << units << " do not match existing profile units" << nativeFieldProfile->getUnits();
        }
        // scale and offset not allowed if a fit field is overwritten.
        scale = 0;
        offset = 0;
    }

    CFitFieldProfile devFieldProfile = CFitFieldProfile(nullptr, fieldName, *CFitBaseTypeMap::get(baseType), fieldDefNr,
                                                        scale, offset, units, eFieldTypeDevelopment);
    // for documentation: the development field profile may contain more fields in the future as can be adumbrated by
    // the description field enumeration. According to the FIT specification Rev 2.3  table 4-9 - Field Description Messages
    // those fields are not yet used.

    return devFieldProfile;
}