{ *************************************************************************** }
{                                                                             }
{ EControl Common Library                                                     }
{                                                                             }
{ Copyright (c) 2004 - 2006 EControl Ltd., Zaharov Michael                    }
{     www.econtrol.ru                                                         }
{     support@econtrol.ru                                                     }
{                                                                             }
{ *************************************************************************** }
{$I ecSyntEdit.INC}

unit ecLists;

interface

uses Classes;

type
  TSortedItem = class
  protected
    function GetKey: integer; virtual; abstract;
  end;

  TSortedList = class
  private
    FList: TList;
    function GetCount: integer;
    function GetItem(Index: integer): TSortedItem;
  public
    constructor Create(OwnObjects: Boolean);
    destructor Destroy; override;

    function Add(Item: TSortedItem): integer;
    procedure Delete(Index: integer);
    procedure Remove(Item: TSortedItem);
    procedure Clear;
    function PriorAt(Pos: integer): integer;
    function GetAt(Pos: integer): TSortedItem;
    function GetIndexAt(Pos: integer): integer;

    property Items[Index: integer]: TSortedItem read GetItem; default;
    property Count: integer read GetCount;
  end;

  TRange = class(TSortedItem)
  private
    function GetLength: integer;
  {$IFDEF EC_DOTNET}
  public
  {$ELSE}
  protected
  {$ENDIF}
    FStartPos: integer;
    FEndPos: integer;
  protected
    function GetKey: integer; override;
  public
    constructor Create(AStartPos, AEndPos: integer);
    property StartPos: integer read FStartPos;
    property EndPos: integer read FEndPos;
    property Size: integer read GetLength;
  end;

  // Array of sorted ranges
  TRangeList = class
  private
    FList: TList;
    FUnionSiblings: Boolean;
    FPrevIdx: integer;
    function GetCount: integer;
    function GetItems(Index: integer): TRange;
  protected
    // Union ranges with the [Index] and [Index + 1]
    // returns new range index (or union result)
    function UnionRanges(Index: integer): integer; virtual;
    function IsGreater(I1, I2: integer): Boolean;
    // Try to add range if there are no intersections
    function TryAdd(Range: TRange): Boolean;
  public
    constructor Create(UnionSiblings: Boolean = True);
    destructor Destroy; override;
    function Add(Range: TRange): integer; virtual;
    procedure Delete(Index: integer);
    procedure Clear; virtual;
    function ClearFromPos(APos: integer; CopyTo: TRangeList = nil): integer; virtual;
    // Deletes ranges that intersect the bounds, returns number of deleted items
    function DeleteIntersected(AStart, AEnd: integer): integer;

    // Content has been changed, updates ranges upper Pos
    // Removes affected ranges
    function ContentChanged(Pos, Count: integer): Boolean;
    // Exactly at position
    function RangeAt(APos: integer): integer;
    // At position or next
    function NextAt(APos: integer): integer;
    // At position or prior
    function PriorAt(APos: integer): integer;

    property Count: integer read GetCount;
    property Items[Index: integer]: TRange read GetItems; default;
  end;

  // collection of overlapped ranges
  // all ranges are sorted for quick search
  TRangeCollection = class
  private
    FRangeArrays: TList;
    function GetLevelCount: integer;
    function GetLevels(Index: integer): TRangeList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(Range: TRange);
    procedure Clear;
    procedure ClearFromPos(APos: integer);
    function GetRangesAtPos(List: TList; Pos: integer): integer;
    function GetRangesAtRange(List: TList; StartPos, EndPos: integer): integer;
    property LevelCount: integer read GetLevelCount;
    property Levels[Index: integer]: TRangeList read GetLevels; default;  
  end;

// List[Index] > Key  =>  Result > 0
// List[Index] = Key  =>  Result = 0
// List[Index] < Key  =>  Result < 0
TCompareProc = function(const List: TList; Index: integer; Key: TObject): integer;
// Search in sorted list
function QuickSearch(const List: TList; CompProc: TCompareProc;
                     Key: TObject; var Index: integer): Boolean;

implementation

