using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AForge.Imaging;
using DbModel.DB.Model;
using FREngine;
using ImageProcessing.Properties;
using log4net;
using OpenCvSharp;
using OpenCvSharp.CPlusPlus;
using Rect = System.Windows.Rect;
using Size = OpenCvSharp.CPlusPlus.Size;

namespace ImageProcessing
{
    public class IpMethods
    {
        public IpMethods()
        {
            try
            {
                new RqaContext().Database.Initialize(true);
            }
            catch (Exception ex)
            {
                Logger.Error("Database has not been initialized",ex);
            }
        }

        public Functions _fnc;
        private static readonly ILog Logger = LogManager.GetLogger(typeof (IpMethods));
        public int PrinterId;

        public ImageEvaluation EvaluateImage(Point2f[] calibration, Mat source, string desired){
            _fnc = new Functions{PrinterId = PrinterId};
            RqaContext context = new RqaContext();
            var manuf = context.MFPs.Where(x => x.Id == PrinterId).Select(x => x.Model.Manufacturer.Name).First();
            ImageEvaluation imev = new ImageEvaluation();
            var ocrEvaluation = new ImageEvaluation();
            var flannEvaluation = new ImageEvaluation();
            var srcT = _fnc.Transform(source, calibration);
            Stopwatch OcrTime = new Stopwatch();
            Stopwatch FlannTime = new Stopwatch();
            Parallel.Invoke(
                () => 
                {
                    OcrTime.Start();
                    ocrEvaluation = OcrEvaluation(srcT, desired);
                    OcrTime.Stop();
                    if (!string.IsNullOrEmpty(ocrEvaluation.Identified) && ocrEvaluation.Probability >= 0.5 && ocrEvaluation.Identified != "none" &&
                        ocrEvaluation.Identified != "unspecified"){
                        _fnc.FlannToken.Cancel();
                        Logger.Info("Cancellation of FLANN requested, identified: " + ocrEvaluation.Identified);
                    }
                },
                () =>
                {
                    FlannTime.Start();
                    flannEvaluation = FlannEvaluation(srcT);
                    FlannTime.Stop();
                });
            imev.Evaluated = true;
            imev.Desired = desired;
            if (ocrEvaluation == null){
                Logger.Error("OCR evaluation is null, unexpected behaviour");
                throw new NoNullAllowedException();
            }
            Logger.Info("Evaluation of ocr took: " + OcrTime.Elapsed.Seconds + " seconds and Flann took: " +
                        FlannTime.Elapsed.Seconds + " seconds.");
            if (ocrEvaluation.PopupWindow){
                imev.PopupWindow = ocrEvaluation.PopupWindow;
                imev.Verdict = ocrEvaluation.Verdict;
                return imev;
            }
            if (string.IsNullOrEmpty(ocrEvaluation.Identified) || ocrEvaluation.Identified.Equals("none")){
                Logger.Debug(
                    "OCR (complex) method did not find any text on the screen, or the screen with recognized text was not found");
                if (!string.IsNullOrEmpty(flannEvaluation.Identified)){
                    Logger.Info("Evaluated with FLANN");
                    imev.Identified = flannEvaluation.Identified;
                    imev.Verdict = "Plausible";
                    imev.Probability = flannEvaluation.Probability;
                    imev.Corresponds = desired == imev.Identified;
                }
                else{
                    Logger.Debug("FLANN did not recognized screen");
                    imev.Identified = "none";
                    imev.Verdict = "Unrecognized";
                    imev.Probability = 0;
                }
            }
            else if (ocrEvaluation.Identified == flannEvaluation.Identified || _fnc.FlannToken.IsCancellationRequested){
                imev.Identified = ocrEvaluation.Identified;
                imev.Verdict = "Confirmed";
                imev.Probability = ocrEvaluation.Probability;
            }
            else if (ocrEvaluation.Identified != flannEvaluation.Identified){
                imev.Identified = ocrEvaluation.Probability >= flannEvaluation.Probability
                    ? ocrEvaluation.Identified
                    : flannEvaluation.Identified;
                imev.Probability = ocrEvaluation.Probability >= flannEvaluation.Probability
                    ? ocrEvaluation.Probability
                    : flannEvaluation.Probability;
                imev.Verdict = imev.Probability >= 0.5 ? "Sporadic" : "Unrecognized";
                Logger.Debug("Probabilities - Ocr: "+ocrEvaluation.Probability+" identified: "+ocrEvaluation.Identified+", Flann: "+flannEvaluation.Probability+ " identified: " + flannEvaluation.Identified);
            }
            else{
                Logger.Warn("Unexpected variation");
            }
            if (string.IsNullOrEmpty(imev.Identified)){
                Logger.Warn("This should not happen");
                imev.Identified = "none";
            }
            imev.Corresponds = imev.Desired == imev.Identified;
            Logger.Debug(imev.ToString());
            return imev;
        }

