using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ZionExtractor.Controllers.LegworkFilters;
using ZionVault.Basic;

namespace ZionExtractor.Controllers
{
    class Legwork
    {
        public ZionVault.Binder.Output.Document Match(ZionSchematics.Schematics schematics,
                                                  ZionVault.Binder.OCR.Document document)
        {
            ZionVault.Binder.Output.Document output = new ZionVault.Binder.Output.Document();
            //output.Type = schematics.Title;

            if ((document.Pages.Count < schematics.MinimumNumberOfPages)
                || (document.Pages.Count > schematics.MaximumNumberOfPages && schematics.MaximumNumberOfPages != 0))
                return null;

            foreach (ZionVault.Binder.OCR.Page page in document.Pages)
            {
                ZionVault.Binder.Output.Page CurrentPage = new ZionVault.Binder.Output.Page(page.Text);
                CurrentPage.Index = page.Index;
                CurrentPage.FullText = page.Text;
                CurrentPage.Type = schematics.Title;
                var blocks = Search(schematics.SearchElements.All, page);
                try
                {
                    foreach (var field in blocks)
                        CurrentPage.SearchElements.Add(field);
                }
                catch (Exception ex)
                {
                    Console.Write(ex.StackTrace);
                }
                output.Pages.Add(CurrentPage);
            }

            if ((from ZionVault.Binder.Output.Page p in output.Pages
                 from ZionVault.Binder.Output.Field f in p.SearchElements
                 where !f.isFound && (from ZionSchematics.Models.Field arch in schematics.SearchElements.All
                                      where arch.SearchControl.Type is ZionSchematics.Models.SearchControlTypes.RequiredElement
                                      select arch.FullName).ToList().Contains(f.FullName)
                 select f).Count() > 0)
                return null;

            return output;
        }

        public ZionVault.Binder.Output.Page Match(ZionSchematics.Schematics schematics,
                                                  ZionVault.Binder.OCR.Page page)
        {
            if ((page.Index < schematics.MinimumNumberOfPages) ||
                (page.Index > schematics.MaximumNumberOfPages && schematics.MaximumNumberOfPages != 0))
                return null;

            if (schematics.SearchElements.Header.Elements.Count() > 0)
            {
                var headers = Search(schematics.SearchElements.Header.Elements, page);
                if (headers.Where(h => h.isFound).Count() != schematics.SearchElements.Header.Elements.Count())
                    return null;
            }

            ZionVault.Binder.Output.Page CurrentPage = new ZionVault.Binder.Output.Page(page.Text);
            CurrentPage.Type = schematics.Title;
            CurrentPage.Index = page.Index;
            CurrentPage.FullText = page.Text;
            var blocks = Search(schematics.SearchElements.Footer.Elements, page);
            blocks.AddRange(Search( schematics.SearchElements.Fields.Elements, page));

            try
            {
                foreach (var field in blocks)
                    CurrentPage.SearchElements.Add(field);
            }
            catch (Exception ex)
            {
                Console.Write(ex.StackTrace);
            }
            
            return CurrentPage;
        }

        private List<ZionVault.Binder.Output.Field> Search(List<ZionSchematics.Models.Field> searchable,
                                                           ZionVault.Binder.OCR.Page page)
        {
            List<ZionVault.Binder.Output.Field> Blocks = new List<ZionVault.Binder.Output.Field>();

            foreach (ZionSchematics.Models.Field field in searchable)
            {
                ZionVault.Basic.Location AreaConstraint = Limit.Go(field.SearchRelations?.Relations, Blocks);
                {
                    Blocks.AddRange(
                        this.GetFields(
                            this.GetHypotheses(field, page, Blocks, AreaConstraint),
                            Blocks,
                            field.SearchRelations?.Nearest,
                            field
                            )
                        );
                }
            }

            return Blocks;
        }

