/* 
 * File:   M10Decoder.cpp
 * Author: Viproz
 * Used code from rs1729
 * Created on December 12, 2018, 11:31 PM
 */

#include "M10Decoder.h"
#include "M10GtopParser.h"
#include "M10TrimbleParser.h"

char M10Decoder::header[] = "10011001100110010100110010011001";

M10Decoder::M10Decoder() {
    m10Gtop = new M10GtopParser();
    m10Trimble = new M10TrimbleParser();
    m10Parser = m10Gtop;
    
    frameSamples = NULL;
    audioFile = NULL;
}

M10Decoder::~M10Decoder() {
    delete m10Gtop;
    delete m10Trimble;

    if (frameSamples)
        delete frameSamples;

    if (audioFile)
        delete audioFile;
}

int M10Decoder::startDecode(std::string fname) {
    filename = fname;
    int error = 0;

    audioFile = new AudioFile(fname, baudRate, &error);

    if (error) {
        return error;
    }

    samplesPerBit = audioFile->getSamplesPerBit();

    samplesBufLength = (DATA_LENGTH * 8 + 100) * samplesPerBit * 2;
    frameSamples = new std::vector<int>(samplesBufLength);

    double res = 1;
    bool supported = true;
    while (res != EOF_INT) {
        res = findFrameStart();
        if (res == EOF_INT)
            break;

        totalFrames++;
        if (decodeMessage(res) == EOF_INT)
            break;

        long sondeType = ((long) frame_bytes[1] << 8) + (long) frame_bytes[2];
        frameLength = frame_bytes[0];
        supported = true;
        switch (sondeType) {
            case 0xAF02:
                m10Parser = m10Gtop;
                break;
            case 0x9F20:
                m10Parser = m10Trimble;
                break;
            default:
                supported = false;
        }

        bool correctCRC = checkCRC();
        if (correctCRC || verboseLevel >= 1) {
            if (!supported) {
                fprintf(stderr, "Not supported : %#06x\n", (unsigned int) sondeType);
                continue;
            }
            if (correctCRC) {
                correctFrames++;
                lastGoodFrame = frame_bytes;
            }

            m10Parser->changeData(frame_bytes, correctCRC);
            m10Parser->printFrame();

            if (!correctCRC) {
                if (correctFrames == 0 && tryStats) // Add the frame to the record
                    m10Parser->addToStats();
                else if (tryRepair && correctFrames != 0) // Put the last correct to repair
                    m10Parser->changeData(lastGoodFrame, correctFrames > 0);
            }
        }
    }
    if (tryStats && verboseLevel >= 1)
        m10Parser->printStatsFrame();

    if (dispResult)
        fprintf(stderr, "Result of %i/%i decoding\n", correctFrames, totalFrames);
    return 0;
}

double M10Decoder::findFrameStart() {
    int headerLength = strlen(header);
    double posData[headerLength];
    char decoded[headerLength];
    int currentIndex = 0;

    for (int i = 0; i < headerLength; ++i)
        decoded[i] = '0'; // Fill in decoded to compare later

    int prevV = 1;
    int len = 0;
    int v;
    int smallBufLen = (int) (samplesPerBit * 5.);
    std::vector<int> vals(smallBufLen);
    int valIndex = 0;
    double activeSum = 0;
    for (int j = 0; 1; ++j) {
        v = audioFile->readSignedSample();
        vals[valIndex++ % smallBufLen] = v;
        if (v == EOF_INT)
            return EOF_INT;
        // Average over the last 6 samples to comply with a global offset
        activeSum = (activeSum + (double) v)*(samplesPerBit * AVG_NUM) / (samplesPerBit * AVG_NUM + 1.);
        v = v - activeSum / (samplesPerBit * AVG_NUM);

        len++;
        if (v * prevV > 0) // If the signs are the same
            continue;

        for (int i = 0; i < round((double) len / samplesPerBit); ++i) { // Multiple bits in one detection
            if (prevV < 0)
                decoded[currentIndex] = '1';
            else
                decoded[currentIndex] = '0';

            // Store the position of the start of the bit
            posData[currentIndex] = (double) j - (1. - (double) i / round((double) len / samplesPerBit))*(double) len;

            char normal = 1;
            char inv = 1;
            int headerIndex = 0;

            // Check if the header is correct
            for (int k = 0; k < headerLength; ++k) {
                if (decoded[(k + currentIndex + 1) % headerLength] == header[headerIndex])
                    inv = 0;
                else
                    normal = 0;
                headerIndex++;
            }

            if (normal || inv) {
                // Calculate the real position of the data averaging over the headerLength samples
                double pos = 0;
                for (int k = 0; k < headerLength; ++k) {
                    pos += posData[(k + currentIndex + 1) % headerLength] + (double) (headerLength - k) * samplesPerBit;
                    //fprintf(stderr, "%.2f\n", posData[(k + currentIndex + 1) % headerLength] + (double) (headerLength - k) * samplesPerBit);
                }

                pos /= (double) headerLength;

                int tmpIndex = 0;
                valIndex--;
                valIndex %= smallBufLen;
                if (j - (int) pos > smallBufLen) // If absurdly long way back
                    continue;
                // Store the previous values in the buffer
                for (curIndex = 0; curIndex < (j - (int) pos); ++curIndex) {
                    tmpIndex = valIndex + curIndex - (j - (int) pos);
                    if (tmpIndex < 0)
                        tmpIndex += smallBufLen;
                    frameSamples->at(curIndex) = vals.at(tmpIndex);
                }

                // Only the weight of the first bit is useful, they are stored already
                return pos - (int) pos;
            }

            currentIndex = (currentIndex + 1) % headerLength;
        }
        len = 0;
        prevV = v;
    }
}

