/*
   Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012
   Her Majesty the Queen in Right of Canada (Communications Research
   Center Canada)

   Copyright (C) 2015
   Matthias P. Braendli, matthias.braendli@mpb.li

    http://opendigitalradio.org
 */
/*
   This file is part of ODR-DabMod.

   ODR-DabMod 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.

   ODR-DabMod 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 ODR-DabMod.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <string>

#include "DabModulator.h"
#include "PcDebug.h"

#include "FrameMultiplexer.h"
#include "PrbsGenerator.h"
#include "BlockPartitioner.h"
#include "QpskSymbolMapper.h"
#include "FrequencyInterleaver.h"
#include "PhaseReference.h"
#include "DifferentialModulator.h"
#include "NullSymbol.h"
#include "SignalMultiplexer.h"
#include "CicEqualizer.h"
#include "OfdmGenerator.h"
#include "GainControl.h"
#include "GuardIntervalInserter.h"
#include "Resampler.h"
#include "ConvEncoder.h"
#include "FIRFilter.h"
#include "PuncturingEncoder.h"
#include "TimeInterleaver.h"
#include "TimestampDecoder.h"
#include "RemoteControl.h"
#include "Log.h"

using namespace boost;

DabModulator::DabModulator(
        struct modulator_offset_config& modconf,
        RemoteControllers* rcs,
        Logger& logger,
        unsigned outputRate, unsigned clockRate,
        unsigned dabMode, GainMode gainMode,
        float digGain, float normalise,
        std::string filterTapsFilename
        ) :
    ModCodec(ModFormat(1), ModFormat(0)),
    myLogger(logger),
    myOutputRate(outputRate),
    myClockRate(clockRate),
    myDabMode(dabMode),
    myGainMode(gainMode),
    myDigGain(digGain),
    myNormalise(normalise),
    myEtiReader(EtiReader(modconf, myLogger)),
    myFlowgraph(NULL),
    myFilterTapsFilename(filterTapsFilename),
    myRCs(rcs)
{
    PDEBUG("DabModulator::DabModulator(%u, %u, %u, %u) @ %p\n",
            outputRate, clockRate, dabMode, gainMode, this);

    if (myDabMode == 0) {
        setMode(2);
    } else {
        setMode(myDabMode);
    }
}


DabModulator::~DabModulator()
{
    PDEBUG("DabModulator::~DabModulator() @ %p\n", this);

    delete myFlowgraph;
}


void DabModulator::setMode(unsigned mode)
{
    switch (mode) {
    case 1:
        myNbSymbols = 76;
        myNbCarriers = 1536;
        mySpacing = 2048;
        myNullSize = 2656;
        mySymSize = 2552;
        myFicSizeOut = 288;
        break;
    case 2:
        myNbSymbols = 76;
        myNbCarriers = 384;
        mySpacing = 512;
        myNullSize = 664;
        mySymSize = 638;
        myFicSizeOut = 288;
        break;
    case 3:
        myNbSymbols = 153;
        myNbCarriers = 192;
        mySpacing = 256;
        myNullSize = 345;
        mySymSize = 319;
        myFicSizeOut = 384;
        break;
    case 4:
        myNbSymbols = 76;
        myNbCarriers = 768;
        mySpacing = 1024;
        myNullSize = 1328;
        mySymSize = 1276;
        myFicSizeOut = 288;
        break;
    default:
        throw std::runtime_error("DabModulator::setMode invalid mode size");
    }

    myOutputFormat.size((size_t)((myNullSize + (myNbSymbols * mySymSize))
                * sizeof(complexf) / 2048000.0 * myOutputRate));
}


int DabModulator::process(Buffer* const dataIn, Buffer* dataOut)
{
    PDEBUG("DabModulator::process(dataIn: %p, dataOut: %p)\n",
            dataIn, dataOut);

    myEtiReader.process(dataIn);
    if (myFlowgraph == NULL) {
        unsigned mode = myEtiReader.getMode();
        if (myDabMode != 0) {
            mode = myDabMode;
        } else if (mode == 0) {
            mode = 4;
        }
        setMode(mode);

        myFlowgraph = new Flowgraph();
        ////////////////////////////////////////////////////////////////
        // CIF data initialisation
        ////////////////////////////////////////////////////////////////
        shared_ptr<PrbsGenerator> cifPrbs(new PrbsGenerator(864 * 8, 0x110));
        shared_ptr<FrameMultiplexer> cifMux(
                new FrameMultiplexer(myFicSizeOut + 864 * 8,
                &myEtiReader.getSubchannels()));

        shared_ptr<BlockPartitioner> cifPart(
                new BlockPartitioner(mode, myEtiReader.getFp()));

        shared_ptr<QpskSymbolMapper> cifMap(new QpskSymbolMapper(myNbCarriers));
        shared_ptr<PhaseReference> cifRef(new PhaseReference(mode));
        shared_ptr<FrequencyInterleaver> cifFreq(new FrequencyInterleaver(mode));
        shared_ptr<DifferentialModulator> cifDiff(
                new DifferentialModulator(myNbCarriers));

        shared_ptr<NullSymbol> cifNull(new NullSymbol(myNbCarriers));
        shared_ptr<SignalMultiplexer> cifSig(new SignalMultiplexer(
                (1 + myNbSymbols) * myNbCarriers * sizeof(complexf)));

        // TODO this needs a review
        bool useCicEq = false;
        unsigned cic_ratio = 1;
        if (myClockRate) {
            cic_ratio = myClockRate / myOutputRate;
            cic_ratio /= 4; // FPGA DUC
            if (myClockRate == 400000000) { // USRP2
                if (cic_ratio & 1) { // odd
                    useCicEq = true;
                } // even, no filter
            }
            else {
                useCicEq = true;
            }
        }

        shared_ptr<CicEqualizer> cifCicEq(new CicEqualizer(myNbCarriers,
                (float)mySpacing * (float)myOutputRate / 2048000.0f,
                cic_ratio));


        shared_ptr<OfdmGenerator> cifOfdm(
                new OfdmGenerator((1 + myNbSymbols), myNbCarriers, mySpacing));

        shared_ptr<GainControl> cifGain(
                new GainControl(mySpacing, myGainMode, myDigGain, myNormalise));

        cifGain->enrol_at(*myRCs);

        shared_ptr<GuardIntervalInserter> cifGuard(
                new GuardIntervalInserter(myNbSymbols, mySpacing,
                myNullSize, mySymSize));

        FIRFilter* cifFilter = NULL;
        if (myFilterTapsFilename != "") {
            cifFilter = new FIRFilter(myFilterTapsFilename);
            cifFilter->enrol_at(*myRCs);
        }
        shared_ptr<OutputMemory> myOutput(new OutputMemory(dataOut));

        Resampler* cifRes = NULL;
        if (myOutputRate != 2048000) {
            cifRes = new Resampler(2048000, myOutputRate, mySpacing);
        } else {
            fprintf(stderr, "No resampler\n");
        }

        myFlowgraph->connect(cifPrbs, cifMux);

        ////////////////////////////////////////////////////////////////
        // Processing FIC
        ////////////////////////////////////////////////////////////////
        shared_ptr<FicSource> fic(myEtiReader.getFic());
        ////////////////////////////////////////////////////////////////
        // Data initialisation
        ////////////////////////////////////////////////////////////////
        myFicSizeIn = fic->getFramesize();

        ////////////////////////////////////////////////////////////////
        // Modules configuration
        ////////////////////////////////////////////////////////////////

        // Configuring FIC channel

        PDEBUG("FIC:\n");
        PDEBUG(" Framesize: %zu\n", fic->getFramesize());

        // Configuring prbs generator
        shared_ptr<PrbsGenerator> ficPrbs(new PrbsGenerator(myFicSizeIn, 0x110));

        // Configuring convolutionnal encoder
        shared_ptr<ConvEncoder> ficConv(new ConvEncoder(myFicSizeIn));

        // Configuring puncturing encoder
        shared_ptr<PuncturingEncoder> ficPunc(new PuncturingEncoder());
        std::vector<PuncturingRule*> rules = fic->get_rules();
        std::vector<PuncturingRule*>::const_iterator rule;
        for (rule = rules.begin(); rule != rules.end(); ++rule) {
            PDEBUG(" Adding rule:\n");
            PDEBUG("  Length: %zu\n", (*rule)->length());
            PDEBUG("  Pattern: 0x%x\n", (*rule)->pattern());
            ficPunc->append_rule(*(*rule));
        }
        PDEBUG(" Adding tail\n");
        ficPunc->append_tail_rule(PuncturingRule(3, 0xcccccc));

        myFlowgraph->connect(fic, ficPrbs);
        myFlowgraph->connect(ficPrbs, ficConv);
        myFlowgraph->connect(ficConv, ficPunc);
        myFlowgraph->connect(ficPunc, cifPart);

        ////////////////////////////////////////////////////////////////
        // Configuring subchannels
        ////////////////////////////////////////////////////////////////
        std::vector<shared_ptr<SubchannelSource> > subchannels =
            myEtiReader.getSubchannels();
        std::vector<shared_ptr<SubchannelSource> >::const_iterator subchannel;
        for (subchannel = subchannels.begin();
                subchannel != subchannels.end();
                ++subchannel) {

            ////////////////////////////////////////////////////////////
            // Data initialisation
            ////////////////////////////////////////////////////////////
            size_t subchSizeIn = (*subchannel)->framesize();
            size_t subchSizeOut = (*subchannel)->framesizeCu() * 8;

            ////////////////////////////////////////////////////////////
            // Modules configuration
            ////////////////////////////////////////////////////////////

            // Configuring subchannel
            PDEBUG("Subchannel:\n");
            PDEBUG(" Start address: %zu\n",
                    (*subchannel)->startAddress());
            PDEBUG(" Framesize: %zu\n",
                    (*subchannel)->framesize());
            PDEBUG(" Bitrate: %zu\n", (*subchannel)->bitrate());
            PDEBUG(" Framesize CU: %zu\n",
                    (*subchannel)->framesizeCu());
            PDEBUG(" Protection: %zu\n",
                    (*subchannel)->protection());
            PDEBUG("  Form: %zu\n",
                    (*subchannel)->protectionForm());
            PDEBUG("  Level: %zu\n",
                    (*subchannel)->protectionLevel());
            PDEBUG("  Option: %zu\n",
                    (*subchannel)->protectionOption());

            // Configuring prbs genrerator
            shared_ptr<PrbsGenerator> subchPrbs(
                    new PrbsGenerator(subchSizeIn, 0x110));

            // Configuring convolutionnal encoder
            shared_ptr<ConvEncoder> subchConv(
                    new ConvEncoder(subchSizeIn));

            // Configuring puncturing encoder
            shared_ptr<PuncturingEncoder> subchPunc(
                    new PuncturingEncoder());

            std::vector<PuncturingRule*> rules = (*subchannel)->get_rules();
            std::vector<PuncturingRule*>::const_iterator rule;
            for (rule = rules.begin(); rule != rules.end(); ++rule) {
                PDEBUG(" Adding rule:\n");
                PDEBUG("  Length: %zu\n", (*rule)->length());
                PDEBUG("  Pattern: 0x%x\n", (*rule)->pattern());
                subchPunc->append_rule(*(*rule));
            }
            PDEBUG(" Adding tail\n");
            subchPunc->append_tail_rule(PuncturingRule(3, 0xcccccc));

            // Configuring time interleaver
            shared_ptr<TimeInterleaver> subchInterleaver(
                    new TimeInterleaver(subchSizeOut));

            myFlowgraph->connect(*subchannel, subchPrbs);
            myFlowgraph->connect(subchPrbs, subchConv);
            myFlowgraph->connect(subchConv, subchPunc);
            myFlowgraph->connect(subchPunc, subchInterleaver);
            myFlowgraph->connect(subchInterleaver, cifMux);
        }

        myFlowgraph->connect(cifMux, cifPart);
        myFlowgraph->connect(cifPart, cifMap);
        myFlowgraph->connect(cifMap, cifFreq);
        myFlowgraph->connect(cifRef, cifDiff);
        myFlowgraph->connect(cifFreq, cifDiff);
        myFlowgraph->connect(cifNull, cifSig);
        myFlowgraph->connect(cifDiff, cifSig);
        if (useCicEq) {
            myFlowgraph->connect(cifSig, cifCicEq);
            myFlowgraph->connect(cifCicEq, cifOfdm);
        } else {
            myFlowgraph->connect(cifSig, cifOfdm);
        }
        myFlowgraph->connect(cifOfdm, cifGain);
        myFlowgraph->connect(cifGain, cifGuard);

        if (myFilterTapsFilename != "") {
            shared_ptr<FIRFilter> cifFilterptr(cifFilter);
            myFlowgraph->connect(cifGuard, cifFilterptr);
            if (cifRes != NULL) {
                shared_ptr<Resampler> res(cifRes);
                myFlowgraph->connect(cifFilterptr, res);
                myFlowgraph->connect(res, myOutput);
            } else {
                myFlowgraph->connect(cifFilterptr, myOutput);
            }
        }
        else { //no filtering
            if (cifRes != NULL) {
                shared_ptr<Resampler> res(cifRes);
                myFlowgraph->connect(cifGuard, res);
                myFlowgraph->connect(res, myOutput);
            } else {
                myFlowgraph->connect(cifGuard, myOutput);
            }

        }
    }

    ////////////////////////////////////////////////////////////////////
    // Proccessing data
    ////////////////////////////////////////////////////////////////////
    return myFlowgraph->run();
}