using skyscraper5.Skyscraper.IO;

namespace skyscraper8.Skyscraper.Scraper.Storage.Tar;

public class TarArchive : IDisposable, IAsyncDisposable
{
    public TarArchive(Stream stream)
    {
        if (!stream.CanSeek)
            throw new Exception("TarArchive must be able to seek.");

        backend = stream;
        byte[] headerBuffer = null;
        while (stream.Position < stream.Length)
        {
            if (headerBuffer == null)
                headerBuffer = new byte[512];
            
            stream.Read(headerBuffer, 0, 512);
            TarHeader header = TarHeader.Deserialize(headerBuffer);

            TarArchiveEntry entry = new TarArchiveEntry(header.Filename, header.Size, stream.Position);
            entry.FileName = header.Filename;
            entry.FileSize = header.Size;
            entry.Offset = stream.Position;

            stream.Position += header.GetSizeOnTape();
            
            if (entries == null)
                entries = new List<TarArchiveEntry>();
            entries.Add(entry);
        }
    }

    struct TarArchiveEntry
    {
	    public TarArchiveEntry(string fileName, long fileSize, long offset)
	    {
		    FileName = fileName;
		    FileSize = fileSize;
		    Offset = offset;
	    }

	    public string FileName;
        public long FileSize;
        public long Offset;
    }

    private List<TarArchiveEntry> entries;
    private Stream backend;

    public void WriteEntry(string filename, byte[] buffer)
    {
	    backend.Position = backend.Length;

        //Write header
        TarHeader header = new TarHeader(filename, buffer.Length);
        byte[] serialize = header.Serialize();
        backend.Write(serialize);

        //Remember Entry
        TarArchiveEntry entry = new TarArchiveEntry(filename, buffer.Length, backend.Position);
        
        //Write actual data
        backend.Write(buffer, 0, buffer.Length);
        
        //Write stuffing
        long stuffingLength64 = header.GetSizeOnTape();
        stuffingLength64 -= header.Size;
        if (stuffingLength64 > 0)
        {
            byte[] stuffingBuffer = new byte[stuffingLength64];
            backend.Write(stuffingBuffer, 0, (int)stuffingLength64);
        }
        backend.Flush();
        entries.Add(entry);
    }

    public void WriteEntry(string filename, Stream source)
    {
	    backend.Position = backend.Length;

	    //Write header
	    TarHeader header = new TarHeader(filename, source.Length);
	    byte[] serialize = header.Serialize();
	    backend.Write(serialize);

	    //Remember Entry
	    TarArchiveEntry entry = new TarArchiveEntry(filename, source.Length, backend.Position);

	    //Write actual data
	    source.CopyTo(backend);

	    //Write stuffing
	    long stuffingLength64 = header.GetSizeOnTape();
	    stuffingLength64 -= header.Size;
	    if (stuffingLength64 > 0)
	    {
		    byte[] stuffingBuffer = new byte[stuffingLength64];
		    backend.Write(stuffingBuffer, 0, (int)stuffingLength64);
	    }
	    backend.Flush();
	    entries.Add(entry);
    }

	public void Dispose()
    {
        backend.Close();
        backend.Dispose();
    }

    public async ValueTask DisposeAsync()
    {
        backend.Close();
        await backend.DisposeAsync();
    }

    public bool HasEntry(string filename)
    {
	    if (entries == null)
		    return false;
	    if (entries.Count == 0)
		    return false;

	    foreach (TarArchiveEntry entry in entries)
	    {
		    if (entry.FileName.Equals(filename))
			    return true;
	    }

	    return false;
    }

    public byte[] ReadEntry(string filename)
    {
		if (entries == null)
			return null;
		if (entries.Count == 0)
			return null;

		foreach (TarArchiveEntry entry in entries)
		{
			if (entry.FileName.Equals(filename))
			{
				backend.Position = entry.Offset;
				return backend.ReadBytes(entry.FileSize);
			}
		}

		return null;
	}
}
