using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using BallotTemplates;
using EHF;
using IniParser.Model;
using InterpreterLibrary.Models;
using InterpreterLibrary.Processing;
using Scan.ReadSoftEmulation;
using Tesseract;
using Valg.EVA.Skanning.Scan.ReadSoftEmulation.ScannerAccess;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using CognitiveToolkit;
using CognitiveToolkit.OpenCvSharp;
using CrossCutting;
using CrossCutting.Generic;
using InterpreterLibrary.Interpreters;
using OpenCvSharp;
using Valg.EVA.Skanning.Scan.ReadSoftEmulation.BallotInterpretation;
using Valg.EVA.Skanning.ServicesModel.Internal.SystemSettings;
using Point = OpenCvSharp.Point;
using Rect = OpenCvSharp.Rect;

namespace Valg.EVA.Skanning.Scan.ReadSoftEmulation
{
    public class ScanApp : IEHF_IScanApp
    {
        private BlockingCollection<object> _interpretationQueue;
        private BallotInterpreters _ballotInterpreters;
        private ManualResetEvent _interpretersCreated = new ManualResetEvent(false);

        private readonly float _minimumTesseractConfidence;
        private BlockingCollection<(int order,byte[] imageBytes1, byte[] imageBytes2, bool areDeskewed)> _formsToProcessQueue = new BlockingCollection<(int,byte[], byte[], bool)>();
        private class TemplateWithFrequency
        {
            public BallotTemplate Template;
            public int Frequency;
            public override string ToString()
            {
                return $"({Template.TypeId}, {Frequency})";
            }
        }


        private readonly IDictionary<short, string> _returnValues = new Dictionary<short, string>();
        
        private readonly IDictionary<SubscribableEvent, (object HandlerObject, MethodInfo MethodInfo)> _subscriptions = new Dictionary<SubscribableEvent, (object, MethodInfo)>();
        
        private ICollection<TemplateWithFrequency> _templates;

        private string _currentJobFilename = "";
        
        private string _templateDirectoryName;
        private readonly ScannerConfig _scannerConfig;
        private ReadSoftEmulation.Form _form;
        private readonly TesseractEngine _ocr;
        private readonly INumberInterpreter<Mat, OpenCvSharp.Rect> _numberInterpreter;
        private readonly IWriteInInterpreter<Mat, Rect, Point> _writeInInterpreter;
        private readonly ICheckboxInterpreter<Mat> _checkBoxInterpreter;
        private ISystemSettingsProvider _systemSettingsProvider;
        private Lazy<ScannerAgent> _scannerAgent;
        private bool _isCurrentlyProcessingForm;
        private int _ballotsScannedCounter = 0;


