/*
 * Logserver
 * Copyright (C) 2017-2025 Joel Reardon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef __IB__ZCAT__H__
#define __IB__ZCAT__H__

#include <memory>
#include <string>
#include <sstream>
#include <string.h>
#include <assert.h>
#include "zlib.h"

using namespace std;

/* helper class for using zlib. */
class ZCat {
public:
	/* gunzip the data string */
	static string zcat(const string& data) {
		return zcat(data, nullptr);
	}

	/* gunzip the data string and count how much wasn't gunziped */
	static string zcat(const string& data, size_t* leftover) {
		uint8_t buf[1 << 17];
		return zcat(data, leftover, buf, 1 << 17, 0);
	}

	// returns true if the gzip magic number matches at start
	static bool magic(const string& str) {
		return magic(str, 0);
	}

	// returns true if the zlib or gzip magic number matches at position pos
	static bool magic(const string& str, size_t pos) {
		return magic_zlib(str, pos) || magic_gzip(str, pos);
	}

	// returns true if the gzip magic number matches at position pos
	static bool magic_gzip(const string& str, size_t pos) {
		if (pos + 2 >= str.length()) return false;
		return str[pos] == (char) 0x1f &&
		       str[pos + 1] == (char) 0x8b &&
	               str[pos + 2] == (char) 0x08;
	}

	// returns true if the zlib magic number matches at position pos
	static bool magic_zlib(const string& str, size_t pos) {
		if (pos + 1 >= str.length()) return false;
		if (str[pos] == (char) 0x78 &&
		    str[pos + 1] == (char) 0x9c) return true;
		if (str[pos] == (char) 0x78 &&
		    str[pos + 1] == (char) 0x5e) return true;
		if (str[pos] == (char) 0x78 &&
		    str[pos + 1] == (char) 0xda) return true;
		return false;
	}

	// tries to gunzip as much of data as it can, assuming there is some
	// gzipped data at the start of data but unclear where it ends
	static string zcat(const string& data, size_t* leftover,
			   uint8_t *out, size_t CHUNK, int mode = 0) {
		int ret;
		unsigned have;
		z_stream strm;

		strm.zalloc = Z_NULL;
		strm.zfree = Z_NULL;
		strm.opaque = Z_NULL;
		strm.avail_in = 0;
		strm.next_in = Z_NULL;
		// zlib / gzip have a terrible design interface
		// 15 is the window size for memory usage
		// if you negate it, then its headerless and the
		// negative value is the window size
		// if you add 16 then it does gzip decoding
		// with the value minus 16 being window size
		// if you add 32 it does either gzip or zlib by
		// checking headers and the value minus 32 is the
		// window size.
		int param = 15 + 16 + 16;
		if (mode == 1) param = -15;
		if (mode == 2) param = 33;
		if (mode == 3) param = -1;
		ret = inflateInit2(&strm, param);
		if (ret != Z_OK) return "";

		stringstream ss;
		strm.avail_in = data.length();
		strm.next_in =
			reinterpret_cast<uint8_t*>(const_cast<char*>(data.data()));
		do {
			strm.avail_out = CHUNK;
			strm.next_out = out;
			ret = inflate(&strm, Z_FINISH);
			if (ret == Z_STREAM_ERROR) return ss.str();
			if (ret == Z_NEED_DICT) return ss.str();
			have = CHUNK - strm.avail_out;
			if (ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
				ss << string(reinterpret_cast<const char*>(out), have);
				if (leftover) *leftover = strm.avail_in;
				inflateEnd(&strm);
				return ss.str();
			}
			ss << string(reinterpret_cast<const char*>(out), have);
		} while (strm.avail_out == 0);
		if (leftover) *leftover = strm.avail_in;
		inflateEnd(&strm);
	        return ss.str();
	}
};

#endif  // __IB__ZCAT__H__
