namespace skyscraper8.ReutersWne;

internal class WneStory : IDisposable
{
    public uint SessionId { get; }

    public WneStory(uint sessionId)
    {
        SessionId = sessionId;
    }
    
    public byte EccGroup;
    public byte ExpectedEccGroup;
    public int timesSucessfullySynced;
    public int FileDeliveryType;
    public int ExpectedContinuityCounter;
    public string SourceFileName;
    public string DestinationFileName;
    public uint ExpectedPayloadBlock;

    private List<byte[]> payloadBlocks;
    public bool Corrupted;
    public bool Delivered;

    private MemoryStream preallocated;
    private uint _fileSize;
    public uint FileSize
    {
        get 
        { 
            return _fileSize; 
        }
        set
        {
            if (payloadBlocks != null)
            {
                throw new NotImplementedException("Can't set the file size when a block was already delivered");
            }
            if (preallocated == null)
            {
                preallocated = new MemoryStream(new byte[value]);
            }
            else if (preallocated.Length != value)
            {
                preallocated.SetLength(value);
            }
            _fileSize = value;
        }
    }

    public long CaughtPayloadBytes;

    public void AppendPayloadBlock(Span<byte> slice)
    {
        if (preallocated != null)
        {
            int writeBlockLen = (int)Math.Min(slice.Length, preallocated.Length - preallocated.Position);
            slice = slice.Slice(0, writeBlockLen);
            preallocated.Write(slice);
            ExpectedPayloadBlock++;
            CaughtPayloadBytes += slice.Length;
        }
        else
        {
            if (payloadBlocks == null)
            {
                payloadBlocks = new List<byte[]>();
            }
            payloadBlocks.Add(slice.ToArray());
            ExpectedPayloadBlock++;
            CaughtPayloadBytes += slice.Length;
        }
    }

    public bool IsEmpty()
    {
        if (preallocated != null)
        {
            return preallocated.Position != preallocated.Length;
        }
        if (payloadBlocks == null)
        {
            return true;
        }

        if (payloadBlocks.Count == 0)
        {
            return true;
        }
        
        for (int i = 0; i < payloadBlocks.Count; i++)
        {
            if (payloadBlocks[i].Length > 0)
            {
                return false;
            }
        }
        
        return true;
    }

    public Stream ToStream()
    {
        if (Delivered)
        {
            throw new InvalidOperationException("Cannot convert a delivered story to a stream.");
        }

		if (preallocated != null)
		{
			preallocated.Position = 0;
			return preallocated;
		}
		else
		{
			return new ListByteArrayStream(payloadBlocks);
		}
    }

    public void Dispose()
    {
        preallocated?.Dispose();
        preallocated = null;
        payloadBlocks?.Clear();
        payloadBlocks = null;
        Delivered = true;
    }

    public WneStoryProgress GetProgress()
    {
        return new WneStoryProgress(this);
    }

    private DateTime progressLastReported;
    internal bool IsProgressEventWorthwhile()
    {
        DateTime now = DateTime.Now;
        TimeSpan sinceLastReport = now - progressLastReported;
        if (sinceLastReport.TotalMilliseconds > 100)
        {
            progressLastReported = now;
            return true;
        }
        else
        {
            return false;
        }
    }
}

struct WneStoryProgress
{
    internal WneStoryProgress(WneStory source)
    {
        this.Filename = source.DestinationFileName;
        this.SessionId = source.SessionId;
		this.FileSize = source.FileSize;
        this.FileCaught = source.CaughtPayloadBytes;
        if (!string.IsNullOrEmpty(this.Filename))
        {
            if (this.Filename.StartsWith("\\"))
                this.Filename = this.Filename.Substring(1);
        }
        else
        {
            this.Filename = String.Format("skyscraper8_wne_{0}.bin", SessionId);
        }
    }

	public string Filename { get; }
	public uint SessionId { get; }
	public uint FileSize { get; }
	public long FileCaught { get; }
}