        private List<Models.Hypothesis> GetHypotheses(ZionSchematics.Models.Field field,
                                                      ZionVault.Binder.OCR.Page page,
                                                      List<ZionVault.Binder.Output.Field> found,
                                                      ZionVault.Basic.Location limit)
        {
            List<Models.Hypothesis> options = new List<Models.Hypothesis>();

            switch (field.SearchOptions.SearchMode.Mode)
            {
                case "Barcode":
                    options.AddRange(
                            Controllers.LegworkMethods.BarcodeSearch.Search(
                                page.Barcodes, field.SearchOptions)
                        );
                    break;
                case "BestQuality":
                    options.AddRange(
                        Controllers.LegworkMethods.BestQuality.Search(
                        (from ZionVault.Binder.Output.Field f in found where field.SearchOptions.SearchValues.Contains(f.FullName) select f).ToList()
                        ));
                    break;
                case "BetweenText":
                    if (field.SearchRelations.Relations.Count == 2)
                    {
                        ZionVault.Binder.Output.Field A = Controllers.LegworkFilters.Title.Go(found, field.SearchRelations.Relations[0].Reference, true);
                        ZionVault.Binder.Output.Field B = Controllers.LegworkFilters.Title.Go(found, field.SearchRelations.Relations[1].Reference, true);
                        limit = new Location(-1, -1, -1, -1);
                        foreach (string word in field.SearchOptions.SearchValues)
                        {
                            if (A != null && B != null)
                                options.Add(Controllers.LegworkMethods.BetweenText.Search(page, A.Position, B.Position, field.SearchOptions));
                            if (options.Count > 0)
                                break;
                        }
                    }
                    break;
                case "BetweenTextVertical":
                    if (field.SearchRelations.Relations.Count == 2)
                    {
                        ZionVault.Binder.Output.Field A = Controllers.LegworkFilters.Title.Go(found, field.SearchRelations.Relations[0].Reference, true);
                        ZionVault.Binder.Output.Field B = Controllers.LegworkFilters.Title.Go(found, field.SearchRelations.Relations[1].Reference, true);
                        limit = new Location(-1, -1, -1, -1);
                        foreach (string word in field.SearchOptions.SearchValues)
                        {
                            if (A != null && B != null)
                                options.Add(Controllers.LegworkMethods.BetweenTextVertical.Search(page, A.Position, B.Position, field.SearchOptions));
                            if (options.Count > 0)
                                break;
                        }
                    }
                    break;
                case "ConstantValue":
                    options.AddRange(Controllers.LegworkMethods.ConstantValue.Search(field.SearchOptions.SearchValues));
                    break;
                case "FirstFound":
                    options.AddRange(Controllers.LegworkMethods.FirstFound.Search(
                        (from ZionVault.Binder.Output.Field f in found where field.SearchOptions.SearchValues.Contains(f.FullName) select f)
                        .ToList()));
                    break;
                case "MergeFields":
                    options.AddRange(Controllers.LegworkMethods.MergeFields.Search(
                             (from ZionVault.Binder.Output.Field f in found where field.SearchOptions.SearchValues.Contains(f.FullName) select f)
                             .ToList()));
                    break;
                case "Position":
                    options.AddRange(
                        Controllers.LegworkMethods.Position.Search(page, new Location(
                            field.SearchOptions.Rect.Top,
                            field.SearchOptions.Rect.Right,
                            field.SearchOptions.Rect.Bottom,
                            field.SearchOptions.Rect.Left
                        )));
                    break;
                case "RegularExpression":
                    foreach (string word in field.SearchOptions.SearchValues)
                    {
                        options.AddRange(
                            Controllers.LegworkMethods.RegularExpression.Search(
                            word, page, limit, field.SearchOptions
                            ).ToList()
                        );
                    }
                    break;
                case "ValueVariable":
                    options.AddRange(
                        Controllers.LegworkMethods.ValueVariable.Search(
                            field.SearchOptions.SearchValues
                        ));
                    break;
            }

            if (field.SearchOptions.ExcludedElements.Count() > 0 && options.Count() > 0)
            {
                field.SearchOptions.ExcludedElements.ForEach(element =>
                {
                    ZionVault.Binder.Output.Field f = found.Find(block => block.FullName == element);

                    options.Where(x => Regex.IsMatch(x.Value, f.Value)).ToList().ForEach(o =>
                    {
                        o.Value = Regex.Replace(o.Value, f.Value, string.Empty).Trim();
                    });
                });
            }

            return options;
        }