        public ScanApp(ScannerConfig scannerConfig, float minimumTesseractConfidence = 0.7f, bool interpretInParallel=true)
        {
            _minimumTesseractConfidence = minimumTesseractConfidence;
            _returnValues[0] = ReturnValue.Blank;
            _scannerConfig = scannerConfig;
            _scannerAgent = new Lazy<ScannerAgent>(CreateAgent);
            ThreadPool.QueueUserWorkItem(state =>
            {
                _ballotInterpreters = new BallotInterpretation.BallotInterpreters();
                _interpretersCreated.Set();
            });

            _interpretInParallel = interpretInParallel;
            if (!_interpretInParallel)
            {
                _ocr = new TesseractEngine(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tessdata"), "nor"); 
                _numberInterpreter = new NumberInterpreter(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data", "Models", "MNISTConvolution.model"));
                _writeInInterpreter = new WriteInInterpreter(new SingleModelCharacterClassifier(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data", "Models", "ConvNet_Vdir_39.dnn")));
                _checkBoxInterpreter = new CheckboxClassifier(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data", "Models", "ConvNet_checkbox_3state_Vdir.dnn"));
                ProcessFormsInQueue();
            }
        }

        private async Task ProcessFormsInQueue()
        {
            while (true)
            {
                if (_formsToProcessQueue.Any())
                {
                    var form = _formsToProcessQueue.Take();
                    _isCurrentlyProcessingForm = true;
                    CreateForm(form.Item1, form.Item2, form.Item3, form.Item4);
                    RaiseFormScanEnd();
                }
                await Task.Delay(10);
            }
        }


        
        
        
        
        
        
        
        
        

        private readonly Dictionary<string, object> _sharedObjects = new Dictionary<string, object>();
        void IEHC_IApplication.CloseApp()
        {
            
        }

        string IEHF_IScanApp.GetSharedVariable(string VariableName)
        {
            throw new NotSupportedException();
        }

        object IEHF_IScanApp.GetSharedObject(string ObjectName, bool ReleaseObject)
        {
            return _sharedObjects[ObjectName];
        }

        void IEHF_IScanApp.SetSharedObject(string ObjectName, object SharedObject)
        {
            _sharedObjects[ObjectName] = SharedObject;
        }

        void IEHF_IScanApp.SetSharedVariable(string VariableName, string Value)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.LogSettings(ehLogDevice LogDevice, ehLogSettings LogOptions)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.SetReturnValue(short ArgNo, string ArgValue)
        {
            _returnValues[ArgNo] = ArgValue;
        }

        object IEHF_IScanApp.GetClassicObject()
        {
            throw new NotSupportedException();
        }

        int IEHF_IScanApp.Subscribe(object HandlerObject, string EventName, string HandlerFunction)
        {
            if (HandlerObject == null)
            {
                return (int)EHF.ehReturnValue.EV_ERROR;
            }
            var evt = SubscribableEvent.SupportedEvents.FirstOrDefault(e => e.ToString().Equals(EventName));
            if (evt == null)
            {
                
                return (int)EHF.ehReturnValue.EV_ERROR;
                
            }
            var methodInfo = HandlerObject.GetType().GetMethod(HandlerFunction);
            if (methodInfo is null || methodInfo.MemberType != MemberTypes.Method)
            {
                return (int)EHF.ehReturnValue.EV_ERROR;
            }

            _subscriptions[evt] = (HandlerObject, methodInfo);
            return (int)EHF.ehReturnValue.EV_OK;
        }

        int IEHF_IScanApp.SubscribeToObject(object HandlerObject, string EventName, string ObjectName, string HandlerFunction)
        {
            throw new NotSupportedException();

        }

        void IEHF_IScanApp.SetEventServer(object EventServer)
        {
            throw new NotSupportedException();
        }

        string IEHF_IScanApp.DBName
        {
            get => throw new NotSupportedException();
            set => throw new NotSupportedException();
        }

        short IEHF_IScanApp.AddMenuItem(ehUserDefinedMenu MenuCmdNo, string MenuText, string Accelerator, bool Visible, bool Enabled)
        {
            throw new NotSupportedException();
        }

        short IEHF_IScanApp.DeleteMenuItem(ehUserDefinedMenu MenuCmdNo)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.InitUserDefinedMenu(string MenuText)
        {
            throw new NotSupportedException();
        }

        string IEHF_IScanApp.GetMenuItemAccelerator(int MenuCmdNo)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.SetMenuItemAccelerator(int MenuCmdNo, string Acc)
        {
            throw new NotSupportedException();
        }

        string IEHF_IScanApp.GetMenuItemText(int MenuCmdNo)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.SetMenuItemText(int MenuCmdNo, string Text)
        {
            throw new NotSupportedException();
        }

        bool IEHF_IScanApp.GetMenuItemEnabled(int MenuCmdNo)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.SetMenuItemEnabled(int MenuCmdNo, bool Enabled)
        {
            throw new NotSupportedException();
        }

        bool IEHF_IScanApp.GetMenuItemVisible(int MenuCmdNo)
        {
            throw new NotSupportedException();
        }

        void IEHF_IScanApp.SetMenuItemVisible(int MenuCmdNo, bool Visible)
        {
            throw new NotSupportedException();
        }

        public short LoadJob(string JobFileName)
        {
            _templateDirectoryName = System.IO.Path.GetDirectoryName(JobFileName);
            
            ReadTemplates();
            return (short)EHF.ehReturnValue.EV_OK;


        }

        public short StartJob()
        {
            _formsToProcessQueue = new BlockingCollection<(int,byte[], byte[], bool)>();

            System.Diagnostics.Debug.Print(nameof(StartJob));
            
            
            
            
            if (_templates == null || _templates.Count == 0)
            {
                return (short)EHF.ehReturnValue.EV_ERROR;
            }
            RaiseJobStarted();
            Task.Run(() => StartScanning());

            return (short)EHF.ehReturnValue.EV_OK;

        }

        public short StopJob()
        {
            
            _scannerAgent.Value.AbortScanning();
            ((IEHF_IScanApp)this).SetReturnValue(0, ReturnValue.Stop);
            RaiseJobStopped();
            return (short)EHF.ehReturnValue.EV_OK;
            
        }

        void IEHF_IScanApp.CloseApp()
        {
            
        }

        string IEHF_IApplication.GetSharedVariable(string VariableName)
        {
            throw new NotImplementedException();
        }

        object IEHF_IApplication.GetSharedObject(string ObjectName, bool ReleaseObject)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetSharedObject(string ObjectName, object SharedObject)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetSharedVariable(string VariableName, string Value)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.LogSettings(ehLogDevice LogDevice, ehLogSettings LogOptions)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetReturnValue(short ArgNo, string ArgValue)
        {
            throw new NotImplementedException();
        }

        object IEHF_IApplication.GetClassicObject()
        {
            throw new NotImplementedException();
        }

        int IEHF_IApplication.Subscribe(object HandlerObject, string EventName, string HandlerFunction)
        {
            throw new NotImplementedException();
        }

        int IEHF_IApplication.SubscribeToObject(object HandlerObject, string EventName, string ObjectName, string HandlerFunction)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetEventServer(object EventServer)
        {
            throw new NotImplementedException();
        }

        string IEHF_IApplication.DBName
        {
            get => throw new NotImplementedException();
            set => throw new NotImplementedException();
        }

        short IEHF_IApplication.AddMenuItem(ehUserDefinedMenu MenuCmdNo, string MenuText, string Accelerator, bool Visible, bool Enabled)
        {
            throw new NotImplementedException();
        }

        short IEHF_IApplication.DeleteMenuItem(ehUserDefinedMenu MenuCmdNo)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.InitUserDefinedMenu(string MenuText)
        {
            throw new NotImplementedException();
        }

        string IEHF_IApplication.GetMenuItemAccelerator(int MenuCmdNo)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetMenuItemAccelerator(int MenuCmdNo, string Acc)
        {
            throw new NotImplementedException();
        }

        string IEHF_IApplication.GetMenuItemText(int MenuCmdNo)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetMenuItemText(int MenuCmdNo, string Text)
        {
            throw new NotImplementedException();
        }

        bool IEHF_IApplication.GetMenuItemEnabled(int MenuCmdNo)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetMenuItemEnabled(int MenuCmdNo, bool Enabled)
        {
            throw new NotImplementedException();
        }

        bool IEHF_IApplication.GetMenuItemVisible(int MenuCmdNo)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.SetMenuItemVisible(int MenuCmdNo, bool Visible)
        {
            throw new NotImplementedException();
        }

        void IEHF_IApplication.CloseApp()
        {
            throw new NotImplementedException();
        }

        string IEHC_IApplication.GetSharedVariable(string VariableName)
        {
            throw new NotImplementedException();
        }

        object IEHC_IApplication.GetSharedObject(string ObjectName, bool ReleaseObject)
        {
            throw new NotImplementedException();
        }

        void IEHC_IApplication.SetSharedObject(string ObjectName, object SharedObject)
        {
            throw new NotImplementedException();
        }

        void IEHC_IApplication.SetSharedVariable(string VariableName, string Value)
        {
            throw new NotImplementedException();
        }

        void IEHC_IApplication.LogSettings(ehLogDevice LogDevice, ehLogSettings LogOptions)
        {
            throw new NotImplementedException();
        }

        void IEHC_IApplication.SetReturnValue(short ArgNo, string ArgValue)
        {
            throw new NotImplementedException();
        }

        object IEHC_IApplication.GetClassicObject()
        {
            throw new NotImplementedException();
        }

        int IEHC_IApplication.Subscribe(object HandlerObject, string EventName, string HandlerFunction)
        {
            return ((IEHF_IScanApp)this).Subscribe(HandlerObject, EventName, HandlerFunction);
        }

        int IEHC_IApplication.SubscribeToObject(object HandlerObject, string EventName, string ObjectName, string HandlerFunction)
        {
            throw new NotImplementedException();
        }

        void IEHC_IApplication.SetEventServer(object EventServer)
        {
            throw new NotImplementedException();
        }

        string IEHC_IApplication.DBName
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        string IEHC_IApplication.Name
        {
            get { throw new NotImplementedException(); }
        }

        string IEHF_IScanApp.Version
        {
            get { throw new NotImplementedException(); }
        }

        string IEHF_IScanApp.NextJob
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        string IEHF_IScanApp.Name
        {
            get { throw new NotImplementedException(); }
        }

        string IEHF_IApplication.Version
        {
            get { throw new NotImplementedException(); }
        }

        string IEHF_IApplication.NextJob
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        string IEHF_IApplication.Info
        {
            get { throw new NotImplementedException(); }
        }

        IEHF_IField IEHF_IScanApp.Field
        {
            get { throw new NotImplementedException(); }
        }

        IEHF_IFieldDef IEHF_IScanApp.FieldDef
        {
            get { throw new NotImplementedException(); }
        }

        IEHF_IForm IEHF_IScanApp.Form => _form;

        IEHF_IFormDef IEHF_IScanApp.FormDef
        {
            get { throw new NotImplementedException(); }
        }

        IEHF_IJob IEHF_IScanApp.Job
        {
            get { throw new NotImplementedException(); }
        }

        IEHF_ISet IEHF_IScanApp.Set
        {
            get { throw new NotImplementedException(); }
        }

        IEHF_ISetDef IEHF_IScanApp.SetDef
        {
            get { throw new NotImplementedException(); }
        }

        IEHC_IBatch IEHF_IScanApp.Batch => throw new NotImplementedException();

        IEHC_IBatchDef IEHF_IScanApp.BatchDef => throw new NotImplementedException();

        string IEHF_IScanApp.Info => throw new NotImplementedException();

        IEHF_IField IEHF_IApplication.Field => throw new NotImplementedException();

        IEHF_IFieldDef IEHF_IApplication.FieldDef => throw new NotImplementedException();

        IEHF_IForm IEHF_IApplication.Form => _form;

        IEHF_IFormDef IEHF_IApplication.FormDef => throw new NotImplementedException();

        IEHF_IJob IEHF_IApplication.Job => throw new NotImplementedException();

        IEHF_ISet IEHF_IApplication.Set => throw new NotImplementedException();


        IEHF_ISetDef IEHF_IApplication.SetDef => throw new NotImplementedException();

        IEHC_IBatch IEHF_IApplication.Batch => throw new NotImplementedException();

        IEHC_IBatchDef IEHF_IApplication.BatchDef => throw new NotImplementedException();

        string IEHF_IApplication.Name => throw new NotImplementedException();

        string IEHC_IApplication.Version => throw new NotImplementedException();

        string IEHC_IApplication.NextJob
        {
            get => throw new NotImplementedException();
            set => throw new NotImplementedException();
        }

        string IEHC_IApplication.Info => throw new NotImplementedException();


        private ehReturnValue RaiseJobStarted()
        {
            var evt = SubscribableEvent.JobStarted;
            if (!_subscriptions.ContainsKey(evt))
            {
                return ehReturnValue.EV_OK;
            }
            var result = _subscriptions[evt].MethodInfo.Invoke(_subscriptions[evt].HandlerObject, new object[] { });
            try
            {
                return (ehReturnValue)result;
            }
            catch (InvalidCastException)
            {
                return ehReturnValue.EV_OK;
            }

        }

        private ehReturnValue RaiseJobStopped()
        {
            var evt = SubscribableEvent.JobStopped;
            if (!_subscriptions.ContainsKey(evt))
            {
                return ehReturnValue.EV_OK;
            }
            var result = _subscriptions[evt].MethodInfo.Invoke(_subscriptions[evt].HandlerObject, new object[] { });
            try
            {
                return (ehReturnValue)result;
            }
            catch (InvalidCastException)
            {
                return ehReturnValue.EV_OK;
            }
        }


        private ehReturnValue RaiseFormScanEnd()
        {
            var evt = SubscribableEvent.FormScanEnd;
            if (!_subscriptions.ContainsKey(evt))
            {
                return ehReturnValue.EV_OK;
            }
            var result = _subscriptions[evt].MethodInfo.Invoke(_subscriptions[evt].HandlerObject, new object[] { });
            _isCurrentlyProcessingForm = false;
            try
            {
                return (ehReturnValue)result;
            }
            catch (InvalidCastException)
            {
                return ehReturnValue.EV_OK;
            }
        }

        private ehReturnValue RaiseScanBeforeSaveDlg(BeforeSaveDlgSituation situation)
        {
            var evt = SubscribableEvent.ScanBeforeSaveDlg;
            if (!_subscriptions.ContainsKey(evt))
            {
                return ehReturnValue.EV_OK;
            }
            var result = _subscriptions[evt].MethodInfo.Invoke(_subscriptions[evt].HandlerObject, new object[] { Convert.ToInt32(situation) });
            try
            {
                return (ehReturnValue)result;
            }
            catch (InvalidCastException)
            {
                return ehReturnValue.EV_OK;
            }
        }

        private ehReturnValue RaiseScanNotifyUser(ErrorSituation situation, string errorInfo)
        {
            var evt = SubscribableEvent.ScanNotifyUser;
            if (!_subscriptions.ContainsKey(evt))
            {
                return ehReturnValue.EV_OK;
            }
            var result = _subscriptions[evt].MethodInfo.Invoke(_subscriptions[evt].HandlerObject, new object[] { Convert.ToInt32(situation), errorInfo });
            try
            {
                return (ehReturnValue)result;
            }
            catch (InvalidCastException)
            {
                return ehReturnValue.EV_OK;
            }
        }

        private ehReturnValue RaiseAppError(int errNo, int errType, int errId, int errIdExt)
        {
            var evt = SubscribableEvent.AppError;
            if (!_subscriptions.ContainsKey(evt))
            {
                return ehReturnValue.EV_OK;
            }
            var result = _subscriptions[evt].MethodInfo.Invoke(_subscriptions[evt].HandlerObject, new object[] { errNo, errType, errId, errIdExt });
            try
            {
                return (ehReturnValue)result;
            }
            catch (InvalidCastException)
            {
                return ehReturnValue.EV_OK;
            }
        }

        private void ReadTemplates()
        {
            if (_templates != null)
                return;
            var templates = new List<TemplateWithFrequency>();
            var files = Directory.GetFiles(_templateDirectoryName, "*.mxml");
            if (!files.Any())
            {
                throw new Exception($"No templates found in {_templateDirectoryName}");
            }
            foreach (var formDefName in files)
            {
                var template = BallotTemplate.Load(formDefName);
                if (template != null)
                {
                    templates.Add(new TemplateWithFrequency { Template = template, Frequency = 0 });
                }
                else
                {
                    templates = null;
                    throw new BallotTemplateNotFoundException(formDefName);
                }
            }

            _templates = templates;
        }

        private void StartScanning()
        {
            if (_interpretInParallel)
            {
                StartParallelScanning();
            }
            else
            {
                StartSingleThreadScanning();
            }
        }

        private void StartSingleThreadScanning()
        {
            
            _feederEmptyReceived = false;
            _ballotsScannedCounter = 0;
            _returnValues[0] = ReturnValue.Cont;
            while (_returnValues[0] == ReturnValue.Cont)
            {
                
                _scanningError = null;

                try
                {
                    if (!_formsToProcessQueue.IsAddingCompleted)
                    {
                        _scannerAgent.Value.StartScanning();
                    }

                    if (_scanningError != null)
                    {
                        break;
                    }
                    if (_feederEmptyReceived)
                    {
                        RaiseScanNotifyUser(ErrorSituation.NoMorePaper, "The automatic document feeder is empty.");
                        break;
                    }
                }
                catch (COMException e)
                {
                    RaiseScanNotifyUser(ErrorSituation.ScannerError, e.Message);
                    break;
                }
            }
        }

        private void StartParallelScanning()
        {
            
            _feederEmptyReceived = false;
            _ballotsScannedCounter = 0;
            _returnValues[0] = ReturnValue.Cont;
            while (_returnValues[0] == ReturnValue.Cont)
            {
                
                _scanningError = null;
                _interpretationQueue = new BlockingCollection<object>();
                try
                {
                    if (!_formsToProcessQueue.IsAddingCompleted)
                    {
                        Task.Run(() => { _ballotInterpreters.Start(_formsToProcessQueue, _interpretationQueue, GetTemplates().ToArray()); });
                        Task.Run(() =>
                            { _scannerAgent.Value.StartScanning(); });
                        StartConsuming();
                    }

                    if (_scanningError != null)
                    {
                        break;
                    }
                    if (_feederEmptyReceived)
                    {
                        RaiseScanNotifyUser(ErrorSituation.NoMorePaper, "The automatic document feeder is empty.");
                        break;
                    }
                    else
                    {
                        Debug.Assert(false);
                    }
                }
                catch (COMException e)
                {
                    RaiseScanNotifyUser(ErrorSituation.ScannerError, e.Message);
                    break;
                }
            }
        }

        private void StartConsuming()
        {
            while (!_interpretationQueue.IsCompleted)
            {
                if (!_interpretationQueue.TryTake(out object item))
                {
                    Thread.Sleep(100);
                    continue;
                }

                if (item is Tuple<int,ScannedBallotPaper, BallotPreprocessorOutput, byte[], byte[]> t)
                {
                    DispatchBallotPaper(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5);
                }
                else if (item is Tuple<int,BallotPreprocessorOutput, byte[], byte[]> ppo)
                {
                    DispatchPreprocessorOutput(ppo.Item1,ppo.Item2, ppo.Item3, ppo.Item4);
                }
                else if (item is Exception ex)
                {
                    RaiseScanNotifyUser(ErrorSituation.ScannerError, ex.Message);
                    break;
                }
            }

            _feederEmptyReceived = true;
        }

        private void DispatchPreprocessorOutput(int order, BallotPreprocessorOutput preprocessorOutput, byte[] frontBytes, byte[] backBytes)
        {

            _form = new Form();
            _form.SetName("");
            _form.ArchiveFrontImageFileName = null; 
            _form.ArchiveBackImageFileName = null; 
            _form.FrontRotatedDegrees = 0;
            _form.BackRotatedDegrees = 0;

            byte[] frontBytesToSave, backBytesToSave;
            if (preprocessorOutput.FrontImage != null && preprocessorOutput.BackImage != null)
            {
                frontBytesToSave = preprocessorOutput.IsReverseOrder ? backBytes : frontBytes;
                backBytesToSave = preprocessorOutput.IsReverseOrder ? frontBytes : backBytes;
            }
            else
            {
                frontBytesToSave = frontBytes;
                backBytesToSave = backBytes;
            }
            _form.ArchiveFrontImageBytes = frontBytesToSave;
            _form.ArchiveBackImageBytes = backBytesToSave;
            _form.ScannedOrder = order;
            RaiseFormScanEnd();
        }

        private void DispatchBallotPaper(int order, ScannedBallotPaper ballot, BallotPreprocessorOutput preprocessorOutput,
            byte[] frontBytes, byte[] backBytes)
        {
            _form = new Form();

            {
                byte[] frontBytesToSave, backBytesToSave;

                RegisterTemplateHit(preprocessorOutput.Template);
                _form.SetName(System.IO.Path.GetFileNameWithoutExtension(preprocessorOutput.Template.FileName));
                _form.ArchiveFrontImageFileName = null; 
                _form.ArchiveBackImageFileName = null; 
                _form.FrontRotatedDegrees = preprocessorOutput.FrontRotationDegrees;
                _form.BackRotatedDegrees = preprocessorOutput.BackRotationDegrees;

                frontBytesToSave = preprocessorOutput.IsReverseOrder ? backBytes : frontBytes;
                backBytesToSave = preprocessorOutput.IsReverseOrder ? frontBytes : backBytes;
                try
                {
                    var mapper = new FieldMapper(ballot.Output, preprocessorOutput.Template, _form, _minimumTesseractConfidence);
                    mapper.MapFields();
                }
                catch (Exception e)
                {
                    RaiseScanNotifyUser(ErrorSituation.DocumentError, e.Message);
                }

                _form.ArchiveFrontImageBytes = frontBytesToSave;
                _form.ArchiveBackImageBytes = backBytesToSave;
                _form.ScannedOrder = order;
                RaiseFormScanEnd();
            }
        }

        private ScannerAgent CreateAgent()
        {
            var ret = ScannerAgent.Create(
                _scannerConfig, 240, 100);
            ret.DocumentScanned += Agent_DocumentScanned;
            ret.ScanningError += Agent_ScanningError;
            ret.FeederEmpty += Agent_FeederEmpty;
            return ret;
        }

        private void Agent_FeederEmpty(object sender, EventArgs e)
        {
            _formsToProcessQueue.CompleteAdding();
            Task.Run(async () =>
            {
                while (!_formsToProcessQueue.IsCompleted || _isCurrentlyProcessingForm)
                {
                    await Task.Delay(10);
                }
                _feederEmptyReceived = true;
            });

        }

        private void Agent_BatchAborted(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }

        private void Agent_DocumentScanned(object sender, DocumentScannedEventArgs e)
        {
            _ballotsScannedCounter++;
            _formsToProcessQueue.Add((_ballotsScannedCounter,e.FrontImageBytes, e.BackImageBytes, e.IsDeskewed));
        }

        private void CreateForm(int order, byte[] frontBytes, byte[] backBytes, bool isDeskewed)
        {
            _form = new Form();
            var tempDir = Path.GetTempPath();
            var templates = GetTemplates();
            using (var preprocessor = new BallotPreprocessor(frontBytes, backBytes,
                _ocr,
                new BallotPreprocessorOptions() { AreDeskewed = isDeskewed, MinimumTypeIdConfidence = _minimumTesseractConfidence },
                templates.ToArray()))
            {
                byte[] frontBytesToSave, backBytesToSave;
                if (preprocessor.Output.Template != null)
                {
                    RegisterTemplateHit(preprocessor.Output.Template);
                    
                    
                    
                    
                    

                    _form.SetName(System.IO.Path.GetFileNameWithoutExtension(preprocessor.Output.Template.FileName));
                    _form.ArchiveFrontImageFileName = null;
                    _form.ArchiveBackImageFileName = null; 
                    _form.FrontRotatedDegrees = preprocessor.Output.FrontRotationDegrees;
                    _form.BackRotatedDegrees = preprocessor.Output.BackRotationDegrees;
                    _form.ScannedOrder = order;

                    frontBytesToSave = preprocessor.Output.IsReverseOrder ? backBytes : frontBytes;
                    backBytesToSave = preprocessor.Output.IsReverseOrder ? frontBytes : backBytes;
                    var ballot = new ScannedBallotPaper(preprocessor.Output, _ocr, _numberInterpreter, _writeInInterpreter, _checkBoxInterpreter);
                    try
                    {
                        ballot.Interpret();
                        var mapper = new FieldMapper(ballot.Output, preprocessor.Output.Template, _form, _minimumTesseractConfidence);
                        mapper.MapFields();
                    }
                    catch (Exception e)
                    {
                        RaiseScanNotifyUser(ErrorSituation.DocumentError, e.Message);
                    }

                }
                else
                {

                    
                    
                    

                    
                    
                    
                    
                    
                    
                    _form.SetName("");
                    _form.ArchiveFrontImageFileName = null; 
                    _form.ArchiveBackImageFileName = null; 
                    _form.FrontRotatedDegrees = 0;
                    _form.BackRotatedDegrees = 0;

                    if (preprocessor.Output.FrontImage != null && preprocessor.Output.BackImage != null)
                    {
                        frontBytesToSave = preprocessor.Output.IsReverseOrder ? backBytes : frontBytes;
                        backBytesToSave = preprocessor.Output.IsReverseOrder ? frontBytes : backBytes;
                    }
                    else
                    {
                        frontBytesToSave = frontBytes;
                        backBytesToSave = backBytes;
                    }
                }
                _form.ArchiveFrontImageBytes = frontBytesToSave;
                _form.ArchiveBackImageBytes = backBytesToSave;
            }
        }



        private void RegisterTemplateHit(BallotTemplate template)
        {
            if (!_templates.Any(e => e.Template.Equals(template)))
            {
                return;
            }
            var entry = _templates.Single(e => e.Template.Equals(template));
            entry.Frequency++;
        }


        private bool _feederEmptyReceived;
        private readonly bool _interpretInParallel;
        private Exception _scanningError;
        private void Agent_ScanningError(object sender, ScanningErrorEventArgs e)
        {
            _scanningError = e.Exception;
            RaiseScanNotifyUser(ErrorSituation.ScannerError, e.Exception.Message);
        }

        private IEnumerable<BallotTemplate> GetTemplates()
        {
            return from entry in _templates.OrderByDescending(e => e.Frequency) select entry.Template;
        }
    }
}

