using System;
using System.Linq;
using Spd;
using Spd.Interop;
using SvdCli.Storage;

namespace SvdCli.DriverAdapt
{
    public class VirtualDrive
    {
        private readonly ISvdStorage _storage;
        private readonly SpdImplement _spdImplement;
        private readonly StorageUnitHost _host;

        public long Size { get; }

        public VirtualDrive(ISvdStorage storage)
        {
            _storage = storage;
            _spdImplement = new SpdImplement(_storage);
            _host = new StorageUnitHost(_spdImplement)
            {
                ProductId = storage.ProductId,
                ProductRevisionLevel = storage.ProductVersion,
                WriteProtected = storage.WriteProtect,
                UnmapSupported = storage.SupportTrim,
                BlockCount = storage.BlockCount,
                BlockLength = storage.BlockSize,
                MaxTransferLength = storage.MaxTransferLength,
                CacheSupported = true,
                EjectDisabled = false,
                Guid = Guid.NewGuid(),
            };

            Size = storage.BlockSize * storage.BlockSize;
        }

        public void Attach()
        {
            _storage.Init();
            _host.Start();
            _storage.FireMountedEvent();
        }

        public void Detach()
        {
            _host.Shutdown();
            _storage.Exit();
        }

        private class SpdImplement : StorageUnitBase
        {
            private readonly ISvdStorage _storage;

            public SpdImplement(ISvdStorage storage) => _storage = storage;

            public override void Read(byte[] buffer, ulong blockAddress, uint blockCount, bool flush, ref StorageUnitStatus status)
            {
                if (flush) _storage.Flush();
                _storage.ReadBlocks(buffer, blockAddress, blockCount);
            }

            public override void Write(byte[] buffer, ulong blockAddress, uint blockCount, bool flush, ref StorageUnitStatus status)
            {
                _storage.WriteBlocks(buffer, blockAddress, blockCount);
                if (flush) _storage.Flush();
            }

            public override void Flush(ulong blockAddress, uint blockCount, ref StorageUnitStatus status)
            {
                _storage.Flush();
            }

            public override void Unmap(UnmapDescriptor[] descriptors, ref StorageUnitStatus status)
            {
                var trimDescriptors = descriptors.Select(p => new TrimDescriptor((long)p.BlockAddress * _storage.BlockSize, p.BlockCount * _storage.BlockSize)).ToArray();
                _storage.Trim(trimDescriptors);
            }
        }
    }
}