{************************************************************}
{                   Delphi VCL Extensions                    }
{                                                            }
{     New DWG section AcDb:AcDsPrototype_1b format parcer    }
{                                                            }
{      Copyright (c) 2002-2013 SoftGold software company     }
{                                                            }
{************************************************************}


{$I SGDXF.INC}
unit sgDataSegFile;

interface

uses
  Windows, SysUtils, Classes, CADImage, sgFunction, sgConsts, sgLists, DXFConv,
  sgComparer;

type
  TsgSegmentedFileHeader = packed record
		FileSignature: Cardinal;
		FileHeaderSize: Integer;
		Unknown3: Integer;
		Version: Integer;
		Unknown5: Integer;
		DsEntryCount: Integer;
		SegIdxOffset: Integer;
		SegIdxUnknown: Integer;
		SegIdxEntryCount: Integer;
		SchIdxIndex: Integer;
		DatIdxIndex: Integer;
		SearchIndex: Integer;
		PrvsavIndex: Integer;
		FileSize: Integer;
  end;

  PsgFileSegmentHeader = ^TsgFileSegmentHeader;
  TsgSegName = packed array[0..6] of AnsiChar;
  TsgFileSegmentHeader = packed record
		Signature: Word; //$D5AC
		Name: TsgSegName;
		SegmentIndex: Integer;
		Unknown1: Integer;
		SegmentSize: Cardinal;
		Unknown2: Integer;
		DataStorageRevision: Integer;
		Unknown3: Integer;
		SysDataAlignOffset: Integer;
		ObjDataAlignOffset: Integer;
    AlignmentBytes: packed array[0..7] of Byte;
  end;

  PsgFileSegment = ^TsgFileSegment;
  TsgFileSegment = packed record
    Header: TsgFileSegmentHeader;
  	StartOffset: UInt64;
  end;

  TsgSegIdxEntryType = (stUnknown, stSegInd, stDatInd, stData, stSchInd,
    stSchData, stBlob01, stPrvsav, stFreesp);

  PsgSegIdxEntry = ^TsgSegIdxEntry;
  TsgSegIdxEntry = record
    Offset: UInt64;
    Size: Cardinal;
    SegIdxType: TsgSegIdxEntryType;
  end;

  PsgDatIdxEntry = ^TsgDatIdxEntry;
  TsgDatIdxEntry = packed record
    SegmentIndex: Integer;
		LocalOffset: Cardinal;
    SchemaIndex: Cardinal;
  end;

  PsgDatHeaderEntry = ^TsgDatHeaderEntry;
  TsgDatHeaderEntry = packed record
    EntrySize: Cardinal;
		Unknown: Cardinal;
    Handle: UInt64;
    LocalOffset: Cardinal;
  end;

  TsgDataBlobReferencePage = packed record
    SegmentIndex: Integer;
    Size: Integer;
  end;
  TsgDataBlobReferencePages = array of TsgDataBlobReferencePage;

  PsgDataBlobReference = ^TsgDataBlobReference;
  TsgDataBlobReference = packed record
	  TotalDataSize: UInt64;
		PageCount: Cardinal;
		RecordSize: Cardinal;
		PageSize: Cardinal;
		LastPageSize: Cardinal;
		Unknown1: Cardinal; //Must be 0
		Unknown2: Cardinal; //Must be 0
  end;

  TsgBlobDataEntry = array of Byte;

  TsgDSDataEntryType = (etUnknown, etACISBin, atACISTxt, etPNG);
  PsgDataSegmentEntry = ^TsgDataSegmentEntry;
  TsgDataSegmentEntry = record
    Header: TsgDatHeaderEntry;
    BlobReference: TsgDataBlobReference;
    DataSize: Cardinal;
    EntryType: TsgDSDataEntryType;
    Data: PByte;
    DataNeedsToDispose: Boolean;
  end;

  TsgBlobEntryReferenceHeader = packed record
    TotalDataSize: UInt64;
    PageStartOffset: UInt64;
    PageIndex: Integer;
    PageCount: Integer;
    PageDataSize: UInt64;
  end;

  PsgBlobEntryReference = ^TsgBlobEntryReference;
  TsgBlobEntryReference = record
    Header: TsgBlobEntryReferenceHeader;
    Offset: UInt64;
    Data: PByte;
  end;

  TsgDataSegFileError = class(Exception);

 	TsgDSEntriesList = class
  private
    FEntries: TsgList;
    function GetCount: Integer;
    function GetEntries: TsgList;
    function GetPEntry(const Index: Integer): Pointer;
  protected
    function GetEntrySize: Integer; virtual; abstract;
    procedure CheckIndex(const Index: Integer);
    property Entries: TsgList read GetEntries;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Clear; virtual;
    procedure SetDefEntryValues(var Entry); virtual;
    procedure Delete(const Index: Integer);
    property Count: Integer read GetCount;
    property PEntry[const Index: Integer]: Pointer read GetPEntry;
  end;

  TsgDSEntriesListWithFileSegment = class(TsgDSEntriesList)
  private
    FFileSegment: TsgFileSegment;
  protected
    property FileSegment: TsgFileSegment read FFileSegment;
  public
    constructor Create; override;
    procedure Clear; override;
  end;

  TsgSegIdxEntriesList = class(TsgDSEntriesListWithFileSegment)
  private
    function GetEntry(const Index: Integer): TsgSegIdxEntry;
  protected
    function GetEntrySize: Integer; override;
  public
    function Add(const Entry: TsgSegIdxEntry): Integer;
    procedure SetDefEntryValues(var Entry); override;
    property Entry[const Index: Integer]: TsgSegIdxEntry read GetEntry; default;
  end;

  TsgIntegerArray = array of Integer;

  TsgDatIdxEntriesList = class(TsgDSEntriesListWithFileSegment)
  private
    FDataSegmentsOffsets: array of TsgIntegerArray;
    function GetEntry(const Index: Integer): TsgDatIdxEntry;
  protected
    function GetEntrySize: Integer; override;
  public
    constructor Create; override;
    destructor Destroy; override;
    function Add(const Entry: TsgDatIdxEntry): Integer;
    property Entry[const Index: Integer]: TsgDatIdxEntry read GetEntry; default;
  end;

  TsgDSEntriesListWithFileSegmentSorted = class(TsgDSEntriesListWithFileSegment)
  protected
    function CompareEntries(const Entry1, Entry2: Pointer): Integer; virtual;
  public
    constructor Create; override;
    procedure Clear; override;
  end;

  TsgBlobEntryRefList = class(TsgDSEntriesListWithFileSegmentSorted)
  private
    function GetEntry(const Index: Integer): TsgBlobEntryReference;
  protected
    function CompareEntries(const Entry1, Entry2: Pointer): Integer; override;
    function GetEntrySize: Integer; override;
  public
    function Add(const Entry: TsgBlobEntryReference): Integer;
    function IndexOf(const Offset: UInt64; const PageIndex: Integer): Integer;
    property Entry[const Index: Integer]: TsgBlobEntryReference read GetEntry; default;
  end;

	TsgDatSegEntriesList = class(TsgDSEntriesListWithFileSegmentSorted)
  private
    function GetEntry(const Index: Integer): TsgDataSegmentEntry;
    function GetPEntry(const Index: Integer): PsgDataSegmentEntry;
  protected
    function CompareEntries(const Entry1, Entry2: Pointer): Integer; override;
    function GetEntrySize: Integer; override;
    procedure SetEntry(const Index: Integer; const Entry: TsgDataSegmentEntry);
  public
    destructor Destroy; override;
    function Add(const Entry: TsgDataSegmentEntry): Integer;
    procedure Clear; override;
    function IndexOf(const Handle: UInt64): Integer;
    procedure SetDefEntryValues(var Entry); override;
    property Entry[const Index: Integer]: TsgDataSegmentEntry read GetEntry; default;
    property PEntry[const Index: Integer]: PsgDataSegmentEntry read GetPEntry;
  end;

  TsgSegmentedFile = class
  private
    FConverter: TsgDXFConverter;
    FHeader: TsgSegmentedFileHeader;
    FStartOffset: UInt64;
    FSegIdxSegment: TsgSegIdxEntriesList;
    FDatIdxSegment: TsgDatIdxEntriesList;
    FDataSegment: TsgDatSegEntriesList;
    FBlobEntries: TsgBlobEntryRefList;
    FStream: TMemoryStream;
    FBlobSegmentWasRead: Boolean;
    function CheckOffset(const Offset: UInt64): Boolean;
    function GetDataEntry(const Index: Integer): TsgDataSegmentEntry;
    function GetDataEntryType(Mem: Pointer; Size: Cardinal): TsgDSDataEntryType;
    function GetSegIndType(const SegName: TsgSegName): TsgSegIdxEntryType;
    procedure ReadAllSegIdx;
    procedure ReadHeader;
    procedure ReadDataSegment(const Offset: UInt64; const Index: Integer);
    procedure ReadDatIdxSegment(Offset: Cardinal);
    function ReadFileIndexes(IdxSegment: TsgDSEntriesListWithFileSegment;
      const Offset: UInt64): Boolean;
    procedure ReadSegIdxSegment;
    procedure ReadBlob01Segment;
    procedure SetDataEntry(PEntry: PsgDataSegmentEntry; Size: Cardinal;
      ReadBytes: PCardinal; Memory: Pointer);
  public
    constructor Create(Converter: TsgDXFConverter);
    constructor CreateFromMemory(Converter: TsgDXFConverter; Mem: Pointer;
      Size: Cardinal);
    destructor Destroy; override;
    procedure ReadFile;
    function IndexOf(const Handle: UInt64): Integer;
    property DataEntry[const Index: Integer]: TsgDataSegmentEntry read
      GetDataEntry; default;
  end;

