/*
 * Copyright 2023 Bloomberg LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef INCLUDED_BUILDBOXCOMMON_STREAMINGSTANDARDOUTPUTIFSTREAMFILEMONITOR
#define INCLUDED_BUILDBOXCOMMON_STREAMINGSTANDARDOUTPUTIFSTREAMFILEMONITOR

#include <buildboxcommon_streamingstandardoutputfilemonitor.h>

#include <chrono>
#include <condition_variable>
#include <fstream>
#include <iostream>
#include <mutex>

namespace buildboxcommon {

class
    StreamingStandardOutputIfstreamFileMonitor // NOLINT
                                               // (cppcoreguidelines-special-member-functions)
        final : public StreamingStandardOutputFileMonitor {
    /* This class spawns a thread to periodically poll data from a monitored
     * output file into its internal read buffer. The poller is implemented
     * using `std::ifstream`. When the internal buffer is full or a timeout
     * happens, it writes the data chunk via the callback.
     *
     * Default buffer size is set as 1MB.
     */
  public:
    // Spawns a thread that monitors a file for changes and reads it as it is
    // being written. When new data is available invokes the given callback.
    // NOLINTBEGIN (cppcoreguidelines-avoid-magic-numbers)
    StreamingStandardOutputIfstreamFileMonitor(
        const std::string &path, DataReadyCallback dataReadyCallback,
        int64_t pollIntervalMilliseconds = 5000L,
        int64_t writeTimeoutMilliseconds = 5000L,
        size_t readBufferSize = 1L * 1024L * 1024L);
    // NOLINTEND (cppcoreguidelines-avoid-magic-numbers)
    ~StreamingStandardOutputIfstreamFileMonitor() override;

    StreamingStandardOutputIfstreamFileMonitor(
        const StreamingStandardOutputFileMonitor &) = delete;
    StreamingStandardOutputIfstreamFileMonitor &
    operator=(const StreamingStandardOutputIfstreamFileMonitor &) = delete;

    void stop() override;

  private:
    std::atomic<bool> d_stopRequested = false;
    std::mutex d_mutex;
    std::condition_variable d_cv;

    const std::string d_filePath;
    const DataReadyCallback d_dataReadyCallback;

    // stream of monitored output file
    std::ifstream d_outputStream;

    // chrono configs and state
    const std::chrono::milliseconds d_pollInterval;
    const std::chrono::milliseconds d_writeTimeout;

    const size_t d_readBufferSize;

    std::thread d_monitoringThread;

    // function running in the thread
    void monitor();
    // read data into buffer
    std::streamsize readChunkFromOutput(char *buffer,
                                        const size_t bufferOffset);
    // helper function to determin whether data in buffer should be flushed
    bool shouldWriteChunk(
        const size_t bufferOffset,
        std::chrono::time_point<std::chrono::system_clock> &lastWriteTime);
};

} // namespace buildboxcommon

#endif // INCLUDED_BUILDBOXCOMMON_STREAMINGSTANDARDOUTPUTIFSTREAMFILEMONITOR
