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

   Copyright (C) 2025
   Matthias P. Braendli, matthias.braendli@mpb.li
   */
/*
   This file is part of ODR-DabMux.

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

#pragma once

#ifdef HAVE_CONFIG_H
#   include "config.h"
#endif

#include "dabOutput/dabOutput.h"
#include "edioutput/Transport.h"
#include "fig/FIGCarousel.h"
#include "fig/FIGCarouselPriority.h"
#include "fig/FIGSchedulerType.h"
#include "MuxElements.h"
#include "RemoteControl.h"
#include "ClockTAI.h"
#include <vector>
#include <memory>
#include <optional>
#include <string>
#include <memory>
#include <boost/property_tree/ptree.hpp>

constexpr uint32_t ETI_FSYNC1 = 0x49C5F8;

class MuxTime {
    private:
        std::time_t m_edi_time = 0;
        uint32_t m_pps_offset_ms = 0;
        int64_t m_tist_offset_ms = 0;

    public:
        std::pair<uint32_t, std::time_t> get_tist_seconds();
        std::pair<uint32_t, std::time_t> get_milliseconds_seconds();


        /* Pre v3 odr-dabmux did the MNSC calculation differently,
         * which works with the easydabv2. The rework in odr-dabmux,
         * deriving MNSC time from EDI time broke this.
         *
         * That's why we're now tracking MNSC time in separate variables,
         * to get the same behaviour back.
         *
         * I'm not aware of any devices using MNSC time besides the
         * easydab. ODR-DabMod now considers EDI seconds or ZMQ metadata.
         */
        bool mnsc_increment_time = false;
        std::time_t mnsc_time = 0;

        /* Setup the time and return the initial currentFrame counter value */
        uint64_t init(uint32_t tist_at_fct0_ms, double tist_offset);
        void increment_timestamp();
        double tist_offset() const { return m_tist_offset_ms / 1000.0; }
        void set_tist_offset(double new_tist_offset);
};

class DabMultiplexerConfig {
    public:
        boost::property_tree::ptree pt;

        void read(const std::string& filename);
        bool valid() const { return m_config_file != ""; }
        std::string config_file() const { return m_config_file; }

    private:
        std::string m_config_file;
};

class DabMultiplexer : public RemoteControllable {
    public:
        DabMultiplexer(DabMultiplexerConfig& config, ClockTAI& clock_tai);

        void prepare(bool require_tai_clock);

        void mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs);

        void print_info();

        void set_edi_config(const edi::configuration_t& new_edi_conf);

        /* Remote control */
        virtual void set_parameter(const std::string& parameter,
               const std::string& value);

        /* Getting a parameter always returns a string. */
        virtual const std::string get_parameter(const std::string& parameter) const;

        virtual const json::map_t get_all_values() const;

    private:
        void prepare_subchannels();
        void prepare_services_components();
        void prepare_data_inputs();

        void reload_linking();

        DabMultiplexerConfig& m_config;

        MuxTime m_time;
        uint64_t currentFrame = 0;

        edi::configuration_t edi_conf;
        std::shared_ptr<edi::Sender> edi_sender;

        std::shared_ptr<dabEnsemble> ensemble;

        bool m_tai_clock_required = false;
        ClockTAI& m_clock_tai;

        /* FIG Carousel - supports classic and priority schedulers
         *
         * Only one of these will be instantiated based on config.
         * The scheduler type is determined by ensemble->fic_scheduler
         * which is set during config parsing in prepare().
         */
        FIC::FIGSchedulerType m_scheduler_type = FIC::FIGSchedulerType::Classic;
        std::optional<FIC::FIGCarousel> m_fig_carousel_classic;
        std::optional<FIC::FIGCarouselPriority> m_fig_carousel_priority;

        /* Helper method for FIG carousel write_fibs */
        size_t fig_carousel_write_fibs(uint8_t* buf, uint64_t current_frame, bool fib3_present);
};