uses Contnrs;

function QuickSearch(const List: TList; CompProc: TCompareProc;
                     Key: TObject; var Index: integer): Boolean;
var
  L, H, I, C: Integer;
begin
  Result := False;
  if List.Count = 0 then
   begin
    Index := -1;
    Exit;
   end;

  L := 0;
  H := List.Count - 1;
  while L <= H do
  begin
    I := (L + H) shr 1;
    C := CompProc(List, I, Key);
    if C < 0 then L := I + 1 else
    begin
      if C = 0 then
      begin
        Result := True;
        Index := I;
        Exit;
      end;
      H := I - 1;
    end;
  end;
  Index := L;
  if Index >= List.Count then Index := List.Count - 1;
  if Index >= 0 then
   if CompProc(List, Index, Key) > 0 then
    dec(Index);
end;

{ TRange }

constructor TRange.Create(AStartPos, AEndPos: integer);
begin
  inherited Create;
//  if AStartPos <= AEndPos then
//   begin
     FStartPos := AStartPos;
     FEndPos := AEndPos;
//   end else
//   begin
//     FStartPos := AEndPos;
//     FEndPos := AStartPos;
//   end;
end;

function TRange.GetKey: integer;
begin
  Result := FStartPos;
end;

function TRange.GetLength: integer;
begin
  Result := FEndPos - FStartPos;
end;

{ TRangeList }

constructor TRangeList.Create(UnionSiblings: Boolean);
begin
  inherited Create;
  FList := TObjectList.Create;
  FUnionSiblings := UnionSiblings;
end;

destructor TRangeList.Destroy;
begin
  FList.Free;
  inherited;
end;

procedure TRangeList.Clear;
begin
  FList.Clear;
end;

function TRangeList.UnionRanges(Index: integer): integer;
begin
  // Default action - union of ranges
  if Items[Index].FEndPos < Items[Index + 1].FEndPos then
    Items[Index].FEndPos := Items[Index + 1].FEndPos;
  FList.Delete(Index + 1);
  Result := Index;
end;

function TRangeList.IsGreater(I1, I2: integer): Boolean;
begin
  if FUnionSiblings then
    Result := I1 >= I2
  else
    Result := I1 > I2;
end;

function TRangeList.TryAdd(Range: TRange): Boolean;
var idx: integer;
begin
  Result := True;
  if (Count = 0) or (Items[Count - 1].EndPos <= Range.StartPos) then
    FList.Add(Range)
  else
  if Items[Count - 1].StartPos <= Range.StartPos then
   Result := False
  else
   begin
     idx := NextAt(Range.StartPos);
     if idx = -1 then FList.Add(Range) else
     if (Items[idx].StartPos >= Range.EndPos) and
        ((idx = 0) or (Items[idx - 1].EndPos <= Range.StartPos)) then
        FList.Insert(idx, Range)
     else
       Result := False;
   end;
end;

function TRangeList.Add(Range: TRange): integer;
var idx, k: integer;
begin
  // Stream adding
  if (Count = 0) or (Items[Count - 1].EndPos < Range.StartPos) then
   begin
    Result := Count;
    FList.Add(Range);
    Exit;
   end;

  idx := PriorAt(Range.StartPos);
  if idx = Count - 1 then FList.Add(Range)
   else FList.Insert(idx + 1, Range);
  // Lower range check
  if (idx <> -1) and IsGreater(Items[idx].EndPos, Range.StartPos) then
    idx := UnionRanges(idx)
  else
    idx := idx + 1;
  k := idx + 1;
  while (k < Count) and IsGreater(Items[idx].EndPos, Items[k].StartPos) do
   begin
    idx := UnionRanges(idx);
    k := idx + 1;
   end;

  Result := idx;
end;

procedure TRangeList.Delete(Index: integer);
begin
  FList.Delete(Index);
end;

function TRangeList.GetCount: integer;
begin
  Result := FList.Count;
end;

function TRangeList.GetItems(Index: integer): TRange;
begin
  Result := TRange(FList[Index]);
end;

