// Scintilla source code edit control
/** @file PositionCache.h
 ** Classes for caching layout information.
 **/
// Copyright 1998-2007 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#ifndef POSITIONCACHE_H
#define POSITIONCACHE_H

#ifdef SCI_NAMESPACE
namespace Scintilla {
#endif

static inline bool IsEOLChar(char ch) {
	return (ch == '\r') || (ch == '\n');
}

class SelectionPosition {
	int position;
	int virtualSpace;
public:
	explicit SelectionPosition(int position_=INVALID_POSITION, int virtualSpace_=0) : position(position_), virtualSpace(virtualSpace_) {
		PLATFORM_ASSERT(virtualSpace < 800000);
		if (virtualSpace < 0)
			virtualSpace = 0;
	}
	void Reset() {
		position = 0;
		virtualSpace = 0;
	}
	void MoveForInsertDelete(bool insertion, int startChange, int length) {
		if (insertion) {
			if (position > startChange) {
				position += length;
			}
		} else {
			if (position > startChange) {
				int endDeletion = startChange + length;
				if (position > endDeletion) {
					position -= length;
				} else {
					position = startChange;
				}
			}
		}
	}
	bool operator ==(const SelectionPosition &other) const {
		return position == other.position && virtualSpace == other.virtualSpace;
	}
	bool operator <(const SelectionPosition &other) const {
		if (position == other.position)
			return virtualSpace < other.virtualSpace;
		else
			return position < other.position;
	}
	bool operator >(const SelectionPosition &other) const {
		if (position == other.position)
			return virtualSpace > other.virtualSpace;
		else
			return position > other.position;
	}
	bool operator <=(const SelectionPosition &other) const {
		if (position == other.position && virtualSpace == other.virtualSpace)
			return true;
		else
			return other > *this;
	}
	bool operator >=(const SelectionPosition &other) const {
		if (position == other.position && virtualSpace == other.virtualSpace)
			return true;
		else
			return *this > other;
	}
	int Position() const {
		return position;
	}
	void SetPosition(int position_) {
		position = position_;
		virtualSpace = 0;
	}
	int VirtualSpace() const {
		return virtualSpace;
	}
	void SetVirtualSpace(int virtualSpace_) {
		PLATFORM_ASSERT(virtualSpace_ < 800000);
		if (virtualSpace_ >= 0)
			virtualSpace = virtualSpace_;
	}
};

struct SelectionRange {
	SelectionPosition caret;
	SelectionPosition anchor;