implementation

const
  cntSegIndName: TsgSegName = ('s', 'e', 'g', 'i', 'd', 'x', #0);
  cntDatIndName: TsgSegName = ('d', 'a', 't', 'i', 'd', 'x', #0);
  cntDataName: TsgSegName = ('_', 'd', 'a', 't', 'a', '_', #0);
  cntSchIndName: TsgSegName = ('s', 'c', 'h', 'i', 'd', 'x', #0);
  cntSchDataName: TsgSegName = ('s', 'c', 'h', 'd', 'a', 't', #0);
  cntBlob01Name: TsgSegName = ('b', 'l', 'o', 'b', '0', '1', #0);
  cntPrvsavName: TsgSegName = ('p', 'r', 'v', 's', 'a', 'v', #0);
  cntFreespName: TsgSegName = ('f', 'r', 'e', 'e', 's', 'p', #0);

{ TsgSegmentedFile }

function TsgSegmentedFile.CheckOffset(const Offset: UInt64): Boolean;
begin
  Result := (Offset > 0) and (Offset < FStream.Size);
end;

constructor TsgSegmentedFile.Create(Converter: TsgDXFConverter);
begin
  FBlobSegmentWasRead := False;
  FConverter := Converter;
  FStream := TMemoryStream.Create;
  FSegIdxSegment := TsgSegIdxEntriesList.Create;
  FDatIdxSegment := TsgDatIdxEntriesList.Create;
  FDataSegment := TsgDatSegEntriesList.Create;
  FBlobEntries := TsgBlobEntryRefList.Create;
  FStartOffset := 0;
  FillChar(FHeader, SizeOf(FHeader), 0);
  FHeader.FileSignature := $6472616A;
end;

constructor TsgSegmentedFile.CreateFromMemory(Converter: TsgDXFConverter;
  Mem: Pointer; Size: Cardinal);
begin
  Create(Converter);
  FStream.Write(Mem^, Size);
  FStream.Position := 0;
end;

destructor TsgSegmentedFile.Destroy;
begin
  FStream.Free;
  FSegIdxSegment.Free;
  FDatIdxSegment.Free;
  FDataSegment.Free;
  FBlobEntries.Free;
  inherited Destroy;
end;

function TsgSegmentedFile.GetDataEntry(const Index: Integer): TsgDataSegmentEntry;
begin
  FDataSegment.SetDefEntryValues(Result);
  if (Index >= 0) and (Index < FDataSegment.Count) then
    Result := FDataSegment[Index];
end;

function TsgSegmentedFile.GetDataEntryType(Mem: Pointer; Size: Cardinal): TsgDSDataEntryType;
const
  cntPNG: AnsiString = '‰PNG';
var
  S: AnsiString;
begin
  SetString(S, PAnsiChar(Mem), Size);
  Result := etUnknown;
  if IsSatBinaryFile(Mem, Size) then
    Result := etACISBin
  else
    if Pos(cntPNG, S) = 1 then
      Result := etPNG;
end;

function TsgSegmentedFile.GetSegIndType(const SegName: TsgSegName): TsgSegIdxEntryType;
var
  vSize: Integer;

  function CompareName(const Name: TsgSegName): Boolean;
  begin
    Result := CompareMem(@SegName, @Name, vSize);
  end;

begin
  Result := stUnknown;
  vSize := SizeOf(TsgSegName);
  if CompareName(cntSegIndName) then
    Result := stSegInd
  else
    if CompareName(cntDatIndName) then
      Result := stDatInd
    else
      if CompareName(cntDataName) then
        Result := stData
      else
        if CompareName(cntSchIndName) then
          Result := stSchInd
        else
          if CompareName(cntSchDataName) then
            Result := stSchData
          else
            if CompareName(cntBlob01Name) then
              Result := stBlob01
            else
              if CompareName(cntPrvsavName) then
                Result := stPrvsav
              else
                if CompareName(cntFreespName) then
                  Result := stFreesp;
end;

function TsgSegmentedFile.IndexOf(const Handle: UInt64): Integer;
begin
  Result := FDataSegment.IndexOf(Handle);
end;

procedure TsgSegmentedFile.ReadAllSegIdx;
var
  I: Integer;
  vSegment: TsgDSEntriesListWithFileSegment;
  vPEntry: Pointer;
begin
  vSegment := TsgSegIdxEntriesList.Create;
  try
    for I := 0 to FSegIdxSegment.Count - 1 do
    begin
      vSegment.Clear;
      if FSegIdxSegment[I].Offset = 0 then Continue;
      if not ReadFileIndexes(vSegment, FSegIdxSegment[I].Offset) then
        Continue;
      vPEntry := FSegIdxSegment.PEntry[I];
      TsgSegIdxEntry(vPEntry^).SegIdxType := GetSegIndType(vSegment.FFileSegment.Header.Name);
    end;
  finally
    vSegment.Free;
  end;
end;

procedure TsgSegmentedFile.ReadBlob01Segment;
var
  vSegIndxEntry: TsgSegIdxEntry;
  vBlobEntry: TsgBlobEntryReference;
  I, vPageCount: Integer;
  vPos: Int64;
begin
  if FBlobSegmentWasRead then Exit;
  vPos := FStream.Position;
  try
    for I := 0 to FSegIdxSegment.Count - 1 do
    begin
      vSegIndxEntry := FSegIdxSegment[I];
      if vSegIndxEntry.SegIdxType = stBlob01 then
      begin
        FStream.Position := vSegIndxEntry.Offset;
        if not ReadFileIndexes(FBlobEntries, vSegIndxEntry.Offset) then
          Exit;
        vPageCount := MaxInt;
        repeat
          FStream.Read(vBlobEntry, SizeOf(vBlobEntry.Header));
          vBlobEntry.Data := FStream.Memory;
          Inc(vBlobEntry.Data, FStream.Position);
          FStream.Position := FStream.Position + vBlobEntry.Header.PageDataSize;
          vBlobEntry.Offset := vSegIndxEntry.Offset;
          FBlobEntries.Add(vBlobEntry);
          if vPageCount = MaxInt then
            vPageCount := vBlobEntry.Header.PageCount;
          Dec(vPageCount);
        until vPageCount = 0;
      end;
    end;
  finally
    FStream.Position := vPos;
    FBlobEntries.Entries.Sorted := True;
    FBlobSegmentWasRead := True;
  end;
end;

procedure TsgSegmentedFile.ReadDataSegment(const Offset: UInt64;
  const Index: Integer);
var
  vDataStartPos, vMaxStreamPos, vDataHeadersStartPos: UInt64;
  I, J, vIndex, vFirstIndex: Integer;
  vLow, vHigh: Integer;
  vDataSize, vReadBytes, vMaxRecordSize: Cardinal;
  vEntry: TsgDataSegmentEntry;
  vPEntry: PsgDataSegmentEntry;
  vBlobReferencePage: TsgDataBlobReferencePage;
  vBlobReferencePages: TsgDataBlobReferencePages;
begin
  if not CheckOffset(Offset) then Exit;
  FDataSegment.Entries.Sorted := False;
  if not ReadFileIndexes(FDataSegment, Offset) then
    Exit;
  vFirstIndex := FDataSegment.Count;
  vLow := Low(FDatIdxSegment.FDataSegmentsOffsets[Index]);
  vHigh := High(FDatIdxSegment.FDataSegmentsOffsets[Index]);
  vDataHeadersStartPos := FStream.Position;
  for I := vLow to vHigh do
  begin
    FDataSegment.SetDefEntryValues(vEntry);
    FStream.Position := vDataHeadersStartPos +
      FDatIdxSegment.FDataSegmentsOffsets[Index][I];
    FStream.Read(vEntry.Header, SizeOf(vEntry.Header));
    FDataSegment.Add(vEntry);
  end;
  vDataStartPos := FDataSegment.FileSegment.Header.ObjDataAlignOffset shl 4 +
    Offset;
  vMaxStreamPos := Offset + FDataSegment.FileSegment.Header.SegmentSize;
  for I := vLow to vHigh do
  begin
    vPEntry := FDataSegment.PEntry[I + vFirstIndex];
    FStream.Position := vDataStartPos + vPEntry^.Header.LocalOffset;
    vMaxRecordSize := vMaxStreamPos - FStream.Position;
    FStream.Read(vDataSize, 4);
    if vDataSize = 0 then
      Continue;
    if vDataSize + 4 <= vMaxRecordSize then
    begin
      vReadBytes := FStream.Position;
      SetDataEntry(vPEntry, vDataSize, @vReadBytes, nil);
      if vReadBytes >= FDataSegment.FileSegment.Header.SegmentSize then
        Break;
    end
    else
    begin
      if vDataSize = $BB106BB1 then
      begin
        if not FBlobSegmentWasRead then
          ReadBlob01Segment;
        FStream.Read(vPEntry^.BlobReference, SizeOf(TsgDataBlobReference));
        try
          SetLength(vBlobReferencePages, vPEntry^.BlobReference.PageCount);
        except
          Continue;
        end;
        for J := Low(vBlobReferencePages) to High(vBlobReferencePages) do
        begin
          FStream.Read(vBlobReferencePage, SizeOf(vBlobReferencePage));
          vBlobReferencePages[J] := vBlobReferencePage;
        end;
        for J := Low(vBlobReferencePages) to High(vBlobReferencePages) do
        begin
          vBlobReferencePage := vBlobReferencePages[J];
          if vPEntry^.BlobReference.PageCount = 1 then
          begin
            vIndex := FBlobEntries.IndexOf(FSegIdxSegment[vBlobReferencePage.SegmentIndex].Offset,
              0);
            if vIndex >= 0 then
              SetDataEntry(vPEntry, FBlobEntries[vIndex].Header.PageDataSize,
                nil, FBlobEntries[vIndex].Data);
          end
          else
          begin
            // we have no files where BlobReference.PageCount is greater than 1
          end;
        end;
      end;
    end;
  end;
end;

procedure TsgSegmentedFile.ReadDatIdxSegment(Offset: Cardinal);
var
  I, vNumEntries, vValue: Integer;
  vEntry: TsgDatIdxEntry;
begin
  if (not CheckOffset(Offset)) or (not ReadFileIndexes(FDatIdxSegment, Offset)) then Exit;
  FStream.Read(vNumEntries, SizeOf(vNumEntries));
  FStream.Read(vValue, SizeOf(vValue));
  for I := 0 to vNumEntries - 1 do
  begin
    FStream.Read(vEntry, SizeOf(vEntry));
    if vEntry.SegmentIndex = 0 then
      Continue;
    FDatIdxSegment.Add(vEntry);
  end;
end;

procedure TsgSegmentedFile.ReadFile;
var
  I, vLow, vHigh: Integer;
begin
  FBlobSegmentWasRead := False;
  FSegIdxSegment.Clear;
  FDatIdxSegment.Clear;
  FDataSegment.Clear;
  FBlobEntries.Clear;
  if FStream.Size = 0 then
    raise TsgDataSegFileError.Create(sDSFileIsNotInitializedError);
  ReadHeader;
  if not CheckOffset(FHeader.SegIdxOffset) then
    Exit;
  ReadSegIdxSegment;
  ReadDatIdxSegment(FSegIdxSegment[FHeader.DatIdxIndex].Offset);
  vLow := Low(FDatIdxSegment.FDataSegmentsOffsets);
  vHigh := High(FDatIdxSegment.FDataSegmentsOffsets);
  for I := 0 to FSegIdxSegment.Count - 1 do
  begin
    if FSegIdxSegment[I].SegIdxType = stData then
    begin
      if (I >= vLow) and (I <= vHigh) then
        ReadDataSegment(FSegIdxSegment[I].Offset, I);
    end;
  end;
  FDataSegment.Entries.Sorted := True;
end;

function TsgSegmentedFile.ReadFileIndexes(IdxSegment: TsgDSEntriesListWithFileSegment;
  const Offset: UInt64): Boolean;
var
  vHeader: PsgFileSegmentHeader;
  vVal1, vVal2: UInt64;

  procedure ClearIndSegment;
  begin
    vHeader := @IdxSegment.FileSegment.Header;
    FillChar(vHeader^, SizeOf(vHeader^), 0);
    IdxSegment.FFileSegment.StartOffset := 0;
  end;

begin
  Result := False;
  ClearIndSegment;
  if not CheckOffset(Offset) then
    Exit;
  FStream.Position := Offset;
  IdxSegment.FFileSegment.StartOffset := FStream.Position;
  FStream.Read(vHeader^.Signature, SizeOf(vHeader^.Signature));
  FStream.Read(vHeader^.Name, 6);
  FStream.Read(vHeader^.SegmentIndex, SizeOf(vHeader^.SegmentIndex));
  FStream.Read(vHeader^.Unknown1, SizeOf(vHeader^.Unknown1));
  FStream.Read(vHeader^.SegmentSize, SizeOf(vHeader^.SegmentSize));
  FStream.Read(vHeader^.Unknown2, SizeOf(vHeader^.Unknown2));
  FStream.Read(vHeader^.DataStorageRevision, SizeOf(vHeader^.DataStorageRevision));
  FStream.Read(vHeader^.Unknown3, SizeOf(vHeader^.Unknown3));
  FStream.Read(vHeader^.SysDataAlignOffset, SizeOf(vHeader^.SysDataAlignOffset));
  FStream.Read(vHeader^.ObjDataAlignOffset, SizeOf(vHeader^.ObjDataAlignOffset));
  FStream.Read(vVal1, 8);
  FillChar(vVal2, 8, 85);
  Result := vVal1 = vVal2;
  if not Result then
    ClearIndSegment;
end;

procedure TsgSegmentedFile.ReadHeader;
begin
  FStream.Position := 0;
  FStream.Read(FHeader, SizeOf(FHeader)); //may be right to read the FileHeader fields separately
end;

procedure TsgSegmentedFile.ReadSegIdxSegment;
var
  I: Integer;
  vEntry: TsgSegIdxEntry;
begin
  if not CheckOffset(FHeader.SegIdxOffset) then Exit;
  if not ReadFileIndexes(FSegIdxSegment, FHeader.SegIdxOffset) then Exit;
  for I := 0 to FHeader.SegIdxEntryCount - 1 do
  begin
    FSegIdxSegment.SetDefEntryValues(vEntry);
    FStream.Read(vEntry.Offset, SizeOf(vEntry.Offset));
    FStream.Read(vEntry.Size, SizeOf(vEntry.Size));
    FSegIdxSegment.Add(vEntry);
  end;
  ReadAllSegIdx;
end;

procedure TsgSegmentedFile.SetDataEntry(PEntry: PsgDataSegmentEntry;
  Size: Cardinal; ReadBytes: PCardinal; Memory: Pointer);
begin
  if PEntry <> nil then
  begin
    if Memory = nil then
    begin
      PEntry^.Data := FStream.Memory;
      Inc(PEntry^.Data, FStream.Position);
    end
    else
      PEntry^.Data := Memory;
    PEntry^.DataSize := Size;
    PEntry^.EntryType := GetDataEntryType(PEntry^.Data, Size);
    if ReadBytes <> nil then
      Inc(ReadBytes^, Size);
  end;
end;

{ TsgFileSegment }

procedure TsgDSEntriesList.CheckIndex(const Index: Integer);
begin
  if (Index < 0) or (FEntries.Count = 0) or (Index > FEntries.Count - 1) then
    raise TsgDataSegFileError.Create(sDSFileSegIndexError);
end;

procedure TsgDSEntriesList.Clear;
begin
  FEntries.ClearTypeList(ptvRecord);
end;

constructor TsgDSEntriesList.Create;
begin
  FEntries := TsgList.Create;
  FEntries.Sorted := False;
end;

procedure TsgDSEntriesList.Delete(const Index: Integer);
var
  P: Pointer;
begin
  if (Index < 0) or (Index > FEntries.Count - 1) then Exit;
  P := FEntries.Items[Index];
  Dispose(P);
  FEntries.Delete(Index);
end;

destructor TsgDSEntriesList.Destroy;
begin
  Clear;
  FEntries.Free;
  inherited Destroy;
end;

function TsgDSEntriesList.GetCount: Integer;
begin
  Result := FEntries.Count;
end;

function TsgDSEntriesList.GetEntries: TsgList;
begin
  Result := FEntries;
end;

function TsgDSEntriesList.GetPEntry(const Index: Integer): Pointer;
begin
  CheckIndex(Index);
  Result := FEntries[Index];
end;

procedure TsgDSEntriesList.SetDefEntryValues(var Entry);
begin
  FillChar(Entry, GetEntrySize, 0);
end;

{ TsgDSEntriesListWithSegment }

procedure TsgDSEntriesListWithFileSegment.Clear;
begin
  inherited Clear;
  FillChar(FFileSegment, SizeOf(FFileSegment), 0);
end;

constructor TsgDSEntriesListWithFileSegment.Create;
begin
  inherited Create;
  Clear;
end;

{ TsgDataSegment }

function TsgDatSegEntriesList.Add(const Entry: TsgDataSegmentEntry): Integer;
var
  vEntry: PsgDataSegmentEntry;
begin
  New(vEntry);
  vEntry^ := Entry;
  Result := Entries.Add(vEntry);
end;

procedure TsgDatSegEntriesList.Clear;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
  begin
    if PEntry[I]^.DataNeedsToDispose and (PEntry[I]^.Data <> nil) then
      FreeMem(PEntry[I]^.Data);
  end;
  inherited Clear;
end;

function TsgDatSegEntriesList.CompareEntries(const Entry1, Entry2: Pointer): Integer;
var
  vEntry1, vEntry2: TsgDataSegmentEntry;
begin
  vEntry1 := PsgDataSegmentEntry(Entry1)^;
  vEntry2 := PsgDataSegmentEntry(Entry2)^;
  Result := TsgTypeComparer.CmpUInt64(vEntry1.Header.Handle, vEntry2.Header.Handle);
end;

destructor TsgDatSegEntriesList.Destroy;
begin
  inherited Destroy;
end;

function TsgDatSegEntriesList.GetEntry(const Index: Integer): TsgDataSegmentEntry;
begin
  CheckIndex(Index);
  Result := PsgDataSegmentEntry(Entries[Index])^;
end;

function TsgDatSegEntriesList.GetEntrySize: Integer;
begin
  Result := SizeOf(TsgDataSegmentEntry);
end;

function TsgDatSegEntriesList.GetPEntry(const Index: Integer): PsgDataSegmentEntry;
begin
  CheckIndex(Index);
  Result := PsgDataSegmentEntry(Entries[Index]);
end;

function TsgDatSegEntriesList.IndexOf(const Handle: UInt64): Integer;
var
  vEntry: TsgDataSegmentEntry;
begin
  SetDefEntryValues(vEntry);
  vEntry.Header.Handle := Handle;
  Result := Entries.IndexOf(@vEntry);
end;

procedure TsgDatSegEntriesList.SetDefEntryValues(var Entry);
begin
  inherited SetDefEntryValues(Entry);
  TsgDataSegmentEntry(Entry).Data := nil;
  TsgDataSegmentEntry(Entry).EntryType := etUnknown;
  TsgDataSegmentEntry(Entry).DataNeedsToDispose := False;
end;

procedure TsgDatSegEntriesList.SetEntry(const Index: Integer;
  const Entry: TsgDataSegmentEntry);
begin
  if (Index >= 0) and (Index <= Count - 1) then
  begin
    PsgDataSegmentEntry(Entries[Index])^ := Entry;
    if Entries.Sorted then
      Entries.Sort;
  end;
end;

{ TsgSegIdxEntriesList }

function TsgSegIdxEntriesList.Add(const Entry: TsgSegIdxEntry): Integer;
var
  vEntry: PsgSegIdxEntry;
begin
  New(vEntry);
  vEntry^ := Entry;
  Result := Entries.Add(vEntry);
end;

function TsgSegIdxEntriesList.GetEntry(const Index: Integer): TsgSegIdxEntry;
begin
  CheckIndex(Index);
  Result := PsgSegIdxEntry(Entries[Index])^;
end;

function TsgSegIdxEntriesList.GetEntrySize: Integer;
begin
  Result := SizeOf(TsgSegIdxEntry);
end;

procedure TsgSegIdxEntriesList.SetDefEntryValues(var Entry);
begin
  inherited SetDefEntryValues(Entry);
  TsgSegIdxEntry(Entry).SegIdxType := stUnknown;
end;

{ TsgDatIdxEntriesList }

function TsgDatIdxEntriesList.Add(const Entry: TsgDatIdxEntry): Integer;
var
  vEntry: PsgDatIdxEntry;
begin
  New(vEntry);
  vEntry^ := Entry;
  Result := Entries.Add(vEntry);
  if Length(FDataSegmentsOffsets) < Entry.SegmentIndex + 1 then
    SetLength(FDataSegmentsOffsets, Entry.SegmentIndex + 1);
  SetLength(FDataSegmentsOffsets[Entry.SegmentIndex],
    Length(FDataSegmentsOffsets[Entry.SegmentIndex]) + 1);
  FDataSegmentsOffsets[Entry.SegmentIndex][High(FDataSegmentsOffsets[Entry.SegmentIndex])] := Entry.LocalOffset;
end;

constructor TsgDatIdxEntriesList.Create;
begin
  inherited Create;
end;

destructor TsgDatIdxEntriesList.Destroy;
var
  I: Integer;
begin
  for I := Low(FDataSegmentsOffsets) to High(FDataSegmentsOffsets) do
    Finalize(FDataSegmentsOffsets[I]);
  Finalize(FDataSegmentsOffsets);
  inherited Destroy;
end;

function TsgDatIdxEntriesList.GetEntry(const Index: Integer): TsgDatIdxEntry;
begin
  CheckIndex(Index);
  Result := PsgDatIdxEntry(Entries[Index])^;
end;

function TsgDatIdxEntriesList.GetEntrySize: Integer;
begin
  Result := SizeOf(TsgDatIdxEntry);
end;

{ TsgBlobEntriesList }

function TsgBlobEntryRefList.Add(const Entry: TsgBlobEntryReference): Integer;
var
  vEntry: PsgBlobEntryReference;
begin
  New(vEntry);
  vEntry^ := Entry;
  Result := Entries.Add(vEntry);
end;

function TsgBlobEntryRefList.CompareEntries(const Entry1, Entry2: Pointer): Integer;
var
  vEntry1, vEntry2: TsgBlobEntryReference;
begin
  vEntry1 := PsgBlobEntryReference(Entry1)^;
  vEntry2 := PsgBlobEntryReference(Entry2)^;
  Result := TsgTypeComparer.CmpUInt64(vEntry1.Offset, vEntry2.Offset);
  if Result = 0 then
    Result := TsgTypeComparer.CmpInteger(vEntry1.Header.PageIndex, vEntry2.Header.PageIndex);
end;

function TsgBlobEntryRefList.GetEntry(const Index: Integer): TsgBlobEntryReference;
begin
  CheckIndex(Index);
  Result := PsgBlobEntryReference(Entries[Index])^;
end;

function TsgBlobEntryRefList.GetEntrySize: Integer;
begin
  Result := SizeOf(TsgBlobEntryReference);
end;

function TsgBlobEntryRefList.IndexOf(const Offset: UInt64;
  const PageIndex: Integer): Integer;
var
  vEntry: TsgBlobEntryReference;
begin
  SetDefEntryValues(vEntry);
  vEntry.Offset := Offset;
  vEntry.Header.PageIndex := PageIndex;
  Result := Entries.IndexOf(@vEntry);
end;

{ TsgDSEntriesListWithFileSegmentSorted }

procedure TsgDSEntriesListWithFileSegmentSorted.Clear;
begin
  inherited Clear;
  Entries.Sorted := False;
end;

function TsgDSEntriesListWithFileSegmentSorted.CompareEntries(const Entry1,
  Entry2: Pointer): Integer;
begin
  Result := 0;
end;

constructor TsgDSEntriesListWithFileSegmentSorted.Create;
begin
  inherited Create;
  Entries.Sorted := False;
  Entries.ProcCompare := CompareEntries;
end;

end.
