// Scintilla source code edit control
/** @file Document.h
 ** Text document that handles notifications, DBCS, styling, words and end of line.
 **/
// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#ifndef DOCUMENT_H
#define DOCUMENT_H

/**
 * A Position is a position within a document between two characters or at the beginning or end.
 * Sometimes used as a character index where it identifies the character after the position.
 */
typedef int Position;
const Position invalidPosition = -1;

/**
 * The range class represents a range of text in a document.
 * The two values are not sorted as one end may be more significant than the other
 * as is the case for the selection where the end position is the position of the caret.
 * If either position is invalidPosition then the range is invalid and most operations will fail.
 */
class Range {
public:
    Position start;
    Position end;

    Range(Position pos=0) :
        start(pos), end(pos) {
    };
    Range(Position start_, Position end_) :
        start(start_), end(end_) {
    };

    bool Valid() const {
        return (start != invalidPosition) && (end != invalidPosition);
    }

    // Is the position within the range?
    bool Contains(Position pos) const {
        if (start < end) {
            return (pos >= start && pos <= end);
        } else {
            return (pos <= start && pos >= end);
        }
    }

    // Is the character after pos within the range?
    bool ContainsCharacter(Position pos) const {
        if (start < end) {
            return (pos >= start && pos < end);
        } else {
            return (pos < start && pos >= end);
        }
    }

    bool Contains(Range other) const {
        return Contains(other.start) && Contains(other.end);
    }

    bool Overlaps(Range other) const {
        return
        Contains(other.start) ||
        Contains(other.end) ||
        other.Contains(start) ||
        other.Contains(end);
    }
};

class DocWatcher;
class DocModification;
class RESearch;

/**
 */
class Document {

public:
    /** Used to pair watcher pointer with user data. */
    class WatcherWithUserData {
    public:
        DocWatcher *watcher;
        void *userData;
        WatcherWithUserData() {
            watcher = 0;
            userData = 0;
        }
    };

    enum charClassification { ccSpace, ccNewLine, ccWord, ccPunctuation };

private:
    int refCount;
    CellBuffer cb;
    CharClassify charClass;
    char stylingMask;
    int endStyled;
    int styleClock;
    int enteredCount;
    int enteredReadOnlyCount;

    WatcherWithUserData *watchers;
    int lenWatchers;

    bool matchesValid;
    RESearch *pre;
    char *substituted;

public:
    int stylingBits;
    int stylingBitsMask;

    int eolMode;
    /// Can also be SC_CP_UTF8 to enable UTF-8 mode
    int dbcsCodePage;
    int tabInChars;
    int indentInChars;
    int actualIndentInChars;
    bool useTabs;
    bool tabIndents;
    bool backspaceUnindents;

    Document();
    virtual ~Document();

    int AddRef();
    int Release();