int M10Decoder::decodeMessage(double initialPos) {
    std::array<unsigned char, DATA_LENGTH> frameBackup;
    int v;
    for (; curIndex < samplesBufLength; ++curIndex) {
        v = audioFile->readSignedSample();
        if (v == EOF_INT)
            return EOF_INT;
        frameSamples->at(curIndex) = v;
    }

    // Reset the index
    curIndex = 0;

    int ret;

    ret = decodeMethodCompare(initialPos);
    if (ret == 0)
        return 0;
    if (ret == EOF_INT)
        return EOF_INT;

    if ((tryRepair && correctFrames != 0) || (correctFrames == 0 && tryStats)) {
        frameBackup = frame_bytes;
        frame_bytes = m10Parser->replaceWithPrevious(frame_bytes);
        if (checkCRC())
            return 0;
        frame_bytes = frameBackup;
    }

    if (trySign) {
        // Reset the index
        curIndex = 0;
        ret = decodeMethodSign(initialPos);
        if (ret == 0)
            return 0;
        if (ret == EOF_INT)
            return EOF_INT;

        if ((tryRepair && correctFrames != 0) || (correctFrames == 0 && tryStats)) {
            frameBackup = frame_bytes;
            frame_bytes = m10Parser->replaceWithPrevious(frame_bytes);
            if (checkCRC())
                return 0;
            frame_bytes = frameBackup;
        }
    }

    return 1;
}

int M10Decoder::decodeMethodCompare(double initialPos) {
    char bit0 = 2;

    double j = initialPos;
    double sum = 0;
    int val = getNextBufferValue(); // Read the first value

    if (val == EOF_INT)
        return EOF_INT;
    for (int k = 0; k < FRAME_LEN * 8 + AUX_LEN * 8 + 8; ++k) { // Iterate through needed bits
        // val is set in the previous round
        // Add the first part of the value weighted correctly
        sum = (1. - (j - floor(j)))*(double) val;
        for (int i = ceil(j); i < j + samplesPerBit - 1; ++i) { // Full vals in the middle
            val = getNextBufferValue();
            if (val == EOF_INT)
                return EOF_INT;
            sum += (double) val;
        }
        val = getNextBufferValue();
        if (val == EOF_INT)
            return EOF_INT;
        // Add the rest of the value
        sum += (j + samplesPerBit - floor(j + samplesPerBit))*(double) val;

        double moy1 = sum / samplesPerBit;

        j += samplesPerBit;

        // Same for the second bit
        sum = (1 - (j - floor(j)))*(double) val;
        for (int i = ceil(j); i < j + samplesPerBit - 1; ++i) { // Full vals in the middle
            val = getNextBufferValue();
            if (val == EOF_INT)
                return EOF_INT;
            sum += (double) val;
        }
        val = getNextBufferValue();
        if (val == EOF_INT)
            return EOF_INT;
        sum += (j + samplesPerBit - floor(j + samplesPerBit))*(double) val;

        double moy2 = sum / samplesPerBit;

        j += samplesPerBit;

        // Determination of a combination of raw bits or 10 or 01.
        if (moy1 > moy2) {
            if (bit0 == 0)
                frame_bits[k] = '1';
            else
                frame_bits[k] = '0';
            bit0 = 0;
        } else {
            if (bit0 == 1)
                frame_bits[k] = '1';
            else
                frame_bits[k] = '0';
            bit0 = 1;
        }
    }
    bits2bytes();
    return !checkCRC();
}

