/*
 * 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 __FD_LINE_PROVIDER__H__
#define __FD_LINE_PROVIDER__H__

#include <sys/time.h>

#include <cassert>
#include <fcntl.h>
#include <sstream>
#include <string>

#include "base_line_provider.h"
#include "config.h"
#include "i_log_lines.h"

using namespace std;

/* Wraps a file descriptor. Continually reads from the fd pushing new lines to
 * the log lines. Uses select to avoid blocking and monitors the exit condition
 * to quit.
 */
class FDLineProvider : public BaseLineProvider {
public:
	FDLineProvider(ILogLines* ll, int fd)
		: BaseLineProvider(ll), _fd(fd), _len(1<<12) {}

	virtual ~FDLineProvider() {
		close(_fd);
	}

protected:
	// reads from the fd in a non blocking way. if there is data available,
	// separate it into lines and add those lines to the loglines.
	virtual void loader() override {
		_buf.reset(new char[_len + 1]);
		char* buf = _buf.get();
		stringstream remain_ss;
		bool first = true;
		bool remain = false;
		fcntl(_fd, F_SETFL, O_NONBLOCK);
		bool ret = true;
		fd_set fds;
		struct timeval tv;
		while (!exit()) {
			FD_ZERO(&fds);
			FD_SET(_fd, &fds);
			tv.tv_sec = 0;
			tv.tv_usec = 100000;
			int ready = select(_fd + 1, &fds, nullptr, nullptr, &tv);
			if (exit()) return;
			if (ready != 1) continue;
			ssize_t r = ::read(_fd, buf, _len);
			if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
				first = false;
				continue;
			} else if (r <= 0) {
				if (remain) ret = queue_line(remain_ss.str());
				if (exit() || !ret) return;
				eof();
				break;
			}
			assert(r > 0);
			size_t data_available = static_cast<size_t>(r);

			ssize_t start = 0;
			for (size_t i = 0; i < data_available; ++i) {
				if (buf[i] == '\n') {
					buf[i] = '\0';
					if (!first) unset_eof();
					if (start == 0 && remain) {
						remain_ss << buf;
						ret = queue_line(remain_ss.str());
						if (!ret) return;
						remain_ss.str("");
						remain = false;
					} else {
						ret = queue_line(buf + start);
						if (!ret) return;
					}
					start = i + 1;
				}
			}
			flush_lines();
			if (start < r) {
				buf[r] = '\0';
				remain_ss << buf + start;
				remain = true;
			}
		}
		flush_lines();
	}

	// the file decriptor we are serving
	int _fd;

	// length of the buffer size, e.g., 4 KiB
	size_t _len;

	// the reading buffer
	unique_ptr<char[]> _buf;
};

#endif // __FD_LINE_PROVIDER__H__
