// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core;
using HordeServer.Api;
using HordeServer.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HordeServer.Logs
{
///
/// Data for a log chunk
///
public class LogChunkData
{
///
/// Offset of this chunk
///
public long Offset { get; }
///
/// Length of this chunk
///
public int Length { get; }
///
/// Line index of this chunk
///
public int LineIndex { get; }
///
/// Number of lines in this chunk
///
public int LineCount { get; }
///
/// List of sub-chunks
///
public IReadOnlyList SubChunks { get; }
///
/// Offset of each sub-chunk within this chunk
///
public int[] SubChunkOffset { get; }
///
/// Total line count of the chunk after each sub-chunk
///
public int[] SubChunkLineIndex { get; }
///
/// Constructor
///
/// Offset of this chunk
/// First line index of this chunk
/// Sub-chunks for this chunk
public LogChunkData(long Offset, int LineIndex, IReadOnlyList SubChunks)
{
this.Offset = Offset;
this.LineIndex = LineIndex;
this.SubChunks = SubChunks;
this.SubChunkOffset = CreateSumLookup(SubChunks, x => x.Length);
this.SubChunkLineIndex = CreateSumLookup(SubChunks, x => x.LineCount);
if(SubChunks.Count > 0)
{
int LastSubChunkIdx = SubChunks.Count - 1;
Length = SubChunkOffset[LastSubChunkIdx] + SubChunks[LastSubChunkIdx].Length;
LineCount = SubChunkLineIndex[LastSubChunkIdx] + SubChunks[LastSubChunkIdx].LineCount;
}
}
///
/// Creates a lookup table by summing a field across a list of subchunks
///
///
///
///
static int[] CreateSumLookup(IReadOnlyList SubChunks, Func Field)
{
int[] Total = new int[SubChunks.Count];
int Value = 0;
for (int Idx = 0; Idx + 1 < SubChunks.Count; Idx++)
{
Value += Field(SubChunks[Idx]);
Total[Idx + 1] = Value;
}
return Total;
}
///
/// Gets the offset of a line within the file
///
/// The line index
/// Offset of the line within the file
public long GetLineOffsetWithinChunk(int LineIdx)
{
int SubChunkIdx = SubChunkLineIndex.BinarySearch(LineIdx);
if (SubChunkIdx < 0)
{
SubChunkIdx = ~SubChunkIdx - 1;
}
return SubChunkOffset[SubChunkIdx] + SubChunks[SubChunkIdx].InflateText().LineOffsets[LineIdx - SubChunkLineIndex[SubChunkIdx]];
}
///
/// Gets the sub chunk index for the given line
///
/// Line index within the chunk
/// Subchunk line index
public int GetSubChunkForLine(int ChunkLineIdx)
{
int SubChunkIdx = SubChunkLineIndex.BinarySearch(ChunkLineIdx);
if (SubChunkIdx < 0)
{
SubChunkIdx = ~SubChunkIdx - 1;
}
return SubChunkIdx;
}
///
/// Gets the index of the sub-chunk containing the given offset
///
/// Offset to search for
/// Index of the sub-chunk
public int GetSubChunkForOffsetWithinChunk(int Offset)
{
int SubChunkIdx = SubChunkOffset.BinarySearch(Offset);
if (SubChunkIdx < 0)
{
SubChunkIdx = ~SubChunkIdx - 1;
}
return SubChunkIdx;
}
///
/// Signature for output data
///
const int CurrentSignature = 'L' | ('C' << 8) | ('D' << 16);
///
/// Read a log chunk from the given stream
///
/// The reader to read from
/// Offset of this chunk within the file
/// Line index of this chunk
/// New log chunk data
public static LogChunkData Read(MemoryReader Reader, long Offset, int LineIndex)
{
int Signature = Reader.ReadInt32();
if ((Signature & 0xffffff) != CurrentSignature)
{
List SubChunks = new List();
SubChunks.Add(new LogSubChunkData(LogType.Json, Offset, LineIndex, new ReadOnlyLogText(Reader.Memory)));
Reader.Offset = Reader.Memory.Length;
return new LogChunkData(Offset, LineIndex, SubChunks);
}
int Version = Signature >> 24;
if (Version == 0)
{
List SubChunks = ReadSubChunkList(Reader, Offset, LineIndex);
Reader.ReadVariableLengthBytes();
return new LogChunkData(Offset, LineIndex, SubChunks);
}
else
{
List SubChunks = ReadSubChunkList(Reader, Offset, LineIndex);
return new LogChunkData(Offset, LineIndex, SubChunks);
}
}
///
/// Read a list of sub-chunks from the given stream
///
/// The reader to read from
/// Offset of this chunk within the file
/// Line index of this chunk
/// List of sub-chunks
static List ReadSubChunkList(MemoryReader Reader, long SubChunkOffset, int SubChunkLineIndex)
{
int NumSubChunks = Reader.ReadInt32();
List SubChunks = new List();
for (int Idx = 0; Idx < NumSubChunks; Idx++)
{
LogSubChunkData SubChunkData = Reader.ReadLogSubChunkData(SubChunkOffset, SubChunkLineIndex);
SubChunkOffset += SubChunkData.Length;
SubChunkLineIndex += SubChunkData.LineCount;
SubChunks.Add(SubChunkData);
}
return SubChunks;
}
///
/// Construct an object from flat memory buffer
///
/// Memory buffer
/// Offset of this chunk within the file
/// Line index of this chunk
/// Log chunk data
public static LogChunkData FromMemory(ReadOnlyMemory Memory, long Offset, int LineIndex)
{
MemoryReader Reader = new MemoryReader(Memory);
LogChunkData ChunkData = Read(Reader, Offset, LineIndex);
Reader.CheckOffset(Memory.Length);
return ChunkData;
}
///
/// Write the chunk data to a stream
///
/// Serialized data
public void Write(MemoryWriter Writer)
{
Writer.WriteInt32(CurrentSignature | (1 << 24));
Writer.WriteInt32(SubChunks.Count);
foreach (LogSubChunkData SubChunk in SubChunks)
{
Writer.WriteLogSubChunkData(SubChunk);
}
}
///
/// Construct an object from flat memory buffer
///
/// Log chunk data
public byte[] ToByteArray()
{
byte[] Data = new byte[GetSerializedSize()];
MemoryWriter Writer = new MemoryWriter(Data);
Write(Writer);
Writer.CheckOffset(Data.Length);
return Data;
}
///
/// Determines the size of the serialized buffer
///
public int GetSerializedSize()
{
return sizeof(int) + (sizeof(int) + SubChunks.Sum(x => x.GetSerializedSize()));
}
}
///
/// Extensions for the log chunk data
///
static class LogChunkDataExtensions
{
///
/// Read a log chunk from the given stream
///
/// The reader to read from
/// Offset of this chunk within the file
/// Line index of this chunk
/// New log chunk data
public static LogChunkData ReadLogChunkData(this MemoryReader Reader, long Offset, int LineIndex)
{
return LogChunkData.Read(Reader, Offset, LineIndex);
}
///
/// Write the chunk data to a stream
///
/// Serialized data
public static void WriteLogChunkData(this MemoryWriter Writer, LogChunkData ChunkData)
{
ChunkData.Write(Writer);
}
}
}