function TRangeList.RangeAt(APos: integer): integer;
begin
  Result := PriorAt(APos);
  if (Result <> -1) and (Items[Result].EndPos <= APos) then
    Result := -1;
end;

function TRangeList.NextAt(APos: integer): integer;
begin
  Result := PriorAt(APos);
  if Result = -1 then
   begin
     if Count > 0 then Result := 0
   end else
   if Items[Result].EndPos <= APos then
    if Result < Count - 1 then Inc(Result)
     else Result := -1;
end;

function RangeCmp(const List: TList; Index: integer; Key: TObject): integer;
begin
 with TRange(List[Index]) do
  if FStartPos > integer(Key) then Result := 1 else
   if (FStartPos <= integer(Key)) and (FEndPos > integer(Key)) then Result:= 0
     else Result := -1;
end;

function TRangeList.PriorAt(APos: integer): integer;
begin
  if (FPrevIdx >= 0) and (FPrevIdx < FList.Count - 1) then
   begin
     if TRange(FList[FPrevIdx]).StartPos <= APos then
      if (FPrevIdx >= FList.Count - 1) or
         (TRange(FList[FPrevIdx + 1]).StartPos > APos) then
       begin
        Result := FPrevIdx;
        Exit;
       end else
      if (FPrevIdx >= FList.Count - 2) or
         (TRange(FList[FPrevIdx + 2]).StartPos > APos) then
       begin
        Result := FPrevIdx + 1;
        Exit;
       end;
   end;
  QuickSearch(FList, RangeCmp, TObject(APos), Result);
  FPrevIdx := Result;
end;

function TRangeList.ContentChanged(Pos, Count: integer): Boolean;
var idx: integer;
begin
  idx := PriorAt(Pos);
  if (idx <> -1) and (Items[idx].EndPos >= Pos) then Delete(idx)
   else
    begin
     Inc(idx);
     if idx >= FList.Count then // No change
      begin
       Result := False;
       Exit;
      end;
    end;

  if Count < 0 then
   while (idx < FList.Count) and (Items[idx].StartPos <= Pos - Count) do
    Delete(idx);

  while idx < FList.Count do
    begin
      Inc(Items[idx].FStartPos, Count);
      Inc(Items[idx].FEndPos, Count);
      Inc(idx);
    end;
  Result := True;
end;

function TRangeList.ClearFromPos(APos: integer; CopyTo: TRangeList): integer;
var idx, i: integer;
begin
  Result := APos;
  idx := NextAt(APos);
  if idx <> -1 then
   begin
     if Items[idx].StartPos < APos then
       Result := Items[idx].StartPos;
     if CopyTo <> nil then
      begin
        CopyTo.Clear;
        CopyTo.FList.Capacity := Count - idx;
        for i := idx to Count - 1 do
         begin
           CopyTo.FList.Add(FList[i]);
           FList.List[i] := nil;
         end;
//        N := Count - idx;
//        CopyTo.FList.Count := N;
//        Move(FList.List[idx], CopyTo.FList.List[0], N * sizeof(pointer));
//        for i := Count - 1 downto idx do
//          FList.List[idx] := nil;
      end;
     for i := Count - 1 downto idx do
       Delete(i);
   end;
end;

function TRangeList.DeleteIntersected(AStart, AEnd: integer): integer;
var idx: integer;
begin
  idx := NextAt(AStart);
  if idx = -1 then idx := Count - 1 else
   if Items[idx].StartPos >= AEnd then Dec(idx);
  Result := 0;
  while (idx >= 0) and (idx < Count) and (Items[idx].EndPos > Astart) do
   begin
    Inc(Result);
    Delete(idx);
   end;
end;

{ TRangeCollection }

constructor TRangeCollection.Create;
begin
  inherited;
  FRangeArrays := TObjectList.Create;
end;

destructor TRangeCollection.Destroy;
begin
  Clear;
  FRangeArrays.Free
end;

procedure TRangeCollection.Clear;
begin
  FRangeArrays.Clear;
end;

procedure TRangeCollection.Add(Range: TRange);
var i: integer;
    R: TRangeList;
