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

#include <algorithm>
#include <string>

#include "constants.h"

using namespace std;

/* FormatString extends string to include formatting instructions on it, such as
 * colour, bold, etc */
// TODO: infer more types of data that would benefit from colours
class FormatString : public string {
public:
	/* default constructor */
	FormatString() : string("") {}

	// allow implicit conversion
	FormatString(const string& val) : string("") {
		init(val);
	}

	// destructor
	virtual ~FormatString() {}

	/* store the parameter value as the string, and creates a vector of
	 * format rules of the same length */
	virtual void init(const string& val) {
		assign(val);
		_fmt.resize(val.size());
	}

	/* sets every character of the string to be bolded as highlighting */
	virtual void highlight() {
		for (size_t i = 0; i < length(); ++i) {
			_fmt[i] |= G::BOLD;
		}
	}

	/* replace all occurances of char f with char r. used for making custom
	 * chars into tabs */
	virtual void replace(char f, char r) {
		for (size_t i = 0; i < length(); ++i) {
			if (at(i) == f) {
				(*this)[i] = r;
			}
		}
	}

	/* goes through the string, if it finds colons then put the thing before
	 * it in COLON_COLOUR, when looking at key-value type pairs such as
	 * json or configuration files.
	 * TODO: maybe before = as well. */
	virtual void colour_function() {
		// start at 1 since we colour backwards from a found ':'
		for (size_t i = 1; i < length(); ++i) {
			if (at(i) == ':') {
				// if followed by whitespace do the mark
				if (i + 1 < length() && at(i + 1) == ' ') {
					size_t j = i;
					while (j < length()) {
	                                        if (white_at(j)) break;
						--j;
					}
					if (j >= length()) j = 0;
					mark(G::COLON_COLOUR, j, i + 1 - j);
					break;
                                }
				size_t j;
				// scan ahead for more colons, only do the last
				for (j = i + 1; j < length(); ++j) {
					if (at(j) == ':') goto cont;
				}
				for (j = i; j < length(); --j) {
					if (white_at(j)) break;
				}
				if (j >= length()) j = 0;
				if (j + 1 < i) mark(G::COLON_COLOUR, j, i + 1 - j);
			}
			// for break-continue in nested loop
			cont:;
		}
	}

	/* depending on case sensitivity of keyword in the string,
	 * make relevant findings of it in the line coloured with the code */
	virtual void mark(int code, const string& keyword) {
		string lower = G::to_lower(keyword);
		if (lower != keyword) {
			mark(code, keyword, static_cast<string>(*this));
		} else {
			mark(code, lower,
			     G::to_lower(static_cast<string>(*this)));
		}
	}

	// TODO: avoid highlighting midstream matches for left/right anchors
	// wherever keyword appears, make it in the code colour
	virtual void mark(int code, const string& keyword,
			  const string& line) {
		size_t start_pos = 0;
		while (npos !=
		       (start_pos = line.find(keyword, start_pos))) {
			mark(code, start_pos, keyword.length());
			++start_pos;
		}
	}

	/* returns the format for the position */
	virtual int code(size_t pos) const {
		assert(pos < _fmt.size());
		// high bit signals that format is from the line itself, so that
		// keyword matching gets priority over it
		return static_cast<int>(_fmt.at(pos)) & 127;
	}

	/* used to increase length of string with spaces to size pos and adds
	 * zeros to the format */
	virtual void align(size_t pos) {
		while (pos > _fmt.size()) {
			append(" ");
			_fmt += '\0';
		}
		assert(_fmt.size() == size());
	}

	/* appends suffix and format to the string */
	virtual void add(const string_view& suffix, const string_view& format) {
		assert(suffix.size() == format.size());
		append(suffix);
		for (const auto& x : format) {
			_fmt += x ?
				static_cast<char>(static_cast<uint8_t>(x) | 128)
				: 0;
		}
		assert(_fmt.size() == size());
	}

	/* appends suffix to the string with format in parameter code */
	virtual void add(const string_view& suffix, int code) {
		assert(_fmt.size() == size());

		append(suffix);
		for (size_t i = 0; i < suffix.length(); ++i) {
			_fmt += code ?
			    static_cast<char>(code | 128)
			    : 0;
		}
		assert(_fmt.size() == size());
	}

	/* appends a format string to this one */
	virtual void add(const FormatString& other) {
		append(static_cast<string>(other));
		_fmt += other._fmt;
		assert(_fmt.size() == size());
	}

	/* signals that the cursor is on this line at position in parameter */
	virtual void cursor(size_t pos) {
		assert(pos < _fmt.size());
		assert(_fmt[pos] == 0);
		_fmt[pos] = G::CURSOR_COLOUR;
	}

	virtual void truncate(size_t cols) {
		if (length() <= cols) return;
		size_t start = length() - cols;
		assert(start < length());
		for (size_t i = 0; i < cols; ++i) {
			_fmt[i] = _fmt[i + start];
		}
		init(substr(start));
	}

protected:
	/* returns true if position i is a whitespace character */
	virtual bool white_at(size_t i) const {
		assert(i < length());
		char c = at(i);
		return (c == ' ' || c == '\t' || c == '\n'
		    || c == '\r' || c == '\v');
	}

	/* sets the string from pos to pos+len to have format code if it does
	 * not have a format already. If highlighted then set it to code
	 * highlighted. */
	virtual void mark(int code, size_t pos, size_t len) {
		for (size_t i = pos; i < pos + len; ++i) {
			uint8_t val = static_cast<uint8_t>(_fmt[i]);
			if (val == 0 // blank
			    || val == G::BOLD // bold
			    || val == G::UNDERLINE // underline
			    || val > G::WEAK) { // input colour
				if (val > G::WEAK) val %= G::WEAK;
				val -= (val % G::LOWEST_MODIFIER);
				_fmt[i] = val + code;
			}
		}
	}

	/* same size as string, stores the format */
	string _fmt;
};

#endif  // __FORMAT_STRING__H__
