using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Ionic.Zlib;
using Substrate.Nbt;

namespace Substrate.Core
{
    public enum CompressionType
    {
        None,
        Zlib,
        Deflate,
        GZip,
    }

    public class NBTFile
    {
        private string _filename;

        public NBTFile (string path)
        {
            _filename = path;
        }

        public string FileName
        {
            get { return _filename; }
            protected set { _filename = value; }
        }

        public bool Exists ()
        {
            return File.Exists(_filename);
        }

        public void Delete ()
        {
            File.Delete(_filename);
        }

        public int GetModifiedTime ()
        {
            return Timestamp(File.GetLastWriteTime(_filename));
        }

        public Stream GetDataInputStream ()
        {
            return GetDataInputStream(CompressionType.GZip);
        }

        public virtual Stream GetDataInputStream (CompressionType compression)
        {
            try {
                FileStream fstr = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

                long length = fstr.Seek(0, SeekOrigin.End);
                fstr.Seek(0, SeekOrigin.Begin);

                byte[] data = new byte[length];
                fstr.Read(data, 0, data.Length);

                fstr.Close();

                switch (compression) {
                    case CompressionType.None:
                        return new MemoryStream(data);
                    case CompressionType.GZip:
                        return new GZipStream(new MemoryStream(data), CompressionMode.Decompress);
                    case CompressionType.Zlib:
                        return new ZlibStream(new MemoryStream(data), CompressionMode.Decompress);
                    case CompressionType.Deflate:
                        return new DeflateStream(new MemoryStream(data), CompressionMode.Decompress);
                    default:
                        throw new ArgumentException("Invalid CompressionType specified", "compression");
                }
            }
            catch (Exception ex) {
                throw new NbtIOException("Failed to open compressed NBT data stream for input.", ex);
            }
        }

        public Stream GetDataOutputStream ()
        {
            return GetDataOutputStream(CompressionType.GZip);
        }

        public virtual Stream GetDataOutputStream (CompressionType compression)
        {
            try {
                switch (compression) {
                    case CompressionType.None:
                        return new NBTBuffer(this);
                    case CompressionType.GZip:
                        return new GZipStream(new NBTBuffer(this), CompressionMode.Compress);
                    case CompressionType.Zlib:
                        return new ZlibStream(new NBTBuffer(this), CompressionMode.Compress);
                    case CompressionType.Deflate:
                        return new DeflateStream(new NBTBuffer(this), CompressionMode.Compress);
                    default:
                        throw new ArgumentException("Invalid CompressionType specified", "compression");
                }
            }
            catch (Exception ex) {
                throw new NbtIOException("Failed to initialize compressed NBT data stream for output.", ex);
            }
        }

        class NBTBuffer : MemoryStream
        {
            private NBTFile file;

            public NBTBuffer (NBTFile c)
                : base(8096)
            {
                this.file = c;
            }

            public override void Close ()
            {
                FileStream fstr;
                try {
                    fstr = new FileStream(file._filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                }
                catch (Exception ex) {
                    throw new NbtIOException("Failed to open NBT data stream for output.", ex);
                }

                try {
                    fstr.Write(this.GetBuffer(), 0, (int)this.Length);
                    fstr.Close();
                }
                catch (Exception ex) {
                    throw new NbtIOException("Failed to write out NBT data stream.", ex);
                }
            }
        }

        private int Timestamp (DateTime time)
        {
            DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return (int)((time - epoch).Ticks / (10000L * 1000L));
        }
    }
}