        public ImageEvaluation FastEvaluation(Point2f[] calibration, Mat source, string desired)
        {
            _fnc = new Functions { PrinterId = PrinterId };
            var ocr = new Ocr() {Fnc = _fnc, PrinterId = PrinterId};
            var srcT = _fnc.Transform(source, calibration);
            bool result = ocr.CheckBoxes(srcT, desired);
            ImageEvaluation ev = new ImageEvaluation
            {
                Corresponds = result,
                Probability = result ? 1 : 0,
                Verdict = result ? "Confirmed" : "Unconfirmed",
                Desired = desired,
                Identified = result?desired:string.Empty,
                Evaluated = true
            };
            source.Dispose();
            return ev;
        }

        public ImageEvaluation FindSelectedJobs(Point2f[] calibration, Mat source){
            _fnc = new Functions{PrinterId = PrinterId};
            var srcT = _fnc.Transform(source, calibration);
            ImageEvaluation imev = new ImageEvaluation();
            srcT = srcT.CvtColor(ColorConversion.RgbToGray);
            srcT = srcT.Threshold(127, 255, ThresholdType.Binary);
            RqaContext context = new RqaContext();
            var areaOfInterest = new Size(75,20);
            foreach (var job in Enum.GetNames(typeof(JobSelection)))
            {
                if (job == "None") continue;
                var region = context.FeatureRegions.FirstOrDefault(x => x.Device.Id == PrinterId && x.Tag == job);
                if (region == null)
                {
                    Logger.Error("Region with selection: " + job + " was not found");
                    imev.Evaluated = false;
                    return imev;
                }
                var rect = srcT.GetRectSubPix(areaOfInterest, _fnc.ToPixelTr(new Point2f((float)region.Position.X + region.Size.Width / 2, (float)region.Position.Y + region.Size.Height / 2)));
                var summary = 0;
                for (int i = 0; i < rect.Cols; i++)
                {
                    for (int j = 0; j < rect.Rows; j++)
                    {
                        summary += rect.Get<byte>(j, i);
                    }
                }
                if (summary/(rect.Cols*rect.Rows) > 127)
                    continue;
                JobSelection js;
                JobSelection.TryParse(job, out js);
                imev.JobSelected.Add(js);
            }
            string jobs = String.Empty;
            imev.JobSelected.ForEach(x => jobs += x + ", ");
            Logger.Info("Selected job(s): " + jobs);
            imev.Evaluated = true;
            return imev;
        }

        public ImageEvaluation CheckSelectedJob(Point2f[] calibration, Mat source, JobSelection selection)
        {
            _fnc = new Functions { PrinterId = PrinterId };
            var srcT = _fnc.Transform(source, calibration);
            ImageEvaluation imev = new ImageEvaluation();
            srcT = srcT.CvtColor(ColorConversion.RgbToGray);
            srcT = srcT.Threshold(100, 255, ThresholdType.Binary);
            RqaContext context = new RqaContext();
            var region = context.FeatureRegions.FirstOrDefault(x => x.Device.Id == PrinterId && x.Tag == selection.ToString());
            if (region == null)
            {
                Logger.Error("Region with selection: "+selection+" was not found");
                imev.Evaluated = false;
                return imev;
            }
            var rect = srcT.GetRectSubPix(new Size(25, 25), _fnc.ToPixelTr(new Point2f((float)region.Position.X + region.Size.Width / 2, (float)region.Position.Y + region.Size.Height/ 2)));
            var summary = 0;
            for (int i = 0; i < rect.Cols; i++)
            {
                for (int j = 0; j < rect.Rows; j++)
                {
                    summary += rect.Get<byte>(j, i);
                }
            }
            summary /= (rect.Cols * rect.Rows);
            imev.Corresponds = summary <= 127;
            imev.Evaluated = true;
            rect.Dispose();
            srcT.Dispose();
            return imev;
        }