	SelectionRange() {
	}
	SelectionRange(SelectionPosition single) : caret(single), anchor(single) {
	}
	SelectionRange(SelectionPosition caret_, SelectionPosition anchor_) : caret(caret_), anchor(anchor_) {
	}
	SelectionRange(int caret_, int anchor_) : caret(caret_), anchor(anchor_) {
	}
	bool Empty() const {
		return anchor == caret;
	}
	int Length() {
		if (anchor > caret) {
			return anchor.Position() - caret.Position();
		} else {
			return caret.Position() - anchor.Position();
		}
	}
	bool operator ==(const SelectionRange &other) const {
		return caret == other.caret && anchor == other.anchor;
	}
	void Reset() {
		anchor.Reset();
		caret.Reset();
	}
	void ClearVirtualSpace() {
		anchor.SetVirtualSpace(0);
		caret.SetVirtualSpace(0);
	}
	bool Contains(int pos) const {
		if (anchor > caret)
			return (pos >= caret.Position()) && (pos <= anchor.Position());
		else
			return (pos >= anchor.Position()) && (pos <= caret.Position());
	}
	bool Contains(SelectionPosition sp) const {
		if (anchor > caret)
			return (sp >= caret) && (sp <= anchor);
		else
			return (sp >= anchor) && (sp <= caret);
	}
	bool ContainsCharacter(int posCharacter) const {
		if (anchor > caret)
			return (posCharacter >= caret.Position()) && (posCharacter < anchor.Position());
		else
			return (posCharacter >= anchor.Position()) && (posCharacter < caret.Position());
	}
	bool Intersect(int start, int end, SelectionPosition &selStart, SelectionPosition &selEnd) const {
		SelectionPosition spEnd(end, 100000);	// Large amount of virtual space
		SelectionPosition first;
		SelectionPosition last;
		if (anchor > caret) {
			first = caret;
			last = anchor;
		} else {
			first = anchor;
			last = caret;
		}
		if ((first < spEnd) && (last.Position() > start)) {
			if (start > first.Position()) 
				selStart = SelectionPosition(start);
			else 
				selStart = first;
			if (spEnd < last) 
				selEnd = SelectionPosition(end);
			else 
				selEnd = last;
			return true;
		} else {
			return false;
		}
	}
	SelectionPosition Start() const {
		return (anchor < caret) ? anchor : caret;
	}
	SelectionPosition End() const {
		return (anchor < caret) ? caret : anchor;
	}
	bool Trim(SelectionPosition startPos, SelectionPosition endPos) {
		SelectionPosition startRange = startPos;
		SelectionPosition endRange = endPos;
		if (startPos > endPos) {
			startRange = endPos;
			endRange = startPos;
		}
		SelectionPosition start = Start();
		SelectionPosition end = End();
		PLATFORM_ASSERT(start <= end);
		PLATFORM_ASSERT(startRange <= endRange);
		if ((startRange <= end) && (endRange >= start)) {
			if ((start > startRange) && (end < endRange)) {
				// Completely covered by range -> empty at start
				end = start;
			} else if ((start < startRange) && (end > endRange)) {
				// Completely covers range -> empty at start
				end = start;
			} else if (start <= startRange) {
				// Trim end
				end = startRange;
			} else { // 
				PLATFORM_ASSERT(end >= endRange);
				// Trim start
				start = endRange;
			}
			if (anchor > caret) {
				caret = start;
				anchor = end;
			} else {
				anchor = start;
				caret = end;
			}
			return Empty();
		} else {
			return false;
		}
	}
	// If range is all virtual collapse to start of virtual space
	void MinimizeVirtualSpace() {
		if (caret.Position() == anchor.Position()) {
			int virtualSpace = caret.VirtualSpace();
			if (virtualSpace > anchor.VirtualSpace())
				virtualSpace = anchor.VirtualSpace();
			caret.SetVirtualSpace(virtualSpace);
			anchor.SetVirtualSpace(virtualSpace);
		}
	}
};

class Selection {
	SelectionRange *ranges;
	SelectionRange rangeRectangular;
	size_t allocated;
	size_t nRanges;
	size_t mainRange;
	bool moveExtends;
	void Allocate() {
		if (nRanges >= allocated) {
			size_t allocateNew = (allocated + 1) * 2;
			SelectionRange *rangesNew = new SelectionRange[allocateNew];
			for (size_t r=0; r<Count(); r++)
				rangesNew[r] = ranges[r];
			delete []ranges;
			ranges = rangesNew;
			allocated = allocateNew;
		}
	}
public:
	enum selTypes { noSel, selStream, selRectangle, selLines, selThin };
	selTypes selType;

