// Scintilla source code edit control
/** @file Partitioning.h
 ** Data structure used to partition an interval. Used for holding line start/end positions.
 **/
// Copyright 1998-2007 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#ifndef PARTITIONING_H
#define PARTITIONING_H

/// A split vector of integers with a method for adding a value to all elements
/// in a range.
/// Used by the Partitioning class.

class SplitVectorWithRangeAdd : public SplitVector<int> {
public:
    SplitVectorWithRangeAdd(int growSize_) {
        SetGrowSize(growSize_);
        ReAllocate(growSize_);
    }
    ~SplitVectorWithRangeAdd() {
    }
    void RangeAddDelta(int start, int end, int delta) {
        // end is 1 past end, so end-start is number of elements to change
        int i = 0;
        int rangeLength = end - start;
        int range1Length = rangeLength;
        int part1Left = part1Length - start;
        if (range1Length > part1Left)
            range1Length = part1Left;
        while (i < range1Length) {
            body[start++] += delta;
            i++;
        }
        start += gapLength;
        while (i < rangeLength) {
            body[start++] += delta;
            i++;
        }
    }
};

/// Divide an interval into multiple partitions.
/// Useful for breaking a document down into sections such as lines.
/// A 0 length interval has a single 0 length partition, numbered 0
/// If interval not 0 length then each partition non-zero length
/// When needed, positions after the interval are considered part of the last partition
/// but the end of the last partition can be found with PositionFromPartition(last+1).

class Partitioning {
private:
    // To avoid calculating all the partition positions whenever any text is inserted
    // there may be a step somewhere in the list.
    int stepPartition;
    int stepLength;
    SplitVectorWithRangeAdd *body;

    // Move step forward
    void ApplyStep(int partitionUpTo) {
        if (stepLength != 0) {
            body->RangeAddDelta(stepPartition+1, partitionUpTo + 1, stepLength);
        }
        stepPartition = partitionUpTo;
        if (stepPartition >= body->Length()-1) {
            stepPartition = body->Length()-1;
            stepLength = 0;
        }
    }

    // Move step backward
    void BackStep(int partitionDownTo) {
        if (stepLength != 0) {
            body->RangeAddDelta(partitionDownTo+1, stepPartition+1, -stepLength);
        }
        stepPartition = partitionDownTo;
    }

    void Allocate(int growSize) {
        body = new SplitVectorWithRangeAdd(growSize);
        stepPartition = 0;
        stepLength = 0;
        body->Insert(0, 0); // This value stays 0 for ever
        body->Insert(1, 0); // This is the end of the first partition and will be the start of the second
    }

public:
    Partitioning(int growSize) {
        Allocate(growSize);
    }

    ~Partitioning() {
        delete body;
        body = 0;
    }

    int Partitions() const {
        return body->Length()-1;
    }

    void InsertPartition(int partition, int pos) {
        if (stepPartition < partition) {
            ApplyStep(partition);
        }
        body->Insert(partition, pos);
        stepPartition++;
    }

    void SetPartitionStartPosition(int partition, int pos) {
        ApplyStep(partition+1);
        if ((partition < 0) || (partition > body->Length())) {
            return;
        }
        body->SetValueAt(partition, pos);
    }

    void InsertText(int partitionInsert, int delta) {
        // Point all the partitions after the insertion point further along in the buffer
        if (stepLength != 0) {
            if (partitionInsert >= stepPartition) {
                // Fill in up to the new insertion point
                ApplyStep(partitionInsert);
                stepLength += delta;
            } else if (partitionInsert >= (stepPartition - body->Length() / 10)) {
                // Close to step but before so move step back
                BackStep(partitionInsert);
                stepLength += delta;
            } else {
                ApplyStep(body->Length()-1);
                stepPartition = partitionInsert;
                stepLength = delta;
            }
        } else {
            stepPartition = partitionInsert;
            stepLength = delta;
        }
    }

    void RemovePartition(int partition) {
        if (partition > stepPartition) {
            ApplyStep(partition);
            stepPartition--;
        } else {
            stepPartition--;
        }
        body->Delete(partition);
    }

    int PositionFromPartition(int partition) const {
        PLATFORM_ASSERT(partition >= 0);
        PLATFORM_ASSERT(partition < body->Length());
        if ((partition < 0) || (partition >= body->Length())) {
            return 0;
        }
        int pos = body->ValueAt(partition);
        if (partition > stepPartition)
            pos += stepLength;
        return pos;
    }

    /// Return value in range [0 .. Partitions() - 1] even for arguments outside interval
    int PartitionFromPosition(int pos) const {
        if (body->Length() <= 1)
            return 0;
        if (pos >= (PositionFromPartition(body->Length()-1)))
            return body->Length() - 1 - 1;
        int lower = 0;
        int upper = body->Length()-1;
        do {
            int middle = (upper + lower + 1) / 2;   // Round high
            int posMiddle = body->ValueAt(middle);
            if (middle > stepPartition)
                posMiddle += stepLength;
            if (pos < posMiddle) {
                upper = middle - 1;
            } else {
                lower = middle;
            }
        } while (lower < upper);
        return lower;
    }

    void DeleteAll() {
        int growSize = body->GetGrowSize();
        delete body;
        Allocate(growSize);
    }
};

#endif
