/*
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/Publications.hpp"
#include "../application_api/Subscriptions.hpp"
#include "helicsApp.hpp"

#include <map>
#include <memory>
#include <set>

namespace helics {
class CloningFilter;

namespace apps {
    /** class designed to capture data points from a set of subscriptions or endpoints*/
    class HELICS_CXX_EXPORT Clone: 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
    */
        Clone(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 Clone(std::vector<std::string> args);
        /** construct from command line arguments*/
        Clone(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
    */
        Clone(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
    */
        Clone(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
    */
        Clone(const std::string& name, const std::string& file);
        /** move construction*/
        Clone(Clone&& other_recorder) = default;
        /** move assignment*/
        Clone& operator=(Clone&& record) = default;
        /** destructor*/
        ~Clone();
        /** run the Cloner until the specified time*/
        virtual void runTo(Time runToTime) override;
        /** save the data to a file*/
        void saveFile(const std::string& filename = std::string{});
        /** 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;

        /** set the name of the federate to Clone
    @param federateName the name of the federate to clone
    */
        void setFederateToClone(const std::string& federateName);
        /** set the name of the output file
    @param fileName  the name of the file, can be "" if no file should be auto saved*/
        void setOutputFile(std::string fileName) { outFileName = std::move(fileName); }

      private:
        /** add a subscription to capture*/
        void addSubscription(const std::string& key);

        /** copy all messages that come from a specified endpoint*/
        void addSourceEndpointClone(const std::string& sourceEndpoint);

        virtual void initialize() override;
        void generateInterfaces();
        void captureForCurrentTime(Time currentTime, int iteration = 0);
        /** 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){};
        };

        bool allow_iteration = false; //!< trigger to allow Iteration
        bool verbose = false; //!< print all captured values to the screen
        bool fileSaved = false; //!< true if the file has been saved already
        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<std::string>
            cloneSubscriptionNames; //!< string of the subscriptions of the cloned federate
        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::string captureFederate; //!< storage for the name of the federate to clone
        std::string fedConfig; //!< storage for the federateConfiguration
        std::string outFileName{"clone.json"}; //!< the final output file
        std::vector<int> pubPointCount; //!< a	vector containing the counts of each publication
    };

} // namespace apps
} // namespace helics
