#ifndef WAVE_H
#define WAVE_H

#include <fstream>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
#include <cstdint>

/**
 * @brief Custom exception class for WAV file operations
 *
 * Thrown when errors occur during reading or writing WAV files,
 * such as invalid format, I/O errors, or unsupported features.
 */
class WaveError : public std::runtime_error {
public:
    /**
     * @brief Construct a new Wave Error object
     *
     * @param msg Error message describing the issue
     */
    explicit WaveError(const std::string& msg) : std::runtime_error(msg) {}
};

/**
 * @brief Structure containing WAV file parameters
 *
 * Holds metadata about a WAV file including audio format details
 * such as channel count, sample width, and frame rate.
 */
struct WaveParams {
    int nchannels;      ///< Number of audio channels (1 for mono, 2 for stereo)
    int sampwidth;      ///< Sample width in bytes (e.g., 2 for 16-bit samples)
    int framerate;      ///< Audio sample rate in Hz (e.g., 44100)
    int nframes;        ///< Total number of audio frames
    std::string comptype;  ///< Compression type (typically "NONE" for uncompressed)
    std::string compname;  ///< Compression name
};

/**
 * @brief Class for reading WAV audio files
 *
 * Provides functionality to open, read, and inspect WAV files,
 * handling header parsing and audio data extraction.
 */
class WaveRead {
public:
    /**
     * @brief Construct a new Wave Read object from a file
     *
     * @param filename Path to the WAV file to open
     * @throws WaveError if file cannot be opened or is invalid
     */
    explicit WaveRead(const std::string& filename);

    /**
     * @brief Construct a new Wave Read object from a stream
     *
     * @param stream Input stream containing WAV data
     * @throws WaveError if stream is invalid or contains invalid WAV data
     */
    explicit WaveRead(std::istream& stream);

    /**
     * @brief Destroy the Wave Read object
     *
     * Closes the file stream if it was opened by this object
     */
    ~WaveRead();

    // Disable copy
    WaveRead(const WaveRead&) = delete;
    WaveRead& operator=(const WaveRead&) = delete;

    // Allow move
    WaveRead(WaveRead&& other) noexcept;
    WaveRead& operator=(WaveRead&& other) noexcept;

    /**
     * @brief Rewind to the beginning of the audio data
     *
     * Resets the read position to the start of the sample data
     */
    void rewind();

    /**
     * @brief Close the WAV file
     *
     * Releases any resources associated with the file
     */
    void close();

    /**
     * @brief Get current read position in frames
     *
     * @return Current position in number of frames from the start
     */
    int tell() const;

    /**
     * @brief Get number of audio channels
     *
     * @return Number of channels (1 for mono, 2 for stereo)
     */
    int get_channels() const;

    /**
     * @brief Get total number of audio frames
     *
     * @return Total number of frames in the WAV file
     */
    int get_frames() const;

    /**
     * @brief Get sample width in bytes
     *
     * @return Sample width (e.g., 2 for 16-bit samples)
     */
    int get_sampwidth() const;

    /**
     * @brief Get audio sample rate
     *
     * @return Sample rate in Hz
     */
    int get_framerate() const;

    /**
     * @brief Get compression type
     *
     * @return Compression type string (typically "NONE")
     */
    std::string get_comptype() const;

    /**
     * @brief Get compression name
     *
     * @return Compression name string
     */
    std::string get_compname() const;

    /**
     * @brief Get all WAV parameters
     *
     * @return WaveParams structure containing all audio parameters
     */
    WaveParams get_params() const;

    /**
     * @brief Read audio frames from the WAV file
     *
     * @param nframes Number of frames to read
     * @return std::vector<char> containing raw audio data
     * @throws WaveError if read fails
     */
    std::vector<char> read_frames(int nframes);

private:
    /**
     * @brief Initialize the file stream and parse headers
     *
     * @param file Input stream to initialize
     */
    void initfp(std::istream& file);

    /**
     * @brief Read and parse the format chunk from the WAV file
     *
     * @param chunk Stream containing the format chunk data
     * @param chunk_size Size of the format chunk in bytes
     */
    void read_fmt_chunk(std::istream& chunk, uint32_t chunk_size);

    std::istream* file_;           ///< Pointer to the input stream
    bool i_opened_the_file_;       ///< Flag indicating if this object owns the stream
    std::ifstream file_stream_;    ///< File stream if opened by this object

    int nchannels_;                ///< Number of audio channels
    int sampwidth_;                ///< Sample width in bytes
    int framerate_;                ///< Sample rate in Hz
    int nframes_;                  ///< Total number of frames
    std::string comptype_;         ///< Compression type
    std::string compname_;         ///< Compression name