    int LineFromPosition(int pos);
    int ClampPositionIntoDocument(int pos);
    bool IsCrLf(int pos);
    int LenChar(int pos);
    int MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd=true);

    // Gateways to modifying document
    void ModifiedAt(int pos);
    bool DeleteChars(int pos, int len);
    bool InsertStyledString(int position, char *s, int insertLength);
    int Undo();
    int Redo();
    bool CanUndo() { return cb.CanUndo(); }
    bool CanRedo() { return cb.CanRedo(); }
    void DeleteUndoHistory() { cb.DeleteUndoHistory(); }
    bool SetUndoCollection(bool collectUndo) {
        return cb.SetUndoCollection(collectUndo);
    }
    bool IsCollectingUndo() { return cb.IsCollectingUndo(); }
    void BeginUndoAction() { cb.BeginUndoAction(); }
    void EndUndoAction() { cb.EndUndoAction(); }
    void SetSavePoint();
    bool IsSavePoint() { return cb.IsSavePoint(); }

    int GetLineIndentation(int line);
    void SetLineIndentation(int line, int indent);
    int GetLineIndentPosition(int line);
    int GetColumn(int position);
    int FindColumn(int line, int column);
    void Indent(bool forwards, int lineBottom, int lineTop);
    static char *TransformLineEnds(int *pLenOut, const char *s, size_t len, int eolMode);
    void ConvertLineEnds(int eolModeSet);
    void SetReadOnly(bool set) { cb.SetReadOnly(set); }
    bool IsReadOnly() { return cb.IsReadOnly(); }

    bool InsertChar(int pos, char ch);
    bool InsertString(int position, const char *s);
    bool InsertString(int position, const char *s, size_t insertLength);
    void ChangeChar(int pos, char ch);
    void DelChar(int pos);
    void DelCharBack(int pos);

    char CharAt(int position) { return cb.CharAt(position); }
    void GetCharRange(char *buffer, int position, int lengthRetrieve) {
        cb.GetCharRange(buffer, position, lengthRetrieve);
    }
    char StyleAt(int position) { return cb.StyleAt(position); }
    int GetMark(int line) { return cb.GetMark(line); }
    int AddMark(int line, int markerNum);
    void AddMarkSet(int line, int valueSet);
    void DeleteMark(int line, int markerNum);
    void DeleteMarkFromHandle(int markerHandle);
    void DeleteAllMarks(int markerNum);
    int LineFromHandle(int markerHandle) { return cb.LineFromHandle(markerHandle); }
    int LineStart(int line);
    int LineEnd(int line);
    int LineEndPosition(int position);
    int VCHomePosition(int position);

    int SetLevel(int line, int level);
    int GetLevel(int line) { return cb.GetLevel(line); }
    void ClearLevels() { cb.ClearLevels(); }
    int GetLastChild(int lineParent, int level=-1);
    int GetFoldParent(int line);

    void Indent(bool forwards);
    int ExtendWordSelect(int pos, int delta, bool onlyWordCharacters=false);
    int NextWordStart(int pos, int delta);
    int NextWordEnd(int pos, int delta);
    int Length() { return cb.Length(); }
    void Allocate(int newSize) { cb.Allocate(newSize*2); }
    long FindText(int minPos, int maxPos, const char *s,
        bool caseSensitive, bool word, bool wordStart, bool regExp, bool posix, int *length);
    long FindText(int iMessage, unsigned long wParam, long lParam);
    const char *SubstituteByPosition(const char *text, int *length);
    int LinesTotal();

    void ChangeCase(Range r, bool makeUpperCase);

    void SetDefaultCharClasses(bool includeWordClass);
    void SetCharClasses(const unsigned char *chars, CharClassify::cc newCharClass);
    void SetStylingBits(int bits);
    void StartStyling(int position, char mask);
    bool SetStyleFor(int length, char style);
    bool SetStyles(int length, char *styles);
    int GetEndStyled() { return endStyled; }
    bool EnsureStyledTo(int pos);
    int GetStyleClock() { return styleClock; }
    void IncrementStyleClock();

    int SetLineState(int line, int state) { return cb.SetLineState(line, state); }
    int GetLineState(int line) { return cb.GetLineState(line); }
    int GetMaxLineState() { return cb.GetMaxLineState(); }

    bool AddWatcher(DocWatcher *watcher, void *userData);
    bool RemoveWatcher(DocWatcher *watcher, void *userData);
    const WatcherWithUserData *GetWatchers() const { return watchers; }
    int GetLenWatchers() const { return lenWatchers; }

    bool IsWordPartSeparator(char ch);
    int WordPartLeft(int pos);
    int WordPartRight(int pos);
    int ExtendStyleRange(int pos, int delta, bool singleLine = false);
    bool IsWhiteLine(int line);
    int ParaUp(int pos);
    int ParaDown(int pos);
    int IndentSize() { return actualIndentInChars; }
    int BraceMatch(int position, int maxReStyle);

private:
    void CheckReadOnly();

    CharClassify::cc WordCharClass(unsigned char ch);
    bool IsWordStartAt(int pos);
    bool IsWordEndAt(int pos);
    bool IsWordAt(int start, int end);

    void NotifyModifyAttempt();
    void NotifySavePoint(bool atSavePoint);
    void NotifyModified(DocModification mh);
};

/**
 * To optimise processing of document modifications by DocWatchers, a hint is passed indicating the
 * scope of the change.
 * If the DocWatcher is a document view then this can be used to optimise screen updating.
 */
class DocModification {
public:
    int modificationType;
    int position;
    int length;
    int linesAdded; /**< Negative if lines deleted. */
    const char *text;   /**< Only valid for changes to text, not for changes to style. */
    int line;
    int foldLevelNow;
    int foldLevelPrev;

    DocModification(int modificationType_, int position_=0, int length_=0,
        int linesAdded_=0, const char *text_=0, int line_=0) :
        modificationType(modificationType_),
        position(position_),
        length(length_),
        linesAdded(linesAdded_),
        text(text_),
        line(line_),
        foldLevelNow(0),
        foldLevelPrev(0) {}

    DocModification(int modificationType_, const Action &act, int linesAdded_=0) :
        modificationType(modificationType_),
        position(act.position),
        length(act.lenData),
        linesAdded(linesAdded_),
        text(act.data),
        line(0),
        foldLevelNow(0),
        foldLevelPrev(0) {}
};

/**
 * A class that wants to receive notifications from a Document must be derived from DocWatcher
 * and implement the notification methods. It can then be added to the watcher list with AddWatcher.
 */
class DocWatcher {
public:
    virtual ~DocWatcher() {}

    virtual void NotifyModifyAttempt(Document *doc, void *userData) = 0;
    virtual void NotifySavePoint(Document *doc, void *userData, bool atSavePoint) = 0;
    virtual void NotifyModified(Document *doc, DocModification mh, void *userData) = 0;
    virtual void NotifyDeleted(Document *doc, void *userData) = 0;
    virtual void NotifyStyleNeeded(Document *doc, void *userData, int endPos) = 0;
};

#endif
