using System;
using System.IO;

namespace SvdCli.Storage.Implements
{
    public class RawImageStorage : ISvdStorage
    {
        private readonly string _imageFilePath;
        private FileStream _fileStream;
        private bool _sparse;

        public string ProductId { get; } = "Svd RawImage Disk";
        public string ProductVersion { get; } = "1.0";
        public Guid Guid { get; } = Guid.NewGuid();
        public uint BlockSize { get; } = 512;
        public uint MaxTransferLength { get; } = 64 * CapacityUnits.KiloByte;
        public bool SupportTrim { get; }

        public ulong BlockCount { get; }
        public bool WriteProtect { get; }

        public event EventHandler Mounted;

        public RawImageStorage(string imageFilePath, bool readOnly = false)
        {
            _imageFilePath = imageFilePath;
            WriteProtect = readOnly;
            SupportTrim = !readOnly;

            BlockCount = (ulong)new FileInfo(_imageFilePath).Length / BlockSize;
        }

        public void Init()
        {
            if (null != _fileStream) return;

            var access = WriteProtect ? FileAccess.Read : FileAccess.ReadWrite;
            _fileStream = new FileStream(_imageFilePath, FileMode.Open, access, FileShare.None, (int)MaxTransferLength);
            if (false == WriteProtect) _sparse = _fileStream.SetSparse();
        }

        public void Exit()
        {
            if (null == _fileStream) return;

            if (false == WriteProtect)
            {
                lock (_fileStream)
                {
                    _fileStream.Flush(true);
                }
            }

            lock (_fileStream)
            {
                _fileStream.Close();
                _fileStream = null;
            }
        }

        public void ReadBlocks(byte[] buffer, ulong blockIndex, uint blockCount)
        {
            lock (_fileStream)
            {
                _fileStream.Position = (long)(blockIndex * BlockSize);
                var length = (int)(blockCount * BlockSize);
                var offset = 0;
                while (length > 0)
                {
                    var read = _fileStream.Read(buffer, offset, length);
                    length -= read;
                    offset += read;
                }
            }
        }

        public void WriteBlocks(byte[] buffer, ulong blockIndex, uint blockCount)
        {
            lock (_fileStream)
            {
                _fileStream.Position = (long)(blockIndex * BlockSize);
                _fileStream.Write(buffer, 0, (int)(blockCount * BlockSize));
            }
        }

        public int Read(byte[] buffer, long offset, int index, int count)
        {
            lock (_fileStream)
            {
                _fileStream.Position = offset;
                return _fileStream.Read(buffer, index, count);
            }
        }

        public void Write(byte[] buffer, long offset, int index, int count)
        {
            lock (_fileStream)
            {
                _fileStream.Position = offset;
                _fileStream.Write(buffer, index, count);
            }
        }

        public void Flush()
        {
            lock (_fileStream) _fileStream.Flush(true);
        }

        public void Trim(params TrimDescriptor[] descriptors)
        {
            lock (_fileStream)
            {
                foreach (var trim in descriptors)
                {
                    if (_sparse && _fileStream.Trim(trim.Offset, trim.Length)) continue;

                    _fileStream.Position = trim.Offset;

                    var totalLength = (int)trim.Length;
                    var buffer = new byte[Math.Min(64 * 1024, totalLength)];

                    while (0 < totalLength)
                    {
                        _fileStream.Write(buffer, 0, buffer.Length);
                        totalLength -= buffer.Length;
                    }
                }
            }
        }

        public void FireMountedEvent()
        {
            Mounted?.Invoke(this, EventArgs.Empty);
        }
    }
}