        private List<ZionVault.Binder.Output.Field> GetFields(List<Models.Hypothesis> hypothesis,
                                                  List<ZionVault.Binder.Output.Field> found,
                                                  ZionSchematics.Models.SearchNearestTypes.INearest nearest,
                                                  ZionSchematics.Models.Field intent)
        {
            List<ZionVault.Binder.Output.Field> fields = new List<ZionVault.Binder.Output.Field>();


            if (intent.SearchControl.Type.Rule == new ZionSchematics.Models.SearchControlTypes.RepeatingElement().Rule
                && hypothesis.Count > 0)
            {
                for (int i = 1; i < hypothesis.Count; i++)
                {
                    string name = $"{intent.Name} {i}";
                    string fullName = $"{intent.FullName} {i}";
                    fields.Add(new ZionVault.Binder.Output.Field(name, fullName, intent.SearchOptions.ContentType.Type, intent.Export, intent.Verification, true,
                        hypothesis[i].Value, hypothesis[i].Quality, hypothesis[i].Location));
                }
            }
            else if (hypothesis.Count > 1 && nearest != null)
            {
                Models.Hypothesis h = LegworkFilters.Immediate.Go(nearest, found, hypothesis);
                fields.Add(new ZionVault.Binder.Output.Field(intent.Name, intent.FullName, intent.SearchOptions.ContentType.Type, intent.Export, intent.Verification,
                        (h != null) ? true : false,
                        (h != null) ? h.Value : string.Empty,
                        (h != null) ? h.Quality : 0,
                        (h != null) ? h.Location : null
                    ));
            }
            else if (hypothesis.Count > 0)
            {
                //Modificacao para nao travar quando nao encontra resultado para o novo tipo criado BetweenTextVertical, Tiago Carvalho
                if (hypothesis[0]==null)
                {
                    hypothesis[0] = new Models.Hypothesis("",new Location(0,0,0,0),0);
                }
                //Feito dia 03/01/2023

                hypothesis.ForEach(hip =>
                {
                    hip.Quality =
                    intent.ValidationScript != string.Empty ?
                        (Validation.CallScript.ExecuteValidationVBScript(intent.ValidationScript, hip.Value) ? 100 : 0) :
                        hip.Quality;
                });

                Models.Hypothesis h = LegworkFilters.Quality.Higher(hypothesis);
                fields.Add(new ZionVault.Binder.Output.Field(intent.Name, intent.FullName, intent.SearchOptions.ContentType.Type, intent.Export, intent.Verification,
                        (h != null) ? true : false,
                        (h != null) ? h.Value : string.Empty,
                        (h != null) ? h.Quality : 0,
                        (h != null) ? h.Location : null
                    ));
            }
            else
            {
                fields.Add(new ZionVault.Binder.Output.Field(intent.Name, intent.FullName, intent.SearchOptions.ContentType.Type, intent.Export, intent.Verification,
                        false,
                        string.Empty,
                        0,
                        null
                    ));
            }

            if (fields.Count() > 0)
            {
                fields.Last().Quality =
                    intent.ValidationScript != string.Empty ?
                        (Validation.CallScript.ExecuteValidationVBScript(intent.ValidationScript, fields.Last().Value) ? 100 : 0) :
                        fields.Last().Quality;
            }

            return fields;
        }

    }
}
