using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace DZ.Tools.Tests
{
    [TestFixture]
    public class QualityModelComparerTests
    {
        private static readonly NERWorker Worker = new NERWorker();

        private static readonly List<Tag<TNER>> _Expected = new List<Tag<TNER>>
        {
            new Tag<TNER>(0, 4, TNER.P),
            new Tag<TNER>(15, 26, TNER.L),
            new Tag<TNER>(30, 74, TNER.P),
            new Tag<TNER>(64, 74, TNER.L)
        };

        [Test]
        public void StrictEqualResults_NoErrors()
        {
            var report = new List<Tag<TNER>>
            {
                new Tag<TNER>(0, 4, TNER.P),
                new Tag<TNER>(15, 26, TNER.L),
                new Tag<TNER>(30, 74, TNER.P),
                new Tag<TNER>(64, 74, TNER.L),
            }.CompareTo(_Expected, TagsMatchers<TNER>.Strict, Worker.Values, Worker.Undefined);
            //no errors found
            report.Mismatches.All(e => e.Value.Count == 0).AssertTrue();
            report.Matches[TNER.U].Select(m => m.Actual.Score).Assert(Is.All.EqualTo(1));
        }

        [Test]
        public void LenientEqualResults_NoErrors()
        {
            var report = new List<Tag<TNER>>
            {
                new Tag<TNER>(0,3,TNER.P),
                new Tag<TNER>(14,30,TNER.L),
                new Tag<TNER>(50,74,TNER.P),
                new Tag<TNER>(64,74,TNER.L),
            }.CompareTo(_Expected, TagsMatchers<TNER>.Lenient, Worker.Values, Worker.Undefined);
            //no errors found
            report.Mismatches.Values.Assert(Is.All.Matches<List<Mismatch<TNER>>>(e => e.Count == 0));
            report.Matches[TNER.U].Select(m => m.Actual.Score).Assert(Is.All.EqualTo(1));
        }

        [Test]
        public void SemiStrictEqualResults_NoErrors()
        {
            var report = new List<Tag<TNER>>
            {
                new Tag<TNER>(0,3,TNER.P),
                new Tag<TNER>(14,30,TNER.L),
                new Tag<TNER>(50,74,TNER.P),
                new Tag<TNER>(64,74,TNER.L),
            }.CompareTo(_Expected, TagsMatchers<TNER>.SemiStrict, Worker.Values, Worker.Undefined);
            //no errors found
            report.Mismatches.All(e => e.Value.Count == 0).AssertTrue();
            report.Matches[TNER.U][0].Actual.Score.AssertEqualTo(0.5);
            report.Matches[TNER.U][1].Actual.Score.AssertEqualTo(0.5);
        }

        [Test]
        public void SemiStrictMultipleActualTagsResults()
        {
            var report = new List<Tag<TNER>>
            {
                new Tag<TNER>(0,10,TNER.P),
                new Tag<TNER>(12,30,TNER.P),
            }.CompareTo(new List<Tag<TNER>>
            {
                new Tag<TNER>(0,30,TNER.P)
            }, TagsMatchers<TNER>.SemiStrict, Worker.Values, Worker.Undefined);
            //no errors found
            report.Mismatches.All(e => e.Value.Count == 0).AssertTrue();
            report.Matches[TNER.P].Count.AssertEqualTo(2);
            report.Matches[TNER.P][0].Actual.Score.AssertEqualTo(0.5);
            report.Matches[TNER.P][1].Actual.Score.AssertEqualTo(0);
            report.RenderMatchesAndMismatches(t => t.ToString()).ToConsole();
        }

        [Test]
        public void StrictTypeMismatchAndMissingEntity()
        {
            var report = new List<Tag<TNER>>
            {
                //new Tag<TNER>(0, 4, TNER.P), -- missing
                new Tag<TNER>(15,26,TNER.P), // new Tag<TNER>(15, 26, TNER.L), -- incorrect type 
                new Tag<TNER>(30,74,TNER.P),//match
                new Tag<TNER>(64,74,TNER.L),//match
            }.CompareTo(_Expected, TagsMatchers<TNER>.Strict, Worker.Values, Worker.Undefined);
            report.Render().ToConsole();
            var allErrors = report.Mismatches.SelectMany(p => p.Value).ToList();
            allErrors.Count.AssertEqualTo(2);
            allErrors[1].ExpectedType.AssertEqualTo(TNER.L);
            allErrors[1].ActualType.AssertEqualTo(TNER.P);
        }

        [Test]
        public void LenientTypeMismatchAndMissingEntity()
        {
            var report = new List<Tag<TNER>>
            {
                new Tag<TNER>(10,30,TNER.P),
                new Tag<TNER>(30,74,TNER.P),
                new Tag<TNER>(64,74,TNER.L),
            }.CompareTo(_Expected, TagsMatchers<TNER>.Lenient, Worker.Values, Worker.Undefined);
            //one entity missing other has different type
            var allErrors = report.Mismatches.SelectMany(p => p.Value).ToList();
            allErrors.Count.AssertEqualTo(2);
            allErrors[1].ExpectedType.AssertEqualTo(TNER.L);
            allErrors[1].ActualType.AssertEqualTo(TNER.P);
        }

        [Test]
        public void StrictEdgesMismatchAndRedundantEntity()
        {
            var report = new List<Tag<TNER>>
            {
                new Tag<TNER>(0,4,TNER.P),
                new Tag<TNER>(15,20,TNER.L),
                new Tag<TNER>(30,74,TNER.P),
                new Tag<TNER>(64,74,TNER.L),
                new Tag<TNER>(75,80,TNER.L),
            }.CompareTo(_Expected, TagsMatchers<TNER>.Strict, Worker.Values, Worker.Undefined);
            //one error for 15-26 <> 15-20 another for 75-80 has no matches
            var allErrors = AllErrors(report);
            Console.WriteLine(report.Render());
            allErrors.Count.AssertEqualTo(3);
            allErrors[0].ExpectedType.AssertEqualTo(TNER.U);
            allErrors[1].ExpectedType.AssertEqualTo(TNER.U);
            allErrors[2].ExpectedType.AssertEqualTo(TNER.L);
        }

        private static List<Mismatch<TType>> AllErrors<TType>(ComparisonReport<TType> report)
        {
            var allErrors = report.Mismatches.SelectMany(p => p.Value).ToList();
            return allErrors;
        }

        [Test]
        public void ComplexStructures()
        {
            var etalon =
                "Ïî ñëîâàì <P>ãåíåðàëüíîãî äèðåêòîðà <O>ÎÀÎ «ÈíôîÒåÊÑ»</O> Àíäðåÿ ×àï÷àåâà</P>";
            var actual =
                "Ïî ñëîâàì <P><E>ãåíåðàëüíîãî äèðåêòîðà <O>ÎÀÎ «ÈíôîÒåÊÑ»</O></E> Àíäðåÿ ×àï÷àåâà</P>";
            ComparisonReport<TNER> report;
            var m = GetReport(etalon, actual, out report);
            var allErrors = AllErrors(report);
            Assert.That(allErrors.Count, Is.EqualTo(1));
            allErrors[0].ActualType.AssertEqualTo(TNER.E);
            allErrors[0].ExpectedType.AssertEqualTo(TNER.U);
        }

        [Test]
        public void IdenticalMarkup()
        {
            var etalon =
                "<P>Èñïîëíèòåëüíûé äèðåêòîð <O>ENISA</O> Àíäðåàñ Ïèðîòòè</P> (<P>Andreas Pirotti</P>) íàçâàë";
            var actual =
                "<P>Èñïîëíèòåëüíûé äèðåêòîð <O>ENISA</O> Àíäðåàñ Ïèðîòòè</P> (<P>Andreas Pirotti</P>) íàçâàë";
            ComparisonReport<TNER> report;
            var m = GetReport(etalon, actual, out report);
            var errors = AllErrors(report);
            Assert.That(errors, Is.Empty);
        }

        [Test]
        public void RedundantOrganization()
        {
            var etalon =
                "<O>Ìèíèñòåðñòâî îáîðîíû <L>Àðìåíèè</L></O> è <O>Åðåâàíñêèé ÍÈÈ ìàòåìàòè÷åñêèõ ìàøèí</O>";
            var actual =
                "Ìèíèñòåðñòâî îáîðîíû <L>Àðìåíèè</L> è <O>Åðåâàíñêèé</O> <O>ÍÈÈ</O> ìàòåìàòè÷åñêèõ ìàøèí";
            //expecting errors:
            //1) <O>Ìèíèñòåðñòâî îáîðîíû <G>Àðìåíèè</G></O> is missing
            ComparisonReport<TNER> report;
            GetReport(etalon, actual, out report);
            var errors = AllErrors(report);
            errors.Count.AssertEqualTo(1, "Mismatches");
            //<O>Åðåâàíñêèé ÍÈÈ ìàòåìàòè÷åñêèõ ìàøèí</O> matches 2 tags <O>Åðåâàíñêèé</O> <O>ÍÈÈ</O>
            //handle it as a single match
            report.Matches[TNER.O].Count(m => m.Actual.Score > 0).AssertEqualTo(1);
        }

        [Test]
        public void DoubleMatchPrevented()
        {
            var etalon =
                "<O>çàêîíîâ «Î òåëåêîììóíèêàöèÿõ» è «Î êîðïîðàöèè NTT»</O>";
            var actual =
                "çàêîíîâ <O>«Î òåëåêîììóíèêàöèÿõ»</O> è <O>«Î êîðïîðàöèè NTT»</O>";
            //if some actual tag covers 2 etalon tags then treats these 2 matches as single
            ComparisonReport<TNER> report;
            GetReport(etalon, actual, out report);
            AllErrors(report).Count.AssertEqualTo(0);
            report.Matches[TNER.O].Count(m => m.Actual.Score > 0).AssertEqualTo(1);
        }

        [Test]
        public void InnerTagsWithTheSameType()
        {
            var etalon =
                "<O>Government Public Key <O>Infrastructure</O></O>";
            var actual =
                "<O>Government Public Key <O>Infrastructure</O></O>";
            //if some actual tag covers 2 etalon tags then treats these 2 matches as single
            ComparisonReport<TNER> report;
            GetReport(etalon, actual, out report);
            AllErrors(report).Count.AssertEqualTo(0);
            report.Matches[TNER.O].Count(m => m.Actual.Score > 0).AssertEqualTo(2);
        }

        private static TagsCorpus<TNER> GetReport(string etalon, string actual, out ComparisonReport<TNER> report)
        {
            var m = Worker.Parser.Parse(etalon);
            report = Worker.Parser.Parse(actual).Tags
                .CompareTo(
                    m.Tags,
                    TagsMatchers<TNER>.Lenient,
                    Worker.Values,
                    Worker.Undefined);
            return m;
        }
    }
}