// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef LIBBRILLO_BRILLO_STREAMS_FAKE_STREAM_H_
#define LIBBRILLO_BRILLO_STREAMS_FAKE_STREAM_H_

#include <queue>
#include <string>

#include <base/callback_forward.h>
#include <base/macros.h>
#include <base/time/clock.h>
#include <base/time/time.h>
#include <brillo/secure_blob.h>
#include <brillo/streams/stream.h>

namespace brillo {

// Fake stream implementation for testing.
// This class allows to provide data for the stream in tests that can be later
// read through the Stream interface. Also, data written into the stream can be
// later inspected and verified.
//
// NOTE: This class provides a fake implementation for streams with separate
// input and output channels. That is, read and write operations do not affect
// each other. Also, the stream implementation is sequential only (no seeking).
// Good examples of a use case for fake stream are:
//  - read-only sequential streams (file, memory, pipe, ...)
//  - write-only sequential streams (same as above)
//  - independent channel read-write streams (sockets, ...)
//
// For more complex read/write stream test scenarios using a real MemoryStream
// or temporary FileStream is probably a better choice.
class FakeStream : public Stream {
 public:
  // Construct a new instance of the fake stream.
  //   mode        - expected read/write mode supported by the stream.
  //   clock       - the clock to use to get the current time.
  FakeStream(Stream::AccessMode mode,
             base::Clock* clock);

  // Add data packets to the read queue of the stream.
  // Optional |delay| indicates that the data packet should be delayed.
  void AddReadPacketData(base::TimeDelta delay, const void* data, size_t size);
  void AddReadPacketData(base::TimeDelta delay, brillo::Blob data);
  void AddReadPacketString(base::TimeDelta delay, const std::string& data);

  // Schedule a read error by adding a special error packet to the queue.
  void QueueReadError(base::TimeDelta delay);
  void QueueReadErrorWithMessage(base::TimeDelta delay,
                                 const std::string& message);

  // Resets read queue and clears any input data buffers.
  void ClearReadQueue();

  // Add expectations for output data packets to be written by the stream.
  // Optional |delay| indicates that the initial write operation for the data in
  // the packet should be delayed.
  // ExpectWritePacketSize just limits the size of output packet while
  // ExpectWritePacketData also validates that the data matches that of |data|.
  void ExpectWritePacketSize(base::TimeDelta delay, size_t data_size);
  void ExpectWritePacketData(base::TimeDelta delay,
                             const void* data,
                             size_t size);
  void ExpectWritePacketData(base::TimeDelta delay, brillo::Blob data);
  void ExpectWritePacketString(base::TimeDelta delay, const std::string& data);

  // Schedule a write error by adding a special error packet to the queue.
  void QueueWriteError(base::TimeDelta delay);
  void QueueWriteErrorWithMessage(base::TimeDelta delay,
                                  const std::string& message);

  // Resets write queue and clears any output data buffers.
  void ClearWriteQueue();

  // Returns the output data accumulated so far by all complete write packets,
  // or explicitly flushed.
  const brillo::Blob& GetFlushedOutputData() const;
  std::string GetFlushedOutputDataAsString() const;

  // Overrides from brillo::Stream.
  bool IsOpen() const override { return is_open_; }
  bool CanRead() const override;
  bool CanWrite() const override;
  bool CanSeek() const override { return false; }
  bool CanGetSize() const override { return false; }
  uint64_t GetSize() const override { return 0; }
  bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override;
  uint64_t GetRemainingSize() const override { return 0; }
  uint64_t GetPosition() const override { return 0; }
  bool Seek(int64_t offset,
            Whence whence,
            uint64_t* new_position,
            ErrorPtr* error) override;

  bool ReadNonBlocking(void* buffer,
                       size_t size_to_read,
                       size_t* size_read,
                       bool* end_of_stream,
                       ErrorPtr* error) override;
  bool WriteNonBlocking(const void* buffer,
                        size_t size_to_write,
                        size_t* size_written,
                        ErrorPtr* error) override;
  bool FlushBlocking(ErrorPtr* error) override;
  bool CloseBlocking(ErrorPtr* error) override;
  bool WaitForData(AccessMode mode,
                   const base::Callback<void(AccessMode)>& callback,
                   ErrorPtr* error) override;
  bool WaitForDataBlocking(AccessMode in_mode,
                           base::TimeDelta timeout,
                           AccessMode* out_mode,
                           ErrorPtr* error) override;

 private:
  // Input data packet to be placed on the read queue.
  struct InputDataPacket {
    brillo::Blob data;  // Data to be read.
    base::TimeDelta delay_before;  // Possible delay for the first read.
    bool read_error{false};  // Set to true if this packet generates an error.
  };

  // Output data packet to be placed on the write queue.
  struct OutputDataPacket {
    size_t expected_size{0};  // Output packet size
    brillo::Blob data;  // Possible data to verify the output with.
    base::TimeDelta delay_before;  // Possible delay for the first write.
    bool write_error{false};  // Set to true if this packet generates an error.
  };

  // Check if there is any pending read data in the input buffer.
  bool IsReadBufferEmpty() const;
  // Pops the next read packet from the queue and sets its data into the
  // internal input buffer.
  bool PopReadPacket();

  // Check if the output buffer is full.
  bool IsWriteBufferFull() const;

  // Moves the current full output buffer into |all_output_data_|, clears the
  // buffer, and pops the information about the next expected output packet
  // from the write queue.
  bool PopWritePacket();

  bool is_open_{true};
  Stream::AccessMode mode_;
  base::Clock* clock_;

  // Internal data for read operations.
  std::queue<InputDataPacket> incoming_queue_;
  base::Time delay_input_until_;
  brillo::Blob input_buffer_;
  size_t input_ptr_{0};
  bool report_read_error_{false};

  // Internal data for write operations.
  std::queue<OutputDataPacket> outgoing_queue_;
  base::Time delay_output_until_;
  brillo::Blob output_buffer_;
  brillo::Blob expected_output_data_;
  size_t max_output_buffer_size_{0};
  bool report_write_error_{false};
  brillo::Blob all_output_data_;

  DISALLOW_COPY_AND_ASSIGN(FakeStream);
};

}  // namespace brillo

#endif  // LIBBRILLO_BRILLO_STREAMS_FAKE_STREAM_H_