        private ImageEvaluation FlannEvaluation(Mat src)
        {
            var imev = new ImageEvaluation();
            var screens = _fnc.FlannFeatureDetector(src);
            if (screens.Count == 0 || screens == null)
            {
                imev.Identified = null;
                imev.Probability = 0;
            }
            else if (screens.Max(y => y.Value)/
                  (screens.Where(x => x.Value != screens.Max(y => y.Value)).Sum(x => x.Value)/(screens.Count - 1)) > 2)
            {
                imev.Identified = screens.FirstOrDefault(x => x.Value == screens.Max(y => y.Value)).Screen.Text;
                imev.Probability = screens.Max(y => y.Value)/(screens.Max(x=>x.Value)+(screens.Where(x=>x.Value != screens.Max(y => y.Value))).Max(c=>c.Value));
            }
            else
            {
                imev.Identified = null;
                imev.Probability = 0;
            }
            string probs = String.Empty;
            screens.ForEach(x=>probs += x.Screen.Text +" "+x.Value+", ");
            Logger.Debug("Flann probabilities: "+probs);
            return imev;
        }
        private ImageEvaluation Method2(Mat srcT)
        {
            var imev = new ImageEvaluation();
            RqaContext context = new RqaContext();
            var buttons = context.ButtonPlacements.Where(x => x.Printer.Id == PrinterId && x.IsImgProcessed).ToList();
            var mfp = context.MFPs.Where(x => x.Id == PrinterId).Select(x => x.Model);
            var vendor = mfp.Select(x => x.Manufacturer).First();
            var model = mfp.Select(x => x.Model).First();
            var files = Directory.GetFiles("C:\\RQA\\imgs\\"+vendor+"\\"+model+"\\");
            List<ScreenProbability> sim = new List<ScreenProbability>();
            foreach (var file in files)
            {
                foreach (var button in buttons)
                {
                    var el1 = _fnc.RetrieveElement(srcT, button.Button.Tag);
                    var ms1 = el1.ToMemoryStream();
                    Bitmap bm1 = new Bitmap(ms1);
                    var el2 = _fnc.RetrieveElement(Cv2.ImRead(file, LoadMode.Color), button.Button.Tag);
                    var ms2 = el2.ToMemoryStream();
                    Bitmap bm2 = new Bitmap(ms2);
                    var etm = new ExhaustiveTemplateMatching(0.5f);
                    var screenString = file.Split('\\').Last().Split('.').First();
                    var screen = context.Screens.SingleOrDefault(x => x.Text == screenString);
                    if (screen == null) continue;
                    try
                    {
                        sim.Add(new ScreenProbability()
                        {
                            Value = etm.ProcessImage(bm1, bm2)[0].Similarity,
                            Screen = screen
                        });
                    }
                    catch (Exception ex)
                    {
                        Logger.Warn("Error occured during the Image matching within Method2"+ex.Message);
                    }
                }
            }
            var screens = sim.Select(x=>x.Screen).Distinct();
            List<ScreenProbability> sims = new List<ScreenProbability>();
            foreach (var screen in screens)
            {
                sims.Add(new ScreenProbability() {Value = sim.Where(x => x.Screen == screen).Sum(x => x.Value)/sim.Count(x => x.Screen == screen), Screen = screen });
            }
            var found = sims.FirstOrDefault(x => x.Value == sims.Max(y => y.Value)).Screen.Text;
            if (found == null) return imev;
            imev.Identified = found;
            if (imev.Identified == imev.Desired)
                imev.Corresponds = true;
            imev.Probability = sims.First(x => x.Screen.Text == imev.Identified).Value;
            return imev;
        }