int M10Decoder::decodeMethodSign(double initialPos) {
    char bit0 = 2;

    double j = initialPos;
    double sum = 0;
    int val = audioFile->averageNormalizeSample(getNextBufferValue()); // Read the first value

    if (val == EOF_INT)
        return EOF_INT;
    for (int k = 0; k < FRAME_LEN * 8 + AUX_LEN * 8 + 8; ++k) { // Iterate through needed bits
        // val is set in the previous round
        // Add the first part of the value weighted correctly
        sum = (1. - (j - floor(j)))*(double) val;
        for (int i = ceil(j); i < j + samplesPerBit - 1; ++i) { // Full vals in the middle
            val = audioFile->averageNormalizeSample(getNextBufferValue());
            if (val == EOF_INT)
                return EOF_INT;
            sum += val;
        }

        val = audioFile->averageNormalizeSample(getNextBufferValue());
        if (val == EOF_INT)
            return EOF_INT;

        // Add the rest of the value
        sum += (j + samplesPerBit - floor(j + samplesPerBit))*(double) val;

        double moy1 = sum / samplesPerBit;

        j += samplesPerBit;

        // Same for the second bit
        sum = (1 - (j - floor(j)))*(double) val;
        for (int i = ceil(j); i < j + samplesPerBit - 1; ++i) { // Full vals in the middle
            val = audioFile->averageNormalizeSample(getNextBufferValue());
            if (val == EOF_INT)
                return EOF_INT;
            sum += (double) val;
        }
        val = audioFile->averageNormalizeSample(getNextBufferValue());
        if (val == EOF_INT)
            return EOF_INT;
        sum += (j + samplesPerBit - floor(j + samplesPerBit))*(double) val;

        double moy2 = sum / samplesPerBit;

        j += samplesPerBit;

        // Determination of a combination of raw bits or 10 or 01.
        if (moy1 > moy2) {
            if (bit0 == 0)
                frame_bits[k] = '1';
            else
                frame_bits[k] = '0';
            bit0 = 0;
        } else {
            if (bit0 == 1)
                frame_bits[k] = '1';
            else
                frame_bits[k] = '0';
            bit0 = 1;
        }
    }
    bits2bytes();
    return !checkCRC();
}

void M10Decoder::setRaw(bool b) {
    dispRaw = b;
    m10Gtop->setRaw(b);
    m10Trimble->setRaw(b);
}

int M10Decoder::getNextBufferValue() {
    if (curIndex < samplesBufLength)
        return frameSamples->at(curIndex++);
    else {
        fprintf(stderr, "Error, end of buffer.\n");
        return EOF_INT;
    }
}

bool M10Decoder::checkCRC() {
    int i, cs;

    cs = 0;
    for (i = 0; i < frameLength-1; i++) {
        cs = update_checkM10(cs, frame_bytes[i]);
    }

    return ((cs & 0xFFFF) != 0) && ((cs & 0xFFFF) == ((frame_bytes[frameLength-1] << 8) | frame_bytes[frameLength]));
}

int M10Decoder::update_checkM10(int c, unsigned short b) {
    int c0, c1, t, t6, t7, s;

    c1 = c & 0xFF;

    // B
    b = (b >> 1) | ((b & 1) << 7);
    b ^= (b >> 2) & 0xFF;

    // A1
    t6 = (c & 1) ^ ((c >> 2) & 1) ^ ((c >> 4) & 1);
    t7 = ((c >> 1) & 1) ^ ((c >> 3) & 1) ^ ((c >> 5) & 1);
    t = (c & 0x3F) | (t6 << 6) | (t7 << 7);

    // A2
    s = (c >> 7) & 0xFF;
    s ^= (s >> 2) & 0xFF;


    c0 = b ^ t ^ s;

    return ((c1 << 8) | c0) & 0xFFFF;
}

void M10Decoder::bits2bytes() {
    int i, bit, d, byteval;
    int bitpos, bytepos;

    bitpos = 0;
    bytepos = 0;

    while (bytepos < FRAME_LEN + AUX_LEN) {

        byteval = 0;
        d = 1;
        for (i = 0; i < 8; i++) {
            //bit=*(bitstr+bitpos+i); /* little endian */
            bit = frame_bits[bitpos + 7 - i]; /* big endian */
            if (bit == '1')
                byteval += d;
            else
                byteval += 0;
            d <<= 1;
        }
        bitpos += 8;
        frame_bytes[bytepos++] = byteval & 0xFF;

    }
}
