/* ------------------------------------------------------------------
 * Copyright (C) 2017 AVT GmbH - Fabien Vercasson
 * Copyright (C) 2016 Matthias P. Braendli
 *                    matthias.braendli@mpb.li
 *
 * http://opendigitalradio.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 * -------------------------------------------------------------------
 */

#pragma once
#include <stdio.h>
#include <cassert>
#include <vector>
#include <map>
#include <stdint.h>

namespace EdiDecoder {
namespace PFT {

using pseq_t = uint16_t;
using findex_t = uint32_t; // findex is a 24-bit value

class Fragment
{
    public:
        // Load the data for one fragment from buf into
        // the Fragment.
        // \returns the number of bytes of useful data found in buf
        // A non-zero return value doesn't imply a valid fragment
        // the isValid() method must be used to verify this.
        size_t loadData(const std::vector<uint8_t> &buf);

        bool isValid() const { return _valid; }
        pseq_t Pseq() const { return _Pseq; }
        findex_t Findex() const { return _Findex; }
        findex_t Fcount() const { return _Fcount; }
        bool FEC() const { return _FEC; }
        uint16_t Plen() const { return _Plen; }
        uint8_t RSk() const { return _RSk; }
        uint8_t RSz() const { return _RSz; }
        const std::vector<uint8_t>& payload() const
            { return _payload; }

        bool checkConsistency(const Fragment& other) const;

    private:
        std::vector<uint8_t> _payload;

        pseq_t _Pseq;
        findex_t _Findex;
        findex_t _Fcount;
        bool _FEC;
        bool _Addr;
        uint16_t _Plen;
        uint8_t _RSk;
        uint8_t _RSz;
        uint16_t _Source;
        uint16_t _Dest;
        bool _valid;
};

/* The AFBuilder collects Fragments and builds an Application Frame
 * out of them. It does error correction if necessary
 */
class AFBuilder
{
    public:
        enum class decode_attempt_result_t {
            yes,   // The AF packet can be build because all fragments are present
            maybe, // RS decoding may correctly decode the AF packet
            no,    // Not enough fragments present to permit RS
        };

        AFBuilder(pseq_t Pseq, findex_t Fcount, int lifetime);

        void pushPFTFrag(const Fragment &frag);

        /* Assess if it may be possible to decode this AF packet */
        decode_attempt_result_t canAttemptToDecode() const;

        /* Try to build the AF with received fragments.
         * Apply error correction if necessary (missing packets/CRC errors)
         * \return an empty vector if building the AF is not possible
         */
        std::vector<uint8_t> extractAF(void) const;

        std::pair<findex_t, findex_t>
            numberOfFragments(void) const {
                return {_fragments.size(), _Fcount};
            }

        /* The user of this instance can keep track of the lifetime of this
         * builder
         */
        int lifeTime;

    private:

        // A map from fragment index to fragment
        std::map<findex_t, Fragment> _fragments;

        // cached version of decoded AF packet
        mutable std::vector<uint8_t> _af_packet;

        pseq_t _Pseq;
        findex_t _Fcount;
};

class PFT
{
    public:
        void pushPFTFrag(const Fragment &fragment);

        /* Try to build the AF packet for the next pseq. This might
         * skip one pseq according to the maximum delay setting.
         *
         * \return an empty vector if building the AF is not possible
         */
        std::vector<uint8_t> getNextAFPacket(void);

        /* Set the maximum delay in number of AF Packets before we
         * abandon decoding a given pseq.
         */
        void setMaxDelay(int num_af_packets);

    private:
        bool isPacketBuildable(pseq_t pseq) const;

        void incrementNextPseq(void);

        pseq_t m_next_pseq;
        int m_max_delay = 10;

        // Keep one AFBuilder for each Pseq
        std::map<pseq_t, AFBuilder> m_afbuilders;

};

}

}