	Selection() : ranges(0), allocated(0), nRanges(0), mainRange(0), moveExtends(false), selType(selStream) {
		AddSelection(SelectionPosition(0));
	}
	~Selection() {
		delete []ranges;
	}
	bool IsRectangular() const {
		return (selType == selRectangle) || (selType == selThin);
	}
	int MainCaret() const {
		return ranges[mainRange].caret.Position();
	}
	int MainAnchor() const {
		return ranges[mainRange].anchor.Position();
	}
	SelectionRange &Rectangular() {
		return rangeRectangular;
	}
	size_t Count() const {
		return nRanges;
	}
	size_t Main() const {
		return mainRange;
	}
	void SetMain(size_t r) {
		PLATFORM_ASSERT(r < nRanges);
		mainRange = r;
	}
	SelectionRange &Range(size_t r) {
		return ranges[r];
	}
	SelectionRange &RangeMain() {
		return ranges[mainRange];
	}
	void ClearVirtualSpace(size_t r) {
		ranges[r].ClearVirtualSpace();
	}
	bool MoveExtends() const {
		return moveExtends;
	}
	void SetMoveExtends(bool moveExtends_) {
		moveExtends = moveExtends_;
	}
	bool Empty() {
		for (size_t i=0; i<nRanges; i++) {
			if (!ranges[i].Empty())
				return false;
		}
		return true;
	}
	SelectionPosition Last() {
		SelectionPosition lastPosition;
		for (size_t i=0; i<nRanges; i++) {
			if (lastPosition < ranges[i].caret)
				lastPosition = ranges[i].caret;
			if (lastPosition < ranges[i].anchor)
				lastPosition = ranges[i].anchor;
		}
		return lastPosition;
	}
	int Length() {
		int len = 0;
		for (size_t i=0; i<nRanges; i++) {
			len += ranges[i].Length();
		}
		return len;
	}
	void MovePositions(bool insertion, int startChange, int length) {
		for (size_t i=0; i<nRanges; i++) {
			ranges[i].caret.MoveForInsertDelete(insertion, startChange, length);
			ranges[i].anchor.MoveForInsertDelete(insertion, startChange, length);
		}
	}
	void TrimSelection(SelectionPosition startPos, SelectionPosition endPos) {
		for (size_t i=0; i<nRanges;) {
			if ((i != mainRange) && (ranges[i].Trim(startPos, endPos))) {
				// Trimmed to empty so remove
				for (size_t j=i;j<nRanges-1;j++) {
					ranges[j] = ranges[j+1];
					if (j == mainRange-1)
						mainRange--;
				}
				nRanges--;
			} else {
				i++;
			}
		}
	}
	void AddSelection(SelectionPosition spPos) {
		Allocate();
		TrimSelection(spPos, spPos);
		ranges[nRanges] = SelectionRange(spPos);
		mainRange = nRanges;
		nRanges++;
	}
	void AddSelection(SelectionPosition spStartPos, SelectionPosition spEndPos, bool anchorLeft) {
		Allocate();
		TrimSelection(spStartPos, spEndPos);
		ranges[nRanges].caret = anchorLeft ? spEndPos : spStartPos;
		ranges[nRanges].anchor = anchorLeft ? spStartPos : spEndPos;
		mainRange = nRanges;
		nRanges++;
	}
	bool CharacterInSelection(int posCharacter) const {
		for (size_t i=0; i<nRanges; i++) {
			if (ranges[i].ContainsCharacter(posCharacter))
				return true;
		}
		return false;
	}
	bool InSelectionForEOL(int pos) {
		for (size_t i=0; i<nRanges; i++) {
			if (!ranges[i].Empty() && (pos > ranges[i].Start().Position()) && (pos <= ranges[i].End().Position()))
				return true;
		}
		return false;
	}
	int VirtualSpaceFor(int pos) const {
		int virtualSpace = 0;
		for (size_t i=0; i<nRanges; i++) {
			if ((ranges[i].caret.Position() == pos) && (virtualSpace < ranges[i].caret.VirtualSpace()))
				virtualSpace = ranges[i].caret.VirtualSpace();
			if ((ranges[i].anchor.Position() == pos) && (virtualSpace < ranges[i].anchor.VirtualSpace()))
				virtualSpace = ranges[i].anchor.VirtualSpace();
		}
		return virtualSpace;
	}
	void Clear() {
		nRanges = 1;
		mainRange = 0;
		selType = selStream;
		moveExtends = false;
		ranges[mainRange].Reset();
		rangeRectangular.Reset();
	}
	void EmptyRanges() {
		nRanges = 0;
		mainRange = 0;
	}
};

/**
 */
class LineLayout {
private:
	friend class LineLayoutCache;
	int *lineStarts;
	int lenLineStarts;
	/// Drawing is only performed for @a maxLineLength characters on each line.
	int lineNumber;
	bool inCache;
public:
	enum { wrapWidthInfinite = 0x7ffffff };
	int maxLineLength;
	int numCharsInLine;
	int numCharsBeforeEOL;
	enum validLevel { llInvalid, llCheckTextAndStyle, llPositions, llLines } validity;
	int xHighlightGuide;
	bool highlightColumn;
	Selection *psel;
	bool containsCaret;
	int edgeColumn;
	char *chars;
	unsigned char *styles;
	int styleBitsSet;
	char *indicators;
	int *positions;
	char bracePreviousStyles[2];