        public ImageEvaluation OcrEvaluation(Mat srcT, string desired){
            var imev = new ImageEvaluation();
            var ocr = new Ocr() { Fnc = _fnc, PrinterId = PrinterId };
            RqaContext context = new RqaContext();
            Dictionary<string, string> uniques = new Dictionary<string, string>();
            List<ScreenProbability> countains = new List<ScreenProbability>();
            List<WordsForUniques> wordsForUniques = new List<WordsForUniques>();
            Mat negatedSrc = new Mat();
            Mat ones = Mat.Ones(srcT.Rows, srcT.Cols, MatType.CV_8UC1) * 255;
            Mat bw = srcT.CvtColor(ColorConversion.RgbToGray);
            Cv2.Subtract(ones, bw, negatedSrc);
            var res = new List<string>();
            Parallel.Invoke(
                () =>
                {
                    res.AddRange(ocr.FindAllTexts(srcT));
                },
                () =>
                {
                    res.AddRange(ocr.FindAllTexts(negatedSrc));
                });
            string content = string.Empty;
            res.ForEach(x => content += x + " ");
            Logger.Debug("Found (complex) " + res.Count + " texts: " + content);
            if (res.Count == 0){
                imev.Identified = "none";
                imev.Probability = 0;
                return imev;
            }
            var allScreens =
                context.FeatureRegions.Where(x => x.Device.Id == PrinterId).Select(x => x.Screen).Distinct().ToList();
            // taking all words from feature regions separately
            foreach (var screen in allScreens){
                var tmp =
                    context.FeatureRegions.Where(x => x.Screen.Id == screen.Id && x.Device.Id == PrinterId)
                        .Select(x => x.Text)
                        .ToList();
                wordsForUniques.AddRange(
                    tmp.Select(tmps => new WordsForUniques(){Screen = screen, Words = tmps.Split(' ')}));
            }
            var currentManuf = context.MFPs.Where(x => x.Id == PrinterId).Select(x => x.Model.Manufacturer).First().Name;
            // check for Popup window
            if (currentManuf == Resources.KM){
                //Todo: this code must be remade
                if (new[]{"Invalid", "credentials", "Original", "left on", "glass", "Unexpected", "Error"}.Any(x => res.Contains(x))){
                    var invalidPop = context.FeatureRegions.First(x => x.Device.Id == 4 && x.Tag == "invalid");
                    //var leftOnGlass = context.FeatureRegions.First(x => x.Device.Id == 4 && x.Tag == "invalid");
                    imev.PopupWindow = _fnc.DoesPopupExist(srcT);
                    if (!imev.PopupWindow){
                        var list = ocr.SpecificBox(srcT, invalidPop);
                        imev.PopupWindow = list.Any(x => x.Equals("Invalid") || x.Equals("credentials") || x.Equals("Unexpected") || x.Equals("error") || x.Equals("Original") || x.Equals("left on glass"));
                    }
                    if (imev.PopupWindow){
                        imev.Verdict =
                            new[]{"Invalid", "credentials"}.Any(x => res.Contains(x))
                                ? Popups.InvalidCredentials.ToString()
                                : Popups.LeftOnGlass.ToString();
                        if (res.Contains("Unexpected") || res.Contains("error"))
                            imev.Verdict = Popups.UnexpectedError.ToString();
                        Logger.Info("Popup window occured: "+imev.Verdict);
                        return imev;
                    }
                }
            }
            // check foreach word if is unique
            foreach (var word in res){
                if (wordsForUniques.Count(x => x.Words.Any(y => y.Equals(word))) == 1){
                    // unique word is a whole word which is not present in any other feature region, it is also case sensitive
                    if (!uniques.ContainsKey(word))
                        uniques.Add(word,
                            wordsForUniques.Where(x => x.Words.Any(y => y.Equals(word)))
                                .Select(x => x.Screen.Text)
                                .First());
                }
                var screens =
                    context.FeatureRegions.Where(x => x.Text.Contains(word) && x.Device.Id == PrinterId)
                        .Select(x => x.Screen)
                        .Distinct()
                        .ToList();
                // countains is representation of all feature regions which contains 'word', this representation is screen/number of times word occured 
                countains.AddRange(from screen
                    in screens
                    let i =
                        context.FeatureRegions.Count(
                            x => x.Text.Contains(word) && x.Screen.Id == screen.Id && x.Device.Id == PrinterId)
                    select new ScreenProbability(){Screen = screen, Value = i});
            }
            if (uniques.Count > 0){
                var un = string.Empty;
                uniques.ToList().ForEach(x => un += x.Key + " ");
                Logger.Info("Found (complex) unique words: " + un);
                // if there are two or more uniques for same screen and in the same time all found uniques have same screen, and if there are atleast 4 words which are in featue region for same screen as unique
                if (uniques.Count > 1 && uniques.Values.Distinct().Count() == 1 &&
                    countains.Where(x => x.Screen.Text == uniques.Values.First()).Sum(x => x.Value) > 3){
                    Logger.Debug("Found (complex) enough unique words and countainable words, screen: " +
                                 uniques.First().Value);
                    imev.Identified = uniques.First().Value;
                    imev.Probability = 1;
                    return imev;
                }
                // if there are two uniques which does not have same screen, then for each unique will be checked items in countains if more than 50% are present on the uniques screen
                if (uniques.Count == 2 && uniques.Values.Distinct().Count() == 2){
                    Logger.Debug("Found (complex) two unique words, trying to determine which is correct");
                    foreach (var unique in uniques){
                        if (!(countains.Where(x => x.Screen.Text == unique.Value).Sum(x => x.Value) >=
                              countains.Count(x => x.Screen.Text == unique.Value)/2.0))
                            continue;
                        Logger.Info("(complex) Word: " + unique.Key + " is most possibly the rigth one, screen: " +
                                    unique.Value);
                        imev.Identified = unique.Value;
                        imev.Probability = 0.75;
                        return imev;
                    }
                    Logger.Debug("Could not identified right screen for two different uniques");
                }
                if (uniques.Count > 1 && uniques.Values.Distinct().Count() > 1){
                    Logger.Debug("Found (complex) " + uniques.Count +
                                 " unique words, trying to determine which is correct");
                    // count through 'uniques' in association to screens
                    List<ScreenProbability> screenProbabilities = uniques.Values
                        .Distinct()
                        .Select(screen => new ScreenProbability(){
                            Screen = context.Screens.First(x => x.Text == screen),
                            Value = (double)uniques.Where(x => x.Value == screen)
                                .Select(x => x.Key)
                                .Count()/ uniques.Keys.Count()
                        }).ToList();
                    var screenFound = screenProbabilities.FirstOrDefault(x => x.Value == screenProbabilities.Max(y => y.Value)).Screen.Text; ;
                    if (screenProbabilities.Where(x => x.Screen.Text == screenFound).Select(x => x.Value).First() > 0.5){
                        imev.Identified = screenFound;
                        imev.Probability = 0.75;
                        return imev;
                    }
                }
                // if there is one unique, and if atleast 50% of all words found are present on the unique's sceen
                if (uniques.Count == 1 &&
                    countains.Where(x => x.Screen.Text == uniques.Values.First()).Sum(x => x.Value) >=
                    countains.Count(x => x.Screen.Text == uniques.Values.First())/2.0){
                    Logger.Debug("Found (complex) one unique word: '" + uniques.Keys.First() +
                                 "' and countained words are enough, screen: " + uniques.First().Value);
                    imev.Identified = uniques.First().Value;
                    imev.Probability = 0.5;
                    return imev;
                }
            }
            // if there are no uniques found and total count of words found is less than 4, then screen cannot be identified by OCR
            if (uniques.Count == 0 && countains.Count < 4){
                Logger.Debug("Found (complex) only " + res.Count + " and no uniques within them, counting on FLANN");
                imev.Identified = string.Empty;
                imev.Probability = 0;
                return imev;
            }
            // if there are no uniques but there are 4 or more, identification will be calculated through relative inclusion
            Logger.Info("Could not decide through unique words, counting on relative inclusion");
            var probs = _fnc.ScreenProbabilities(res);
            imev.Identified = "none";
            if (probs.Count == 0)
                return imev;
            var found = probs.FirstOrDefault(x => x.Value == probs.Max(y => y.Value)).Screen.Text;
            if (found == null)
                return imev;
            imev.Identified = found;
            imev.Probability = probs.First(x => x.Screen.Text == imev.Identified).Value;
            Logger.Info("Identified through relative inclusion: "+imev.Identified);
            return imev;
        }

