/*
Copyright (c) 2017-2020,
Battelle Memorial Institute; Lawrence Livermore National Security, LLC; Alliance for Sustainable
Energy, LLC.  See the top-level NOTICE for additional details. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
*/

#pragma once
#include "../application_api/Endpoints.hpp"
#include "../application_api/Subscriptions.hpp"
#include "helicsApp.hpp"

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

namespace helics {
class CloningFilter;

namespace apps {
    /** class designed to capture data points from a set of subscriptions or endpoints*/
    class HELICS_CXX_EXPORT Recorder: public App {
      public:
        /** construct from a FederateInfo structure
    @param name the name of the Recorder, can be left empty for the default or to pull from the
    federateInfo object
    @param fi  a federate information structure
    */
        Recorder(const std::string& name, FederateInfo& fi);
        /** construct from command line arguments in a vector
   @param args the command line arguments to pass in a reverse vector
   */
        explicit Recorder(std::vector<std::string> args);
        /** construct from command line arguments*/
        Recorder(int argc, char* argv[]);

        /**constructor taking a federate information structure and using the given core
    @param name the name of the Recorder, can be left empty for the default or to pull from the
    federateInfo object
    @param core a pointer to core object which the federate can join
    @param fi  a federate information structure
    */
        Recorder(const std::string& name,
                 const std::shared_ptr<Core>& core,
                 const FederateInfo& fi);
        /**constructor taking a federate information structure and using the given core
    @param name the name of the federate (can be empty to use defaults from fi)
    @param core a coreApp object that can be joined
    @param fi  a federate information structure
    */
        Recorder(const std::string& name, CoreApp& core, const FederateInfo& fi);
        /**constructor taking a file with the required information
    @param name the name of the app
    @param file a file defining the federate information in JSon or text
    */
        Recorder(const std::string& name, const std::string& file);
        /** move construction*/
        Recorder(Recorder&& other_recorder) = default;
        /** move assignment*/
        Recorder& operator=(Recorder&& other_recorder) = default;
        /** destructor*/
        ~Recorder();
        /** run the Player until the specified time*/
        virtual void runTo(Time runToTime) override;
        /** add a subscription to capture*/
        void addSubscription(const std::string& key);
        /** add an endpoint*/
        void addEndpoint(const std::string& endpoint);
        /** copy all messages that come from a specified endpoint*/
        void addSourceEndpointClone(const std::string& sourceEndpoint);
        /** copy all messages that are going to a specific endpoint*/
        void addDestEndpointClone(const std::string& destEndpoint);
        /** add a capture interface
    @param captureDesc describes a federate to capture all the interfaces for
    */
        void addCapture(const std::string& captureDesc);
        /** save the data to a file*/
        void saveFile(const std::string& filename);
        /** get the number of captured points*/
        auto pointCount() const { return points.size(); }
        /** get the number of captured messages*/
        auto messageCount() const { return messages.size(); }
        /** get a string with the value of point index
    @param index the number of the point to retrieve
    @return a pair with the tag as the first element and the value as the second
    */
        std::pair<std::string, std::string> getValue(int index) const;
        /** get a message
    @details makes a copy of a message and returns it in a unique_ptr
    @param index the number of the message to retrieve
    */
        std::unique_ptr<Message> getMessage(int index) const;

      private:
        /** load from a jsonString
    @param jsonString either a JSON filename or a string containing JSON
    */
        virtual void loadJsonFile(const std::string& jsonString) override;
        /** load a text file*/
        virtual void loadTextFile(const std::string& textFile) override;
        /** helper function to write the date to a JSON file*/
        void writeJsonFile(const std::string& filename);
        /** helper function to write the date to a text file*/
        void writeTextFile(const std::string& filename);

        virtual void initialize() override;
        void generateInterfaces();
        void captureForCurrentTime(Time currentTime, int iteration = 0);
        void loadCaptureInterfaces();
        /** encode the string in base64 if needed otherwise just return the string*/
        std::string encode(const std::string& str2encode);
        /** build the command line argument processing application*/
        std::shared_ptr<helicsCLI11App> buildArgParserApp();
        /** process remaining command line arguments*/
        void processArgs();

      protected:
        /** helper class for capturing data points*/
        class ValueCapture {
          public:
            helics::Time time;
            int index{-1};
            int16_t iteration{0};
            bool first{false};
            std::string value;
            ValueCapture() = default;
            ValueCapture(helics::Time t1, int id1, const std::string& val):
                time(t1), index(id1), value(val)
            {
            }
        };

        /** helper class for displaying statistics*/
        class ValueStats {
          public:
            helics::Time time{helics::Time::minVal()};
            std::string lastVal;
            std::string key;
            int cnt{0};
        };

        bool allow_iteration{false};  //!< trigger to allow Iteration
        bool verbose{false};  //!< print all captured values to the screen
        Time nextPrintTimeStep{
            helics::timeZero};  //!< the time advancement period for printing markers
        std::unique_ptr<CloningFilter> cFilt;  //!< a pointer to a clone filter
        std::vector<ValueCapture> points;  //!< lists of points that were captured
        std::vector<Input> subscriptions;  //!< the actual subscription objects
        std::vector<Endpoint> endpoints;  //!< the actual endpoint objects
        std::unique_ptr<Endpoint> cloneEndpoint;  //!< the endpoint for cloned message delivery
        std::vector<std::unique_ptr<Message>> messages;  //!< list of messages
        std::map<helics::interface_handle, int> subids;  //!< map of the subscription ids
        std::map<std::string, int> subkeys;  //!< translate subscription names to an index
        std::map<helics::interface_handle, int> eptids;  // translate subscription id to index
        std::map<std::string, int> eptNames;  //!< translate endpoint name to index
        std::vector<ValueStats> vStat;  //!< storage for statistics capture
        std::vector<std::string> captureInterfaces;  //!< storage for the interfaces to capture
        std::string mapfile;  //!< file name for the on-line file updater
        std::string outFileName{"out.txt"};  //!< the final output file
    };

}  // namespace apps
}  // namespace helics