	// Hotspot support
	int hsStart;
	int hsEnd;

	// Wrapped line support
	int widthLine;
	int lines;
	int wrapIndent; // In pixels

	LineLayout(int maxLineLength_);
	virtual ~LineLayout();
	void Resize(int maxLineLength_);
	void Free();
	void Invalidate(validLevel validity_);
	int LineStart(int line) const;
	int LineLastVisible(int line) const;
	bool InLine(int offset, int line) const;
	void SetLineStart(int line, int start);
	void SetBracesHighlight(Range rangeLine, Position braces[],
		char bracesMatchStyle, int xHighlight);
	void RestoreBracesHighlight(Range rangeLine, Position braces[]);
	int FindBefore(int x, int lower, int upper) const;
};

/**
 */
class LineLayoutCache {
	int level;
	int length;
	int size;
	LineLayout **cache;
	bool allInvalidated;
	int styleClock;
	int useCount;
	void Allocate(int length_);
	void AllocateForLevel(int linesOnScreen, int linesInDoc);
public:
	LineLayoutCache();
	virtual ~LineLayoutCache();
	void Deallocate();
	enum {
		llcNone=SC_CACHE_NONE,
		llcCaret=SC_CACHE_CARET,
		llcPage=SC_CACHE_PAGE,
		llcDocument=SC_CACHE_DOCUMENT
	};
	void Invalidate(LineLayout::validLevel validity_);
	void SetLevel(int level_);
	int GetLevel() { return level; }
	LineLayout *Retrieve(int lineNumber, int lineCaret, int maxChars, int styleClock_,
		int linesOnScreen, int linesInDoc);
	void Dispose(LineLayout *ll);
};

class PositionCacheEntry {
	unsigned int styleNumber:8;
	unsigned int len:8;
	unsigned int clock:16;
	short *positions;
public:
	PositionCacheEntry();
	~PositionCacheEntry();
	void Set(unsigned int styleNumber_, const char *s_, unsigned int len_, int *positions_, unsigned int clock);
	void Clear();
	bool Retrieve(unsigned int styleNumber_, const char *s_, unsigned int len_, int *positions_) const;
	static int Hash(unsigned int styleNumber, const char *s, unsigned int len);
	bool NewerThan(const PositionCacheEntry &other);
	void ResetClock();
};

// Class to break a line of text into shorter runs at sensible places.
class BreakFinder {
	// If a whole run is longer than lengthStartSubdivision then subdivide
	// into smaller runs at spaces or punctuation.
	enum { lengthStartSubdivision = 300 };
	// Try to make each subdivided run lengthEachSubdivision or shorter.
	enum { lengthEachSubdivision = 100 };
	LineLayout *ll;
	int lineStart;
	int lineEnd;
	int posLineStart;
	bool utf8;
	int nextBreak;
	int *selAndEdge;
	unsigned int saeSize;
	unsigned int saeLen;
	unsigned int saeCurrentPos;
	int saeNext;
	int subBreak;
	void Insert(int val);
public:
	BreakFinder(LineLayout *ll_, int lineStart_, int lineEnd_, int posLineStart_, bool utf8_, int xStart);
	~BreakFinder();
	int First();
	int Next();
};

class PositionCache {
	PositionCacheEntry *pces;
	size_t size;
	unsigned int clock;
	bool allClear;
public:
	PositionCache();
	~PositionCache();
	void Clear();
	void SetSize(size_t size_);
	int GetSize() { return size; }
	void MeasureWidths(Surface *surface, ViewStyle &vstyle, unsigned int styleNumber,
		const char *s, unsigned int len, int *positions);
};

inline bool IsSpaceOrTab(int ch) {
	return ch == ' ' || ch == '\t';
}

#ifdef SCI_NAMESPACE
}
#endif

#endif
