using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using InterpreterLibrary.Models;
using OpenCvSharp;
using Tesseract;
using Rect = OpenCvSharp.Rect;

namespace InterpreterLibrary.Interpreters
{
    internal static class OcrExtensions
    {
        private static class TessVariables
        {
            public static string Blacklist = "tessedit_char_blacklist";
            public static string Whitelist = "tessedit_char_whitelist";
            public static string TextordHeavyNr = "textord_heavy_nr";
            public static string ClassifyBlnNumericMode = "classify_bln_numeric_mode";
        }

        private class TransientTesseractVariableSetter : IDisposable
        {
            private readonly TesseractEngine _engine;
            private readonly IDictionary<string, string> _originalStrings;
            private readonly IDictionary<string, double> _originalDoubles;
            public TransientTesseractVariableSetter(TesseractEngine engine)
            {
                _engine = engine;
                _originalStrings = new Dictionary<string, string>();
                _originalDoubles = new Dictionary<string, double>();
            }

            public void Set(string name, string value)
            {
                if (!_originalStrings.ContainsKey(name))
                {
                    if (_engine.TryGetStringVariable(name, out var orig))
                    {
                        _originalStrings.Add(name, orig);
                    }
                }
                _engine.SetVariable(name, value);
            }

            public void Set(string name, double value)
            {
                if (!_originalStrings.ContainsKey(name))
                {
                    if (_engine.TryGetDoubleVariable(name, out var orig))
                    {
                        _originalDoubles.Add(name, orig);
                    }
                }
                _engine.SetVariable(name, value);
            }

            public void Dispose()
            {
                foreach (var entry in _originalDoubles)
                {
                    _engine.SetVariable(entry.Key, entry.Value);
                }
                foreach (var entry in _originalStrings)
                {
                    _engine.SetVariable(entry.Key, entry.Value);
                }
            }
        }

        internal static OcrResult ProcessNumericText(this TesseractEngine ocr, Mat source, Rect area)
        {
            lock (ocr)
            {
                var clip = source.SubMat(area);
                using (var pix = Pix.LoadFromMemory(clip.ToBytes()))
                using (var setter = new TransientTesseractVariableSetter(ocr))
                {
                    setter.Set(TessVariables.Blacklist, "!?@#$%&*()<>_-+=/:;,'\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
                    setter.Set(TessVariables.Whitelist, "0123456789");
                    setter.Set(TessVariables.ClassifyBlnNumericMode, "1");
                    setter.Set(TessVariables.TextordHeavyNr, 1);
                    return ocr.ProcessText(pix, new Tesseract.Rect(0, 0, area.Width, area.Height));
                }
            }
        }

        internal static OcrResult ProcessText(this TesseractEngine ocr, Mat source, Rect area)
        {
            lock (ocr)
            {
                var clip = source.SubMat(area);
                using (var pix = Pix.LoadFromMemory(clip.ToBytes()))
                using (var setter = new TransientTesseractVariableSetter(ocr))
                {
                    setter.Set(TessVariables.Blacklist, "!?@#$%&*()<>_-+=/:;,'\"");
                    setter.Set(TessVariables.Whitelist, "ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅabcdefghijklmnopqrstuvwxyzæøå0123456789");
                    setter.Set(TessVariables.ClassifyBlnNumericMode, "0");
                    setter.Set(TessVariables.TextordHeavyNr, 1);
                    setter.Set("disable_character_fragments","0");
                    lock (ocr)
                    {
                        using (var page = ocr.Process(pix, new Tesseract.Rect(0, 0, area.Width, area.Height), PageSegMode.SingleLine))
                        {
                            return new OcrResult { InterpretedText = page.GetText().Trim(), Confidence = page.GetMeanConfidence() };
                        }
                    }
                }
            }
        }

        internal static OcrResult ProcessCandidateNameText(this TesseractEngine ocr, Mat source, Rect area)
        {
            lock (ocr)
            {
                var clip = source.SubMat(area);
                using (var pix = Pix.LoadFromMemory(clip.ToBytes()))
                using (var setter = new TransientTesseractVariableSetter(ocr))
                {
                    setter.Set(TessVariables.Blacklist, "|!?@#$%&*()<>_-+=/:;'\"");
                    setter.Set(TessVariables.Whitelist, ",ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅabcdefghijklmnopqrstuvwxyzæøå0123456789");
                    setter.Set(TessVariables.ClassifyBlnNumericMode, "0");
                    setter.Set(TessVariables.TextordHeavyNr, 0);
                    lock (ocr)
                    {
                        using (var page = ocr.Process(pix, new Tesseract.Rect(0, 0, area.Width, area.Height), PageSegMode.SingleLine))
                        {
                            return new OcrResult { InterpretedText = Regex.Replace(page.GetText(), @"\,(.*)", "").Trim(), Confidence = page.GetMeanConfidence() };
                        }
                    }
                }
            }
        }

        internal static OcrResult ProcessText(this TesseractEngine ocr, Mat source)
        {
            return ocr.ProcessText(source, new Rect(0, 0, source.Width, source.Height));
        }

        private static OcrResult ProcessText(this TesseractEngine ocr, Pix source, Tesseract.Rect area)
        {
            lock (ocr)
            {
                using (var page = ocr.Process(source, area, PageSegMode.SingleWord))
                {
                    return new OcrResult { InterpretedText = page.GetText().Trim().Replace(" ", ""), Confidence = page.GetMeanConfidence() };
                }
            }
        }
    }
}

