// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core;
using HordeServer.Api;
using HordeServer.Utilities;
using ICSharpCode.SharpZipLib.BZip2;
using Microsoft.AspNetCore.Routing.Constraints;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace HordeServer.Logs
{
///
/// Pending data for a sub-chunk
///
public class LogSubChunkData
{
///
/// Type of data stored in this subchunk
///
public LogType Type { get; }
///
/// Offset within the file of this sub-chunk
///
public long Offset { get; }
///
/// Length of this sub-chunk
///
public int Length { get; }
///
/// Index of the first line in this sub-chunk
///
public int LineIndex { get; }
///
/// Gets the number of lines in this sub-chunk
///
public int LineCount { get; }
///
/// Text data
///
ILogText? TextInternal;
///
/// Compressed text data
///
ReadOnlyMemory CompressedTextInternal;
///
/// The index for this sub-chunk
///
LogIndexData? IndexInternal;
///
/// The log text
///
public ILogText InflateText()
{
if (TextInternal == null)
{
TextInternal = new ReadOnlyLogText(CompressedTextInternal.DecompressBzip2());
}
return TextInternal;
}
///
/// The compressed log text
///
public ReadOnlyMemory DeflateText()
{
if(CompressedTextInternal.IsEmpty)
{
CompressedTextInternal = TextInternal!.Data.CompressBzip2();
}
return CompressedTextInternal;
}
///
/// Index for tokens in this chunk
///
public LogIndexData BuildIndex()
{
if(IndexInternal == null)
{
ILogText PlainText = InflateText();
if (Type != LogType.Text)
{
PlainText = PlainText.ToPlainText();
}
LogIndexBlock[] Blocks = new LogIndexBlock[1];
Blocks[0] = new LogIndexBlock(LineIndex, LineCount, PlainText, PlainText.Data.CompressBzip2());
IndexInternal = new LogIndexData(null, 0, Blocks);
}
return IndexInternal;
}
///
/// Constructor
///
/// Type of data stored in this subchunk
/// Offset within the file of this sub-chunk
/// Index of the first line in this sub-chunk
/// Text to add
public LogSubChunkData(LogType Type, long Offset, int LineIndex, ILogText Text)
{
this.Type = Type;
this.Offset = Offset;
this.Length = Text.Data.Length;
this.LineIndex = LineIndex;
this.LineCount = Text.LineCount;
this.TextInternal = Text;
}
///
/// Constructor for raw data
///
/// Type of data stored in this subchunk
/// Offset within the file of this sub-chunk
/// Length of the uncompressed data
/// Index of the first line
/// Number of lines in the uncompressed text
/// Compressed text data
/// Index data
public LogSubChunkData(LogType Type, long Offset, int Length, int LineIndex, int LineCount, ReadOnlyMemory CompressedText, LogIndexData? Index)
{
this.Type = Type;
this.Offset = Offset;
this.Length = Length;
this.LineIndex = LineIndex;
this.LineCount = LineCount;
this.CompressedTextInternal = CompressedText;
this.IndexInternal = Index;
}
///
/// Constructs a sub-chunk from a block of memory. Uses slices of the given memory buffer rather than copying the data.
///
/// The reader to read from
/// Offset of the sub-chunk within this file
/// Index of the first line in this subchunk
/// New subchunk data
public static LogSubChunkData Read(MemoryReader Reader, long Offset, int LineIndex)
{
int Version = Reader.ReadInt32(); // Version placeholder
if (Version == 0)
{
LogType Type = (LogType)Reader.ReadInt32();
int Length = Reader.ReadInt32();
int LineCount = Reader.ReadInt32();
ReadOnlyMemory CompressedText = Reader.ReadVariableLengthBytes();
Reader.ReadVariableLengthBytes();
return new LogSubChunkData(Type, Offset, Length, LineIndex, LineCount, CompressedText, null);
}
else if (Version == 1)
{
LogType Type = (LogType)Reader.ReadInt32();
int Length = Reader.ReadInt32();
int LineCount = Reader.ReadInt32();
ReadOnlyMemory CompressedText = Reader.ReadVariableLengthBytes();
ReadOnlyMemory CompressedPlainText = Reader.ReadVariableLengthBytes();
ReadOnlyTrie Trie = Reader.ReadTrie();
LogIndexBlock IndexBlock = new LogIndexBlock(LineIndex, LineCount, null, CompressedPlainText);
LogIndexData Index = new LogIndexData(Trie, 0, new[] { IndexBlock });
return new LogSubChunkData(Type, Offset, Length, LineIndex, LineCount, CompressedText, Index);
}
else
{
LogType Type = (LogType)Reader.ReadInt32();
int Length = Reader.ReadInt32();
int LineCount = Reader.ReadInt32();
ReadOnlyMemory CompressedText = Reader.ReadVariableLengthBytes();
LogIndexData Index = Reader.ReadLogIndexData();
Index.SetBaseLineIndex(LineIndex); // Fix for incorrectly saved data
return new LogSubChunkData(Type, Offset, Length, LineIndex, LineCount, CompressedText, Index);
}
}
///
/// Serializes the sub-chunk to a stream
///
/// Writer to output to
public void Write(MemoryWriter Writer)
{
Writer.WriteInt32(2); // Version placeholder
Writer.WriteInt32((int)Type);
Writer.WriteInt32(Length);
Writer.WriteInt32(LineCount);
Writer.WriteVariableLengthBytes(DeflateText().Span);
Writer.WriteLogIndexData(BuildIndex());
}
///
/// Serializes this object to a byte array
///
/// Byte array
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 serialized data
///
public int GetSerializedSize()
{
return sizeof(int) + sizeof(int) + sizeof(int) + sizeof(int) + (sizeof(int) + DeflateText().Length) + BuildIndex().GetSerializedSize();
}
}
///
/// Pending data for a sub-chunk
///
public static class LogSubChunkDataExtensions
{
///
/// Constructs a sub-chunk from a block of memory. Uses slices of the given memory buffer rather than copying the data.
///
/// The reader to read from
/// Offset of this sub-chunk within the file
/// Index of the first line within this chunk
/// New subchunk data
public static LogSubChunkData ReadLogSubChunkData(this MemoryReader Reader, long Offset, int LineIndex)
{
return LogSubChunkData.Read(Reader, Offset, LineIndex);
}
///
/// Serializes the sub-chunk to a stream
///
/// Writer to output to
/// The sub-chunk data to write
public static void WriteLogSubChunkData(this MemoryWriter Writer, LogSubChunkData SubChunkData)
{
SubChunkData.Write(Writer);
}
}
}