    size_t framesize_;             ///< Size of one frame in bytes
    size_t soundpos_;              ///< Current position in frames
    std::streampos data_start_pos_;///< Stream position of the start of audio data
    uint32_t data_size_;           ///< Total size of audio data in bytes
    bool data_seek_needed_;        ///< Flag indicating if a seek is needed before reading
    bool fmt_chunk_read_;          ///< Flag indicating if format chunk was parsed
};

/**
 * @brief Class for writing WAV audio files
 *
 * Provides functionality to create and write WAV files,
 * handling header creation and audio data writing.
 */
class WaveWrite {
public:
    /**
     * @brief Construct a new Wave Write object for a file
     *
     * @param filename Path to the output WAV file
     * @throws WaveError if file cannot be opened
     */
    explicit WaveWrite(const std::string& filename);

    /**
     * @brief Construct a new Wave Write object for a stream
     *
     * @param stream Output stream to write WAV data to
     * @throws WaveError if stream is invalid
     */
    explicit WaveWrite(std::ostream& stream);

    /**
     * @brief Destroy the Wave Write object
     *
     * Finalizes and closes the WAV file if not already closed
     */
    ~WaveWrite();

    // Disable copy
    WaveWrite(const WaveWrite&) = delete;
    WaveWrite& operator=(const WaveWrite&) = delete;

    // Allow move
    WaveWrite(WaveWrite&& other) noexcept;
    WaveWrite& operator=(WaveWrite&& other) noexcept;

    /**
     * @brief Set the number of audio channels
     *
     * @param nchannels Number of channels (1 for mono, 2 for stereo)
     */
    void set_channels(int nchannels);

    /**
     * @brief Get the number of audio channels
     *
     * @return Current number of channels
     */
    int get_channels() const;

    /**
     * @brief Set the sample width in bytes
     *
     * @param sampwidth Sample width (e.g., 2 for 16-bit samples)
     */
    void set_sampwidth(int sampwidth);

    /**
     * @brief Get the sample width in bytes
     *
     * @return Current sample width
     */
    int get_sampwidth() const;

    /**
     * @brief Set the sample rate
     *
     * @param framerate Sample rate in Hz
     */
    void set_framerate(int framerate);

    /**
     * @brief Get the sample rate
     *
     * @return Current sample rate in Hz
     */
    int get_framerate() const;

    /**
     * @brief Set the total number of frames
     *
     * @param nframes Total number of frames
     */
    void set_frames(int nframes);

    /**
     * @brief Get the total number of frames
     *
     * @return Current total number of frames
     */
    int get_frames() const;

    /**
     * @brief Set the compression type and name
     *
     * @param comptype Compression type string
     * @param compname Compression name string
     */
    void set_comptype(const std::string& comptype, const std::string& compname);

    /**
     * @brief Get the compression type
     *
     * @return Current compression type
     */
    std::string get_comptype() const;

    /**
     * @brief Get the compression name
     *
     * @return Current compression name
     */
    std::string get_compname() const;

    /**
     * @brief Set all WAV parameters from a WaveParams structure
     *
     * @param params WaveParams containing desired parameters
     */
    void set_params(const WaveParams& params);

    /**
     * @brief Get all current WAV parameters
     *
     * @return WaveParams structure with current parameters
     */
    WaveParams get_params() const;

    /**
     * @brief Get current write position in frames
     *
     * @return Current position in number of frames
     */
    int tell() const;

    /**
     * @brief Write raw audio frames without validation
     *
     * @param data Raw audio data to write
     */
    void write_frames_raw(const std::vector<char>& data);

    /**
     * @brief Write audio frames with validation
     *
     * @param data Audio data to write
     * @throws WaveError if data size is invalid for current parameters
     */
    void write_frames(const std::vector<char>& data);

    /**
     * @brief Close the WAV file
     *
     * Finalizes the header and releases resources
     */
    void close();

private:
    /**
     * @brief Ensure the WAV header is written before data
     *
     * @param datasize Expected size of audio data in bytes
     */
    void ensure_header_written(size_t datasize);

    /**
     * @brief Write the initial WAV header
     *
     * @param initlength Initial length for header fields
     */
    void write_header(size_t initlength);

    /**
     * @brief Update the header with final values
     *
     * Patches the header with actual data size after writing is complete
     */
    void patch_header();

    std::ostream* file_;           ///< Pointer to the output stream
    bool i_opened_the_file_;       ///< Flag indicating if this object owns the stream
    std::ofstream file_stream_;    ///< File stream if opened by this object

    int nchannels_;                ///< Number of audio channels
    int sampwidth_;                ///< Sample width in bytes
    int framerate_;                ///< Sample rate in Hz
    int nframes_;                  ///< Total number of frames
    std::string comptype_;         ///< Compression type
    std::string compname_;         ///< Compression name

