// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "CPUUsageInfo.h"
#include "ClockHandler.h"
#include "IConnectionTypes.h"
#include "ILogger.h"
#include "ISender.h"
#include "LogLevel.h"
#include "LoggingModule.h"
#include "MemoryUsageInfo.h"
#include "Thread.h"
#include "TraceModule.h"
#include <json/json.h>
#include <memory>
#include <string>

namespace Aws
{
namespace IoTFleetWise
{
namespace OffboardConnectivity
{
using namespace Aws::IoTFleetWise::Platform::Linux;
class RemoteProfiler : public IMetricsReceiver, public ILogger
{
public:
    static const uint16_t MAX_PARALLEL_METRICS = 10;
    static const char *NAME_TOP_LEVEL_LOG_ARRAY;
    static const char *NAME_TOP_LEVEL_LOG_PREFIX;

    static const uint32_t MAX_BYTES_FOR_SINGLE_LOG_UPLOAD =
        16384; // 16KiB. Must be << 128KiB because its sent in one MQTT message
    static const uint32_t JSON_MAX_OVERHEAD_BYTES_PER_LOG = 60; // Including LogLevel

    /**
     * @brief Construct the RemoteProfiler which can upload metrics and logs over MQTT
     *
     * @param metricsSender the channel that should be used to upload metrics
     * @param logSender the channel that should be used to upload logs
     * @param initialMetricsUploadInterval the interval used between two metrics uploads
     * @param initialLogMaxInterval the max interval that logs can be cached before uploading
     * @param initialLogLevelThresholdToSend all logs below this threshold will be ignored
     * @param profilerPrefix this prefix will be added to the metrics name and can for example be used to distinguish
     * specific vehicles
     *
     */
    RemoteProfiler( std::shared_ptr<ISender> metricsSender,
                    std::shared_ptr<ISender> logSender,
                    uint32_t initialMetricsUploadInterval,
                    uint32_t initialLogMaxInterval,
                    LogLevel initialLogLevelThresholdToSend,
                    std::string profilerPrefix );

    /**
     * @brief Implement the ILogger interface
     *
     * Packs the log string into a json and uploads to the cloud if necessary
     * Can be called from multiple threads.
     *
     * @param level the log level used to decide if log entry should be uploaded
     * @param filename the name of the file which emitted the log message
     * @param lineNumber line number
     * @param function the name of the function which emitted the log message
     * @param logEntry the actual log message
     */
    void logMessage( LogLevel level,
                     const std::string &filename,
                     const uint32_t lineNumber,
                     const std::string &function,
                     const std::string &logEntry ) override;

    /**
     * @brief implements IMetricsReceiver and uploads metrics over MQTT
     *
     * @param name the name that is displayed
     * @param value the value as double
     * @param unit the unit can be for example: Seconds | Microseconds | Milliseconds | Bytes | Kilobytes | Megabytes |
     * Gigabytes | Terabytes | Bits | Kilobits | Megabits | Gigabits | Terabits | Percent | Count | Bytes/Second |
     * Kilobytes/Second | Megabytes/Second | Gigabytes/Second | Terabytes/Second | Bits/Second | Kilobits/Second |
     * Megabits/Second | Gigabits/Second | Terabits/Second | Count/Second | None
     */
    void setMetric( const std::string &name, double value, const std::string &unit ) override;

    /**
     * @brief start the thread
     * @return true if thread starting was successful
     */
    bool start();

    /**
     * @brief stops the thread
     * @return true if thread stopping was successful
     */
    bool stop();

    /**
     * @brief check if thread is currently running
     * @return true if thread is currently running
     */
    bool
    isAlive()
    {
        return fThread.isValid() && fThread.isActive();
    }

    ~RemoteProfiler() override
    {
        // To make sure the thread stops during teardown of tests.
        if ( isAlive() )
        {
            stop();
        }
    }

    RemoteProfiler( const RemoteProfiler & ) = delete;
    RemoteProfiler &operator=( const RemoteProfiler & ) = delete;
    RemoteProfiler( RemoteProfiler && ) = delete;
    RemoteProfiler &operator=( RemoteProfiler && ) = delete;

private:
    static void doWork( void *data );

    void sendMetricsOut();

    void sendLogsOut();

    void collectExecutionEnvironmentMetrics();

    void initLogStructure();

    Thread fThread;
    std::atomic<bool> fShouldStop;
    std::shared_ptr<ISender> fMetricsSender;
    std::shared_ptr<ISender> fLogSender;
    std::mutex fThreadMutex;
    std::mutex loggingMutex;
    Signal fWait;
    Json::Value fMetricsRoot;
    Json::Value fLogRoot;
    uint16_t fCurrentMetricsPending;
    uint32_t fInitialUploadInterval;
    uint32_t fInitialLogMaxInterval;
    std::shared_ptr<const Clock> fClock = ClockHandler::getClock();
    Timestamp fLastTimeMetricsSentOut;
    Timestamp fLastTimeMLogsSentOut;
    Timestamp fLastTimeExecutionEnvironmentMetricsCollected;
    LogLevel fLogLevelThreshold;
    CPUUsageInfo fLastCPURUsage;
    CPUUsageInfo::ThreadCPUUsageInfos fLastThreadUsage;
    MemoryUsageInfo fMemoryUsage;
    std::string fProfilerPrefix;
    uint32_t fCurrentUserPayloadInLogRoot;
};
} // namespace OffboardConnectivity
} // namespace IoTFleetWise
} // namespace Aws