#pragma once

#include "fwd.h"
#include "input.h"
#include "output.h"
#include "buffered.h"

#include <util/system/defaults.h>
#include <util/generic/ptr.h>
#include <util/generic/yexception.h>

/**
 * @addtogroup Streams_Archs
 * @{
 */

struct TZLibError: public yexception {
};

struct TZLibCompressorError: public TZLibError {
};

struct TZLibDecompressorError: public TZLibError {
};

namespace ZLib {
    enum StreamType: ui8 {
        Auto = 0, /**< Auto detect format. Can be used for decompression only. */
        ZLib = 1,
        GZip = 2,
        Raw = 3,
        Invalid = 4
    };

    enum {
        ZLIB_BUF_LEN = 8 * 1024
    };
}

/**
 * Non-buffered ZLib decompressing stream.
 *
 * Please don't use `TZLibDecompress` if you read text data from stream using
 * `ReadLine`, it is VERY slow (approx 10 times slower, according to synthetic
 * benchmark). For fast buffered ZLib stream reading use `TBufferedZLibDecompress`
 * aka `TZDecompress`.
 */
class TZLibDecompress: public IInputStream {
public:
    TZLibDecompress(IZeroCopyInput* input, ZLib::StreamType type = ZLib::Auto, TStringBuf dict = {});
    TZLibDecompress(IInputStream* input, ZLib::StreamType type = ZLib::Auto, size_t buflen = ZLib::ZLIB_BUF_LEN,
                    TStringBuf dict = {});

    /**
     * Allows/disallows multiple sequential compressed streams. Allowed by default.
     *
     * If multiple streams are allowed, their decompressed content will be concatenated.
     * If multiple streams are disabled, then only first stream is decompressed. After that end
     * of IInputStream will have happen, i.e. method Read() will return 0.
     *
     * @param allowMultipleStreams - flag to allow (true) or disable (false) multiple streams.
     */
    void SetAllowMultipleStreams(bool allowMultipleStreams);

    ~TZLibDecompress() override;

protected:
    size_t DoRead(void* buf, size_t size) override;

public:
    class TImpl;
    THolder<TImpl> Impl_;
};

/**
 * Non-buffered ZLib compressing stream.
 */
class TZLibCompress: public IOutputStream {
public:
    struct TParams {
        inline TParams(IOutputStream* out)
            : Out(out)
            , Type(ZLib::ZLib)
            , CompressionLevel(6)
            , BufLen(ZLib::ZLIB_BUF_LEN)
        {
        }

        inline TParams& SetType(ZLib::StreamType type) noexcept {
            Type = type;

            return *this;
        }

        inline TParams& SetCompressionLevel(size_t level) noexcept {
            CompressionLevel = level;

            return *this;
        }

        inline TParams& SetBufLen(size_t buflen) noexcept {
            BufLen = buflen;

            return *this;
        }

        inline TParams& SetDict(const TStringBuf dict) noexcept {
            Dict = dict;

            return *this;
        }

        IOutputStream* Out;
        ZLib::StreamType Type;
        size_t CompressionLevel;
        size_t BufLen;
        TStringBuf Dict;
    };

    inline TZLibCompress(const TParams& params) {
        Init(params);
    }

    inline TZLibCompress(IOutputStream* out, ZLib::StreamType type) {
        Init(TParams(out).SetType(type));
    }

    inline TZLibCompress(IOutputStream* out, ZLib::StreamType type, size_t compression_level) {
        Init(TParams(out).SetType(type).SetCompressionLevel(compression_level));
    }

    inline TZLibCompress(IOutputStream* out, ZLib::StreamType type, size_t compression_level, size_t buflen) {
        Init(TParams(out).SetType(type).SetCompressionLevel(compression_level).SetBufLen(buflen));
    }

    ~TZLibCompress() override;

private:
    void Init(const TParams& opts);

    void DoWrite(const void* buf, size_t size) override;
    void DoFlush() override;
    void DoFinish() override;

public:
    class TImpl;

    /** To allow inline constructors. */
    struct TDestruct {
        static void Destroy(TImpl* impl);
    };

    THolder<TImpl, TDestruct> Impl_;
};

/**
 * Buffered ZLib decompressing stream.
 *
 * Supports efficient `ReadLine` calls and similar "reading in small pieces"
 * usage patterns.
 */
class TBufferedZLibDecompress: public TBuffered<TZLibDecompress> {
public:
    template <class T>
    inline TBufferedZLibDecompress(T* in, ZLib::StreamType type = ZLib::Auto, size_t buf = 1 << 13)
        : TBuffered<TZLibDecompress>(buf, in, type)
    {
    }

    ~TBufferedZLibDecompress() override;
};

/** @} */