    int nframes_written_;          ///< Number of frames written so far
    size_t data_written_;          ///< Total bytes of audio data written
    size_t data_length_;           ///< Total length of audio data
    bool header_written_;          ///< Flag indicating if header has been written
    std::streampos form_length_pos_;///< Stream position for format length field
    std::streampos data_length_pos_;///< Stream position for data length field
};

/**
 * @brief Check if a file is a valid WAV file
 *
 * @param filename Path to the file to check
 * @return true if the file is a valid WAV file
 * @return false otherwise
 */
bool is_valid_wave_file(const std::string& filename);

/**
 * @brief Open a WAV file for reading
 *
 * @param filename Path to the WAV file
 * @return std::unique_ptr<WaveRead> Pointer to WaveRead object
 * @throws WaveError if file cannot be opened or is invalid
 */
std::unique_ptr<WaveRead> open_read(const std::string& filename);

/**
 * @brief Open a stream for reading WAV data
 *
 * @param stream Input stream containing WAV data
 * @return std::unique_ptr<WaveRead> Pointer to WaveRead object
 * @throws WaveError if stream is invalid
 */
std::unique_ptr<WaveRead> open_read(std::istream& stream);

/**
 * @brief Open a WAV file for writing
 *
 * @param filename Path to the output WAV file
 * @return std::unique_ptr<WaveWrite> Pointer to WaveWrite object
 * @throws WaveError if file cannot be opened
 */
std::unique_ptr<WaveWrite> open_write(const std::string& filename);

/**
 * @brief Open a stream for writing WAV data
 *
 * @param stream Output stream to write WAV data to
 * @return std::unique_ptr<WaveWrite> Pointer to WaveWrite object
 * @throws WaveError if stream is invalid
 */
std::unique_ptr<WaveWrite> open_write(std::ostream& stream);

/**
 * @brief Polymorphic base class for WAV files
 *
 * Provides a common interface for both reading and writing WAV files
 */
class WaveFile {
public:
    virtual ~WaveFile() = default;

    /**
     * @brief Get the reader interface (if available)
     *
     * @return WaveRead* Pointer to reader or nullptr if not a read file
     */
    virtual WaveRead* get_reader() = 0;

    /**
     * @brief Get the writer interface (if available)
     *
     * @return WaveWrite* Pointer to writer or nullptr if not a write file
     */
    virtual WaveWrite* get_writer() = 0;
};

/**
 * @brief WaveFile implementation for reading WAV files
 */
class WaveReadFile : public WaveFile {
public:
    /**
     * @brief Construct a new Wave Read File object
     *
     * @param filename Path to the WAV file
     */
    explicit WaveReadFile(const std::string& filename) : reader(filename) {}

    /**
     * @brief Construct a new Wave Read File object
     *
     * @param stream Input stream containing WAV data
     */
    explicit WaveReadFile(std::istream& stream) : reader(stream) {}

    /**
     * @brief Get the reader interface
     *
     * @return WaveRead* Pointer to the reader
     */
    WaveRead* get_reader() override { return &reader; }

    /**
     * @brief Get the writer interface (not available)
     *
     * @return nullptr always
     */
    WaveWrite* get_writer() override { return nullptr; }

    WaveRead reader;   ///< The underlying WaveRead object
};

/**
 * @brief WaveFile implementation for writing WAV files
 */
class WaveWriteFile : public WaveFile {
public:
    /**
     * @brief Construct a new Wave Write File object
     *
     * @param filename Path to the output WAV file
     */
    explicit WaveWriteFile(const std::string& filename) : writer(filename) {}

    /**
     * @brief Construct a new Wave Write File object
     *
     * @param stream Output stream to write WAV data to
     */
    explicit WaveWriteFile(std::ostream& stream) : writer(stream) {}

    /**
     * @brief Get the reader interface (not available)
     *
     * @return nullptr always
     */
    WaveRead* get_reader() override { return nullptr; }

    /**
     * @brief Get the writer interface
     *
     * @return WaveWrite* Pointer to the writer
     */
    WaveWrite* get_writer() override { return &writer; }

    WaveWrite writer;  ///< The underlying WaveWrite object
};

/**
 * @brief Polymorphic open function for WAV files
 *
 * @param filename Path to the WAV file
 * @param mode Open mode ("r" for read, "w" for write)
 * @return std::unique_ptr<WaveFile> Pointer to WaveFile object
 * @throws WaveError if mode is invalid or file cannot be opened
 */
std::unique_ptr<WaveFile> open(const std::string& filename, const std::string& mode);

#endif // WAVE_H