// Scintilla source code edit control
/** @file StyleContext.h
 ** Lexer infrastructure.
 **/
// Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org>
// This file is in the public domain.

#ifndef STYLECONTEXT_H
#define STYLECONTEXT_H

namespace Lexilla {

// All languages handled so far can treat all characters >= 0x80 as one class
// which just continues the current token or starts an identifier if in default.
// DBCS treated specially as the second character can be < 0x80 and hence
// syntactically significant. UTF-8 avoids this as all trail bytes are >= 0x80
class StyleContext {
	LexAccessor &styler;
	Scintilla::IDocument * const multiByteAccess;
	const Sci_PositionU lengthDocument;
	const Sci_PositionU endPos;
	const Sci_Position lineDocEnd;

	// Used for optimizing GetRelativeCharacter
	Sci_PositionU posRelative = 0;
	Sci_PositionU currentPosLastRelative;
	Sci_Position offsetRelative = 0;

	void GetNextChar() {
		if (multiByteAccess) {
			chNext = multiByteAccess->GetCharacterAndWidth(currentPos+width, &widthNext);
		} else {
			const unsigned char charNext = styler.SafeGetCharAt(currentPos + width, 0);
			chNext = charNext;
		}
		// End of line determined from line end position, allowing CR, LF,
		// CRLF and Unicode line ends as set by document.
		const Sci_Position currentPosSigned = currentPos;
		if (currentLine < lineDocEnd)
			atLineEnd = currentPosSigned >= (lineStartNext-1);
		else // Last line
			atLineEnd = currentPosSigned >= lineStartNext;
	}

public:
	Sci_PositionU currentPos;
	Sci_Position currentLine;
	Sci_Position lineEnd;
	Sci_Position lineStartNext;
	bool atLineStart;
	bool atLineEnd = false;
	int state;
	int chPrev = 0;
	int ch = 0;
	Sci_Position width = 0;
	int chNext = 0;
	Sci_Position widthNext = 1;

	StyleContext(Sci_PositionU startPos, Sci_PositionU length,
                        int initStyle, LexAccessor &styler_, char chMask = '\377');
	// Deleted so StyleContext objects can not be copied.
	StyleContext(const StyleContext &) = delete;
	StyleContext &operator=(const StyleContext &) = delete;
	void Complete() {
		styler.ColourTo(currentPos - ((currentPos > lengthDocument) ? 2 : 1), state);
		styler.Flush();
	}
	bool More() const noexcept {
		return currentPos < endPos;
	}
	void Forward() {
		if (currentPos < endPos) {
			atLineStart = atLineEnd;
			if (atLineStart) {
				currentLine++;
				lineEnd = styler.LineEnd(currentLine);
				lineStartNext = styler.LineStart(currentLine+1);
			}
			chPrev = ch;
			currentPos += width;
			ch = chNext;
			width = widthNext;
			GetNextChar();
		} else {
			atLineStart = false;
			chPrev = ' ';
			ch = ' ';
			chNext = ' ';
			atLineEnd = true;
		}
	}
	void Forward(Sci_Position nb) {
		for (Sci_Position i = 0; i < nb; i++) {
			Forward();
		}
	}
	void ForwardBytes(Sci_Position nb) {
		const Sci_PositionU forwardPos = currentPos + nb;
		while (forwardPos > currentPos) {
			const Sci_PositionU currentPosStart = currentPos;
			Forward();
			if (currentPos == currentPosStart) {
				// Reached end
				return;
			}
		}
	}
	void ChangeState(int state_) noexcept {
		state = state_;
	}
	void SetState(int state_) {
		styler.ColourTo(currentPos - ((currentPos > lengthDocument) ? 2 : 1), state);
		state = state_;
	}
	void ForwardSetState(int state_) {
		Forward();
		styler.ColourTo(currentPos - ((currentPos > lengthDocument) ? 2 : 1), state);
		state = state_;
	}
	Sci_Position LengthCurrent() const noexcept {
		return currentPos - styler.GetStartSegment();
	}
	int GetRelative(Sci_Position n, char chDefault='\0') {
		const unsigned char chRelative = styler.SafeGetCharAt(currentPos + n, chDefault);
		return chRelative;
	}
	int GetRelativeCharacter(Sci_Position n) {
		if (n == 0)
			return ch;
		if (multiByteAccess) {
			if ((currentPosLastRelative != currentPos) ||
				((n > 0) && ((offsetRelative < 0) || (n < offsetRelative))) ||
				((n < 0) && ((offsetRelative > 0) || (n > offsetRelative)))) {
				posRelative = currentPos;
				offsetRelative = 0;
			}
			const Sci_Position diffRelative = n - offsetRelative;
			const Sci_Position posNew = multiByteAccess->GetRelativePosition(posRelative, diffRelative);
			const int chReturn = multiByteAccess->GetCharacterAndWidth(posNew, nullptr);
			posRelative = posNew;
			currentPosLastRelative = currentPos;
			offsetRelative = n;
			return chReturn;
		} else {
			// fast version for single byte encodings
			const unsigned char chRelative = styler.SafeGetCharAt(currentPos + n, 0);
			return chRelative;
		}
	}
	bool MatchLineEnd() const noexcept {
		const Sci_Position currentPosSigned = currentPos;
		return currentPosSigned == lineEnd;
	}
	bool Match(char ch0) const noexcept {
		const unsigned char uch0 = ch0;
		return ch == uch0;
	}
	bool Match(char ch0, char ch1) const noexcept {
		const unsigned char uch0 = ch0;
		const unsigned char uch1 = ch1;
		return (ch == uch0) && (chNext == uch1);
	}
	bool Match(const char *s) {
		const unsigned char su = *s;
		if (ch != su)
			return false;
		s++;
		if (!*s)
			return true;
		const unsigned char sNext = *s;
		if (chNext != sNext)
			return false;
		s++;
		for (int n=2; *s; n++) {
			if (*s != styler.SafeGetCharAt(currentPos+n, 0))
				return false;
			s++;
		}
		return true;
	}
	// Non-inline
	bool MatchIgnoreCase(const char *s);
	void GetCurrent(char *s, Sci_PositionU len);
	void GetCurrentLowered(char *s, Sci_PositionU len);
	enum class Transform { none, lower };
	void GetCurrentString(std::string &string, Transform transform);
};

}

#endif