        public ImageEvaluation CheckPinCount(Point2f[] calibration, Mat src)
        {
            ImageEvaluation imev = new ImageEvaluation();
            _fnc = new Functions { PrinterId = PrinterId };
            var ocr = new Ocr() { Fnc = _fnc, PrinterId = PrinterId };
            Mat srcT = _fnc.Transform(src, calibration);
            RqaContext context = new RqaContext();
            var pinRegion = context.FeatureRegions.FirstOrDefault(x => x.Tag == "PIN" && x.Device.Id == PrinterId);
            if (pinRegion == null) return null;
            var fpos = _fnc.ToPixel(pinRegion.Position);
            var fsize = _fnc.ToPixel(pinRegion.Size);
            var rect = new Rect(fpos.X, srcT.Height - (fpos.Y + fsize.Height), fsize.Width, fsize.Height);
            var list = ocr.PinCount(srcT,rect);
            imev.Probability = list.Count;
            return imev;
        }

        private T WithEngineAsync<T>(Func<Engine, T> func)
        {
            var engineLoader = new OutprocLoader();
            try
            {
                var engine = engineLoader.GetEngineObjectEx("SWXD-1101-0005-7071-7199-1212",
                    Reserved1: @"C:\RQA\SWXO-1121-0005-7407-8705-5649.ABBYY.LOCALLICENSE",
                    Reserved2: "h2o_c7h5n3o6_c20h25n3o_c21h30o2_c10h15n_c9h13n");
                return func(engine);
            }
            catch (Exception ex)
            {
                Logger.Fatal("OCR Engine did not load license file properly, check if is in C:\\RQA",ex);
                return func(null);
            }
            finally
            {
                engineLoader.ExplicitlyUnload();
            }
        }
    }
}