begin
  for i := 0 to FRangeArrays.Count - 1 do
    if TRangeList(FRangeArrays[i]).TryAdd(Range) then Exit;
  R := TRangeList.Create(False);
  FRangeArrays.Add(R);
  R.Add(Range);
end;

procedure TRangeCollection.ClearFromPos(APos: integer);
var i: integer;
    R: TRangeList;
begin
  for i := FRangeArrays.Count - 1 downto 0 do
   begin
     R := TRangeList(FRangeArrays[i]);
     R.ClearFromPos(APos);
     if R.Count = 0 then
       FRangeArrays.Delete(i);
   end;
end;

function TRangeCollection.GetRangesAtPos(List: TList; Pos: integer): integer;
var i, idx: integer;
    R: TRangeList;
begin
  Result := -1;
  for i := 0 to FRangeArrays.Count - 1 do
   begin
     R := TRangeList(FRangeArrays[i]);
     idx := R.NextAt(Pos);
     if idx <> -1 then
      begin
       if R[idx].StartPos <= Pos then
        begin
         List.Add(R[idx]);
         if (Result = -1) or (Result > R[idx].EndPos) then
           Result := R[idx].EndPos;
        end else
         if (Result = -1) or (Result > R[idx].StartPos) then
           Result := R[idx].StartPos;
      end;
   end;
end;

function TRangeCollection.GetRangesAtRange(List: TList; StartPos,
  EndPos: integer): integer;
var i, idx: integer;
    R: TRangeList;
begin
  Result := -1;
  for i := 0 to FRangeArrays.Count - 1 do
   begin
     R := TRangeList(FRangeArrays[i]);
     idx := R.NextAt(StartPos);
     if (idx <> -1) then
      while (Idx < R.Count) and (R[idx].StartPos < EndPos) do
       begin
        List.Add(R[idx]);
        Inc(Idx);
       end;
   end;
end;

function TRangeCollection.GetLevelCount: integer;
begin
  Result := FRangeArrays.Count;
end;

function TRangeCollection.GetLevels(Index: integer): TRangeList;
begin
  Result := TRangeList(FRangeArrays[Index]);
end;

{ TSortedList }

function TSortedList.Add(Item: TSortedItem): integer;
begin
  if (Count = 0) or (Items[Count - 1].GetKey <= Item.GetKey) then
   begin
    Result := Count;
    FList.Add(Item);
   end else
   begin
    Result := PriorAt(Item.GetKey);
    Inc(Result);
    if Result = Count then FList.Add(Item)
     else FList.Insert(Result, Item);
   end;
end;

procedure TSortedList.Clear;
begin
  FList.Clear;
end;

constructor TSortedList.Create(OwnObjects: Boolean);
begin
  inherited Create;
  if OwnObjects then
    FList := TObjectList.Create
  else
    FList := TList.Create;
end;

procedure TSortedList.Delete(Index: integer);
begin
  FList.Delete(Index);
end;

destructor TSortedList.Destroy;
begin
  FList.Free;
  inherited;
end;

function TSortedList.GetAt(Pos: integer): TSortedItem;
var idx: integer;
begin
  idx := GetIndexAt(Pos);
  if idx = -1 then Result := nil
   else Result := Items[idx];
end;

function TSortedList.GetIndexAt(Pos: integer): integer;
begin
  Result := PriorAt(Pos);
  if (Result <> -1) and (Items[Result].GetKey <> Pos) then
    Result := -1;
end;

function TSortedList.GetCount: integer;
begin
  Result := FList.Count;
end;

function TSortedList.GetItem(Index: integer): TSortedItem;
begin
  Result := TSortedItem(FList[Index]);
end;

function ItemCmp(const List: TList; Index: integer; Key: TObject): integer;
begin
  Result := TSortedItem(List[Index]).GetKey - integer(Key);
end;

function TSortedList.PriorAt(Pos: integer): integer;
begin
  QuickSearch(FList, ItemCmp, TObject(Pos), Result);
end;

procedure TSortedList.Remove(Item: TSortedItem);
begin
  FList.Remove(Item);
end;

end.
