// Scintilla source code edit control
/** @file SparseVector.h
 ** Hold data sparsely associated with elements in a range.
 **/
// Copyright 2016 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#ifndef SPARSEVECTOR_H
#define SPARSEVECTOR_H

namespace Scintilla::Internal {

// SparseVector is similar to RunStyles but is more efficient for cases where values occur
// for one position instead of over a range of positions.
// There are always elements at the start and end, so the element type should have
// a reasonable empty value that will cause no problems.
// The element type should have a noexcept default constructor as that allows methods to
// be noexcept.
template <typename T>
class SparseVector {
private:
	Partitioning<Sci::Position> starts;
	SplitVector<T> values;
	T empty;	// Return from ValueAt when no element at a position.
	void ClearValue(Sci::Position partition) noexcept {
		values.SetValueAt(partition, T());
	}
public:
	SparseVector() : empty() {
		values.InsertEmpty(0, 2);
	}
	Sci::Position Length() const noexcept {
		return starts.Length();
	}
	Sci::Position Elements() const noexcept {
		return starts.Partitions();
	}
	Sci::Position PositionOfElement(Sci::Position element) const noexcept {
		return starts.PositionFromPartition(element);
	}
	Sci::Position ElementFromPosition(Sci::Position position) const noexcept {
		if (position < Length()) {
			return starts.PartitionFromPosition(position);
		} else {
			return starts.Partitions();
		}
	}
	const T& ValueAt(Sci::Position position) const noexcept {
		assert(position <= Length());
		const Sci::Position partition = ElementFromPosition(position);
		const Sci::Position startPartition = starts.PositionFromPartition(partition);
		if (startPartition == position) {
			return values.ValueAt(partition);
		} else {
			return empty;
		}
	}
	T Extract(Sci::Position position) {
		// Move value currently at position; clear and remove position; return value.
		// Doesn't remove position at start or end.
		assert(position <= Length());
		const Sci::Position partition = ElementFromPosition(position);
		assert(partition >= 0);
		assert(partition <= starts.Partitions());
		assert(starts.PositionFromPartition(partition) == position);
		T value = std::move(values.operator[](partition));
		if ((partition > 0) && (partition < starts.Partitions())) {
			starts.RemovePartition(partition);
			values.Delete(partition);
		}
		Check();
		return value;
	}
	template <typename ParamType>
	void SetValueAt(Sci::Position position, ParamType &&value) {
		assert(position <= Length());
		const Sci::Position partition = ElementFromPosition(position);
		const Sci::Position startPartition = starts.PositionFromPartition(partition);
		if (value == T()) {
			// Setting the empty value is equivalent to deleting the position
			if (position == 0 || position == Length()) {
				ClearValue(partition);
			} else if (position == startPartition) {
				// Currently an element at this position, so remove
				ClearValue(partition);
				starts.RemovePartition(partition);
				values.Delete(partition);
			}
			// Else element remains empty
		} else {
			if (position == startPartition) {
				// Already a value at this position, so replace
				ClearValue(partition);
				values.SetValueAt(partition, std::forward<ParamType>(value));
			} else {
				// Insert a new element
				starts.InsertPartition(partition + 1, position);
				values.Insert(partition + 1, std::forward<ParamType>(value));
			}
		}
	}
	void InsertSpace(Sci::Position position, Sci::Position insertLength) {
		assert(position <= Length());
		const Sci::Position partition = starts.PartitionFromPosition(position);
		const Sci::Position startPartition = starts.PositionFromPartition(partition);
		if (startPartition == position) {
			const bool positionOccupied = values.ValueAt(partition) != T();
			// Inserting at start of run so make previous longer
			if (partition == 0) {
				// Inserting at start of document so ensure start empty
				if (positionOccupied) {
					starts.InsertPartition(1, 0);
					values.InsertEmpty(0, 1);
				}
				starts.InsertText(partition, insertLength);
			} else {
				if (positionOccupied) {
					starts.InsertText(partition - 1, insertLength);
				} else {
					// Insert at end of run so do not extend style
					starts.InsertText(partition, insertLength);
				}
			}
		} else {
			starts.InsertText(partition, insertLength);
		}
	}
	void DeletePosition(Sci::Position position) {
		assert(position < Length());
		Sci::Position partition = starts.PartitionFromPosition(position);
		const Sci::Position startPartition = starts.PositionFromPartition(partition);
		if (startPartition == position) {
			if (partition == 0) {
				ClearValue(0);
				if (starts.PositionFromPartition(1) == 1) {
					// Removing all space of first partition, so remove next partition
					// and move value if not last
					if (Elements() > 1) {
						starts.RemovePartition(partition + 1);
						values.Delete(partition);
					}
				}
			} else if (partition == starts.Partitions()) {
				// This should not be possible
				ClearValue(partition);
				throw std::runtime_error("SparseVector: deleting end partition.");
			} else {
				ClearValue(partition);
				starts.RemovePartition(partition);
				values.Delete(partition);
				// Its the previous partition now that gets smaller
				partition--;
			}
		}
		starts.InsertText(partition, -1);
		Check();
	}
	void DeleteAll() {
		starts = Partitioning<Sci::Position>();
		values = SplitVector<T>();
		values.InsertEmpty(0, 2);
	}
	void DeleteRange(Sci::Position position, Sci::Position deleteLength) {
		// For now, delete elements in range - may want to leave value at start
		// or combine onto position.
		if (position > Length() || (deleteLength == 0)) {
			return;
		}
		const Sci::Position positionEnd = position + deleteLength;
		assert(positionEnd <= Length());
		if (position == 0) {
			// Remove all partitions in range, moving values to start
			while ((Elements() > 1) && (starts.PositionFromPartition(1) <= deleteLength)) {
				starts.RemovePartition(1);
				values.Delete(0);
			}
			starts.InsertText(0, -deleteLength);
			if (Length() == 0) {
				ClearValue(0);
			}
		} else {
			const Sci::Position partition = starts.PartitionFromPosition(position);
			const bool atPartitionStart = position == starts.PositionFromPartition(partition);
			const Sci::Position partitionDelete = partition + (atPartitionStart ? 0 : 1);
			assert(partitionDelete > 0);
			for (;;) {
				const Sci::Position positionAtIndex = starts.PositionFromPartition(partitionDelete);
				assert(position <= positionAtIndex);
				if (positionAtIndex >= positionEnd) {
					break;
				}
				assert(partitionDelete <= Elements());
				starts.RemovePartition(partitionDelete);
				values.Delete(partitionDelete);
			}
			starts.InsertText(partition - (atPartitionStart ? 1 : 0), -deleteLength);
		}
		Check();
	}
	Sci::Position PositionNext(Sci::Position start) const noexcept {
		const Sci::Position element = ElementFromPosition(start);
		if (element < Elements()) {
			return PositionOfElement(element + 1);
		}
		return Length() + 1;	// Out of bounds to terminate
	}
	Sci::Position IndexAfter(Sci::Position position) const noexcept {
		assert(position < Length());
		if (position < 0)
			return 0;
		const Sci::Position partition = starts.PartitionFromPosition(position);
		return partition + 1;
	}
	void Check() const {
#ifdef CHECK_CORRECTNESS
		starts.Check();
		if (starts.Partitions() != values.Length() - 1) {
			throw std::runtime_error("SparseVector: Partitions and values different lengths.");
		}
#endif
	}
};

}

#endif
