﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using UnityEditor;

[Serializable]
abstract public class Archive
{
    abstract public int TotalSize { get; }
    abstract public bool IsEmpty { get; }
    /// <summary>first byte position of this archive. The start of the header.</summary>
    public int startOffset;
    /// <summary>number of indexes</summary>
    public int count;
    /// <summary>offset to each block</summary>
    protected int[] offsets;
    /// <summary>index into each offset</summary>
    public int this[int i] { get { return offsets[i]; } }

    abstract public void WriteHeader();
}

[Serializable]
public class ArchiveIndex : Archive
{
    /// <summary>byte size of each block</summary>
    public int[] sizes;
    /// <summary>archive of each block. Null if not a archive (Raw Data).</summary>
    public Archive[] archives;
    /// <summary>Returns true if there are no blocks in this archive chunk</summary>
    public override bool IsEmpty { get { return count == 0; } }
    /// <summary>Returns the total size of the archive</summary>
    public override int TotalSize { get { return offsets[offsets.Length - 1] - startOffset; } }

    public ArchiveIndex(int offset, int count)
    {
        startOffset = offset;
        this.count = count;
        this.archives = new Archive[count];
        this.sizes = new int[count];
        offsets = new int[count + 1];
        int headerSize = (offsets.Length * 4) + 4;
        headerSize = MathUtilsEx.CeilDivide(headerSize, 8) * 8;
        offsets[0] = startOffset + headerSize;
        for (int i = 1; i < offsets.Length; i++)
        {
            offsets[i] = offsets[0];
        }
    }

    public void SetBlock(int blockIndex, Archive archive)
    {
        SetBlock(blockIndex, archive, archive.TotalSize);
    }

    /// <summary>
    /// Sets a block to another archive with the byte size of the block
    /// </summary>
    /// <param name="blockIndex">The block to set</param>
    /// <param name="archive">archive associated with this block. None if null.</param>
    /// <param name="size">byte size of block</param>
    public void SetBlock(int blockIndex, Archive archive, int size)
    {
        archives[blockIndex] = archive;
        sizes[blockIndex] = size;
        offsets[blockIndex + 1] = offsets[blockIndex] + size;
    }

    public override void WriteHeader()
    {
        IOUtils.writeIndex = startOffset;
        IOUtils.WriteInt32(count);
        for (int i = 0; i < offsets.Length; i++)
        {
            IOUtils.WriteInt32(offsets[i] - startOffset);
        }
    }

    /// <summary>
    /// Creates an ArchiveIndex by reading from the IOUtils.readData at offset
    /// </summary>
    /// <param name="offset">to the start of archive</param>
    public static ArchiveIndex FromReadBuffer(int offset)
    {
        IOUtils.readByteIndex = offset;
        int count = IOUtils.ReadInt32();
        ArchiveIndex archive = new ArchiveIndex(offset, count);
        if (count == 0)
            return archive;
        for (int i = 0; i < count + 1; i++)
            archive.offsets[i] = offset + IOUtils.ReadInt32();
        for (int i = 0; i < count; i++)
            archive.sizes[i] = archive.offsets[i + 1] - archive.offsets[i];
        return archive;
    }

}

[Serializable]
public class ArchiveDataSet : Archive
{
    /// <summary>size of each entry</summary>
    public int size;
    /// <summary>Returns true if there are no entries in this archive chunk</summary>
    public override bool IsEmpty { get { return size == 0 || count == 0; } }
    /// <summary>Returns the total size of the archive</summary>
    public override int TotalSize
    {
        get
        {
            int tSize = (size * count) + 8;
            return MathUtilsEx.CeilDivide(tSize, 8) * 8;
        }
    }

    public ArchiveDataSet(int offset, int count, int size)
    {
        startOffset = offset;
        this.count = count;
        if (count == 0)
            size = 0;
        this.size = size;
        offsets = new int[count];
        if (count > 0)
        {
            offsets[0] = startOffset + 8; //8 bytes ahead of start for header
            for (int i = 1; i < count; i++)
            {
                offsets[i] = offsets[0] + (i * size);
            }
        }
    }

    /// <summary>
    /// Convenience function to creates a Archive DataSet by reading from the IOUtils.readBuffer at offset
    /// </summary>
    /// <param name="offset">to the start of DataSet chunk</param>
    public static ArchiveDataSet FromReadBuffer(int offset)
    {
        IOUtils.readByteIndex = offset;
        int size = IOUtils.ReadInt32();
        if (size == 0)
            return new ArchiveDataSet(offset, 0, 0);
        int count = IOUtils.ReadInt32();
        return new ArchiveDataSet(offset, count, size);
    }

    /// <summary>
    /// Convenience function to create a string DataSet
    /// </summary>
    public static ArchiveDataSet FromString(int offset, string s)
    {
        return new ArchiveDataSet(offset, 1, s.Length + 1);
    }

    public override void WriteHeader()
    {
        IOUtils.writeIndex = startOffset;
        IOUtils.WriteInt32(size);
        IOUtils.WriteInt32(count);
    }

    /// <summary>
    /// Assumes this DataSet offsets to a string and returns it from the IOUtils.readBuffer
    /// </summary>
    public string AsString()
    {
        IOUtils.readByteIndex = offsets[0];
        return IOUtils.ReadString(size - 1); //-1 to not read the zero terminated char
    }
}
