using System;
using System.Collections.Generic;
using System.Linq;
using CNTK;
using OpenCvSharp;

namespace OCR
{
    class EvaluationChain
    {
        private readonly Postprocessing _postprocessing;
        private readonly ConvolutionNeuralNet _convolutionNeuralNet;
        private readonly Function _numericNet;
        private readonly Function _lowerNet;
        private readonly Function _upperNet;


        public EvaluationChain()
        {
            _postprocessing = new Postprocessing();
            _convolutionNeuralNet = new ConvolutionNeuralNet();
            _numericNet = Function.Load("numbers.model", DeviceDescriptor.GPUDevice(0));
            Console.WriteLine("Model numbers.model loaded.");
            _lowerNet = Function.Load("lower.model", DeviceDescriptor.GPUDevice(0));
            Console.WriteLine("Model lower.model loaded.");
            _upperNet = Function.Load("upper.model", DeviceDescriptor.GPUDevice(0));
            Console.WriteLine("Model upper.model loaded.");
        }

        
        /// <summary>
        /// Runs the whole evaluation chain including preprocessing, segmentation, recognition and postprocessings
        /// </summary>
        /// <returns>list of found words on possitions</returns>
        public List<Word> ProcessEvaluationChain(Mat image)
        {
            new Window("Input image", image);
            Cv2.WaitKey(1);

            //preprocessing
            Console.WriteLine("Phase 1 of 3: Preprocessing, Status: Started");
            Mat binarized = Preprocessing.InitialConversion(image);
            List<CharacterBoundingBox> characterBoundingBoxes = Preprocess(binarized);
            Console.WriteLine("Phase 1 of 3: Preprocessing, Status: Finished");

            //resize characters to 32*32 + evaluate
            Console.WriteLine("Phase 2 of 3: Recognition, Status: Started");
            var combinedResults = Recognize(binarized.Clone(), characterBoundingBoxes);
            Console.WriteLine("Phase 2 of 3: Recognition, Status: Finished");

            Console.WriteLine("Phase 3 of 3: Postprocessing, Status: Started");
            //combine recognized characters into words
            var mostProbableWords = _postprocessing.GetPossibleWords(combinedResults);
            //compute credibility using dictionary
            var decideWithDictionary = _postprocessing.DecideWithDictionary(mostProbableWords);
            //filter words and return those with credibility over 0.9 and remove duplicate words
            var filteredWords = decideWithDictionary.FilterWords().RemoveDuplicateWords();
            Console.WriteLine("Phase 3 of 3: Postprocessing, Status: Finished");
            return filteredWords;
        }


        /// <summary>
        /// Finds text boxes on binarized image and segments individual characters
        /// </summary>
        /// <param name="binarized">binarized image</param>
        /// <returns>list of character bounding boxes</returns>
        private List<CharacterBoundingBox> Preprocess(Mat binarized)
        {
            //rlsa bounding boxes
            List<Rect> textBoundingBoxes = RunLengthSmearing.GetRLSABoundingBoxes(binarized);
            textBoundingBoxes = textBoundingBoxes.OrderBy(x => x.Top).ThenBy(x => x.Left).ToList();

            //get character bounding boxes
            List<CharacterBoundingBox> characterBoundingBoxes = Segmentation.GetCharacterBoundingBoxes(binarized, textBoundingBoxes);
            return characterBoundingBoxes;
        }


        /// <summary>
        /// Runs recognition chain with 3 CNNs on all segmented characters
        /// </summary>
        /// <param name="binarized">binarized image</param>
        /// <param name="characterBoxes">list of character bounding boxes found by preprocessing</param>
        /// <returns>list of classification combined results of each CNN for each character</returns>
        private List<ClassificationCombinedResult> Recognize(Mat binarized, List<CharacterBoundingBox> characterBoxes)
        {
            var combinedResults = new List<ClassificationCombinedResult>();
            foreach (var box in characterBoxes)
            {
                Mat croppedCharacter = binarized.SubMat(box.BoundingBox).Clone();
                Mat reverted = croppedCharacter.EmptyClone();
                Cv2.BitwiseNot(croppedCharacter, reverted);
                Mat nonZeros = new Mat();
                Cv2.FindNonZero(reverted, nonZeros);
                if (!nonZeros.Empty())
                {
                    var boundingRect = Cv2.BoundingRect(nonZeros);
                    Mat resizedCharacter = croppedCharacter.SubMat(boundingRect);

                    //add two pixel border
                    int diff = Math.Abs(resizedCharacter.Width - resizedCharacter.Height) + 5;
                    if (resizedCharacter.Width > resizedCharacter.Height) {
                        Cv2.CopyMakeBorder(resizedCharacter, resizedCharacter, diff / 2, diff / 2, ((diff - 1) % 2) + 2,
                            2, BorderTypes.Constant, 255);
                    } else {
                        Cv2.CopyMakeBorder(resizedCharacter, resizedCharacter, ((diff - 1) % 2) + 2, 2, diff / 2,
                            diff / 2, BorderTypes.Constant, 255);
                    }
                    resizedCharacter = resizedCharacter.Resize(new Size(32, 32), 0D, 0D, InterpolationFlags.Cubic);

                    //evaluate character and filter results
                    var numFiltered = _postprocessing.FilterClassificationResults(_convolutionNeuralNet.EvaluateSingleCharacter(resizedCharacter, _numericNet, ModelType.Numbers));
                    var lowFiltered = _postprocessing.FilterClassificationResults(_convolutionNeuralNet.EvaluateSingleCharacter(resizedCharacter, _lowerNet, ModelType.Lower));
                    var upFiltered = _postprocessing.FilterClassificationResults(_convolutionNeuralNet.EvaluateSingleCharacter(resizedCharacter, _upperNet, ModelType.Upper));

                    //combine results
                    combinedResults.Add(new ClassificationCombinedResult {
                        CharacterBox = box,
                        Labels = new List<int> { numFiltered.Label, lowFiltered.Label, upFiltered.Label },
                        Probabilities = new List<float> { numFiltered.Probability, lowFiltered.Probability, upFiltered.Probability },
                        DecodedLabels = new List<string> { numFiltered.DecodedLabel, lowFiltered.DecodedLabel, upFiltered.DecodedLabel }
                    });
                    /*
                    //save segmented char
                    string str = DateTime.Now.ToString("yyyy-MM-dd_hhmmss");
                    string fileName = @"D:\diplomka\segmented chars\" + str + "-" + i + ".png";
                    resizedCharacter.SaveImage(fileName);*/
                }
            }
            return combinedResults;
        }
    }
}
