using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ComSurrogateScanningLib;
using Google.Protobuf;
using Google.Protobuf.Collections;
using Grpc.Core;
using Grpc.Core.Utils;
using Rpcscanning;
using ScanningRpcService;

namespace RpcScannerServer
{
    public class RpcScannerServerImplementation : RpcScanner.RpcScannerBase, _IScannerEvents, IDisposable
    {
        
        private readonly ObjectFactory _objectFactory = new ObjectFactory();
        private IsisScanner _scanner;

        private IServerStreamWriter<RpcMessage> _rpcMessagesStream;

        private int _sessionDocumentScannedCounter = 0;
        private bool _sessionCancelled = false;
        private bool _serverConnected;
        private IConnectionPoint _connectionPoint;
        private int _cookie;
        private DataProtectionApi _dataProtectionApi = new DataProtectionApi();

        private string ComputeHash(byte[] bytes)
        {
            using (var sha256 = new SHA256CryptoServiceProvider())
            {
                return Convert.ToBase64String(sha256.ComputeHash(bytes));
            }
        }

        private void WriteRpcMessage(RpcMessage rpcMessage)
        {
            Retry.Do( () => { _rpcMessagesStream.WriteAsync(rpcMessage); }, TimeSpan.FromMilliseconds(300), 10);
        }
        private void StartEventSink()
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(StartEventSink)}()");
            
            var _connectionPointContainer = (IConnectionPointContainer)_scanner;
            _serverConnected = true;
            var eventsInterfaceId = typeof(_IScannerEvents).GUID;
            _connectionPointContainer.FindConnectionPoint(ref eventsInterfaceId, out _connectionPoint);
            _connectionPoint.Advise(this, out _cookie);
        }

        private void StopEventSink()
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(StopEventSink)}()");
            if (!_serverConnected)
            {
                return;
            }

            _connectionPoint.Unadvise(_cookie);
            _connectionPoint = null;
            _serverConnected = false;
        }

        public override Task<Empty> InitializeScanner(ScannerConfiguration request, ServerCallContext context)
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(InitializeScanner)}()");
            
            try
            {
                _scanner = _objectFactory.CreateScannerByDriverDescription(request.DriverDescription, request.ConfigFile);
                StartEventSink();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                WriteRpcMessage(new RpcMessage()
                {
                    EventType = EventType.Exception,
                    ErrorCode = -1,
                    ErrorMessage = e.Message
                });
            }
            return Task.FromResult(new Empty());
        }

        public override Task<Empty> ReleaseScanner(Empty request, ServerCallContext context)
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(ReleaseScanner)}()");
            StopEventSink();
            _scanner?.ReleaseScanner();
            return Task.FromResult(new Empty());
        }

        public override async Task Scan(Empty request, IServerStreamWriter<RpcMessage> responseStream, ServerCallContext context)
        {

            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(Scan)}()");
            _sessionDocumentScannedCounter = 0;
            _sessionCancelled = false;
            _rpcMessagesStream = responseStream;
            try
            {
                _scanner.ScanToByteArrays(0);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                WriteRpcMessage(new RpcMessage()
                {
                    EventType = EventType.Exception,
                    ErrorCode = -1,
                    ErrorMessage = e.Message
                });
            }
        }




        public override async Task BatchAborted(Empty request, IServerStreamWriter<Empty> responseStream, ServerCallContext context)
        {
            
        }


        public override Task<Empty> ConfigureViaGui(Hwnd window, ServerCallContext context)
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(ConfigureViaGui)}(hwnd:{window.Handle:X})");
            _scanner.ConfigureViaGui(0);
            return Task.FromResult(new Empty());
        }

        public override Task<ScannerModelNames> GetConnectedScanners(Empty _, ServerCallContext context)
        {
            var systemInfo = _objectFactory.CreateSystemInfo();
            var driverDescriptions = new RepeatedField<string>();
            var connected = systemInfo.GetConnectedScanners();
            if (connected != null)
            {
                driverDescriptions.AddRange(connected.OfType<string>());
            }

            return Task.FromResult(new ScannerModelNames() { DriverDescriptions = { driverDescriptions } });
        }

        public override Task<ScannerModelNames> GetInstalledScanners(Empty _, ServerCallContext context)
        {
            var systemInfo = _objectFactory.CreateSystemInfo();
            var driverDescriptions = new RepeatedField<string>();
            var installed = systemInfo.GetInstalledScanners();
            if (installed != null)
            {
                driverDescriptions.AddRange(installed.OfType<string>());
            }
            return Task.FromResult(new ScannerModelNames() { DriverDescriptions = { driverDescriptions } });
        }

        public override Task<Empty> StopScanning(Empty _, ServerCallContext context)
        {
            _sessionCancelled = true;
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: StopScanning was called");
            return Task.FromResult(new Empty());
        }

        #region _IScannerEvents implementation

        public void DocumentScanned(string Filename1, string Filename2, bool AreDeskewed, out bool Cancel)
        {
            
            Cancel = true;
        }

        public void FeederEmpty()
        {
                Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: received {nameof(FeederEmpty)} event from COM server, {_sessionDocumentScannedCounter} documents scanned");
                Console.WriteLine("Writing to stream");
                WriteRpcMessage(new RpcMessage() {EventType = EventType.FeederEmpty});
                Console.WriteLine("Written to stream");
        }

        public void BatchAborted()
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: received {nameof(BatchAborted)} event from COM server");
            
        }

        public void ScanningError(int ErrorCode, string ErrorText)
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: received {nameof(ScanningError)}({ErrorCode}, {ErrorText}) from COM server");
            WriteRpcMessage(new RpcMessage() { EventType = EventType.Exception, ErrorCode = ErrorCode, ErrorMessage = ErrorText });
        }

        public void NoScannerDetected()
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: {nameof(NoScannerDetected)}()");
            
        }

        public void DocumentScannedToByteArrays(Array page1, Array page2, bool AreDeskewed, ref bool Cancel)
        {
            Console.WriteLine($"{nameof(RpcScannerServerImplementation)}: received {nameof(DocumentScannedToByteArrays)}({page1.Length} bytes,{page2.Length} bytes, {AreDeskewed}, {Cancel}) event from COM server");
            WriteRpcMessage(new RpcMessage()
            {
                EventType = EventType.NewScannedDocument,
                Document = new Document()
                {
                    FrontImage = ByteString.CopyFrom(page1.OfType<byte>().ToArray()),
                    BackImage = ByteString.CopyFrom(page2.OfType<byte>().ToArray()),
                    AreDeskewed = AreDeskewed,
                    Cancel = Cancel,
                    FrontImageHash = _dataProtectionApi.Encrypt(ComputeHash(page1.OfType<byte>().ToArray())),
                    BackImageHash = _dataProtectionApi.Encrypt(ComputeHash(page2.OfType<byte>().ToArray()))
                }
            });
            _sessionDocumentScannedCounter++;
            Cancel = _sessionCancelled;
        }
        #endregion

        public void Dispose()
        {
            _scanner?.ReleaseScanner();
        }
    }
}

