package de.duehl.basics.io.textfile.dictionary;

/*
 * Copyright 2024 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import static org.junit.Assert.*;

import java.util.List;
import java.util.Map;

import org.junit.Test;

import de.duehl.basics.text.Text;

public class DictionaryTest {

    @Test
    public void createDescriptionWithTitle() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        String actual = dictionary.createDescription("Test-Wörterbuch");
        String expected = """
                Test-Wörterbuch:

                    foo
                        bar
                        baz

                    Apfel
                        Boskop
                        Elstar
                """;
        assertEquals(expected , actual);
    }

    @Test
    public void createDescriptionWithoutTitle() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        String actual = dictionary.createDescription();
        String expected = """
                foo
                    bar
                    baz

                Apfel
                    Boskop
                    Elstar
                """;
        assertEquals(expected , actual);
    }

    @Test
    public void containsAndGetEntry() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        assertTrue(dictionary.containsEntryWithMainWord("foo"));
        assertTrue(dictionary.containsEntryWithMainWord("Apfel"));
        assertFalse(dictionary.containsEntryWithMainWord("bar"));
        assertFalse(dictionary.containsEntryWithMainWord("Boskop"));
        assertFalse(dictionary.containsEntryWithMainWord("blubb"));

        assertTrue(dictionary.containsEntryWithMainWordOrAlternative("foo"));
        assertTrue(dictionary.containsEntryWithMainWordOrAlternative("Apfel"));
        assertTrue(dictionary.containsEntryWithMainWordOrAlternative("bar"));
        assertTrue(dictionary.containsEntryWithMainWordOrAlternative("Boskop"));
        assertTrue(dictionary.containsEntryWithMainWordOrAlternative("Elstar"));
        assertFalse(dictionary.containsEntryWithMainWordOrAlternative("blubb"));

        DictionaryEntry actualApfelEntry = dictionary.getEntryWithMainWord("Apfel");
        assertEquals(apfelEntry, actualApfelEntry);
    }

    @Test
    public void textContainsMainWordOrAlternative() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        String text1 = "Auf der Wiese steht ein Apfelbaum";
        String text2 = "Auf der Wiese steht ein Kirschbaum";
        assertTrue(dictionary.textContainsMainWordOrAlternative(text1));
        assertFalse(dictionary.textContainsMainWordOrAlternative(text2));
    }

    @Test
    public void size() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        assertEquals(2, dictionary.size());
    }

    @Test
    public void get() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        DictionaryEntry actualFooEntry = dictionary.get(0);
        DictionaryEntry actualApfelEntry = dictionary.get(1);
        assertEquals(fooEntry, actualFooEntry);
        assertEquals(apfelEntry, actualApfelEntry);
    }

    @Test
    public void toListOfLists() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");
        fooEntry.addAlternative("baz2");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        List<List<String>> listOfLists = dictionary.toListOfLists();
        assertEquals(2, listOfLists.size());

        List<String> list1 = listOfLists.get(0);
        assertEquals(4, list1.size());
        assertEquals("foo", list1.get(0));
        assertEquals("bar", list1.get(1));
        assertEquals("baz", list1.get(2));
        assertEquals("baz2", list1.get(3));

        List<String> list2 = listOfLists.get(1);
        assertEquals(3, list2.size());
        assertEquals("Apfel", list2.get(0));
        assertEquals("Boskop", list2.get(1));
        assertEquals("Elstar", list2.get(2));
    }

    @Test
    public void toDisjunctFlatListSortedByLengthDescanding() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        List<String> list = dictionary.toDisjunctFlatListSortedByLengthDescanding();
        assertEquals(6, list.size());

        assertEquals("Boskop", list.get(0));
        assertEquals("Elstar", list.get(1));
        assertEquals("Apfel", list.get(2));
        assertEquals("bar", list.get(3));
        assertEquals("baz", list.get(4));
        assertEquals("foo", list.get(5));
    }

    @Test
    public void toDisjunctFlatListSortedByLengthDescandingSillyInput() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        DictionaryEntry fooEntry2 = new DictionaryEntry("foo2");
        fooEntry2.addAlternative("bar");
        fooEntry2.addAlternative("baz");
        fooEntry2.addAlternative("bar2");
        fooEntry2.addAlternative("baz2");

        DictionaryEntry barEntry = new DictionaryEntry("bar");
        barEntry.addAlternative("foo");
        barEntry.addAlternative("foo2");
        barEntry.addAlternative("baz");

        DictionaryEntry boskopEntry = new DictionaryEntry("Boskop");
        boskopEntry.addAlternative("Apfel");
        boskopEntry.addAlternative("Boskop");
        boskopEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);
        dictionary.addDictionaryEntry(fooEntry2);
        dictionary.addDictionaryEntry(barEntry);
        dictionary.addDictionaryEntry(boskopEntry);

        List<String> list = dictionary.toDisjunctFlatListSortedByLengthDescanding();
        assertEquals(9, list.size());

        assertEquals("Boskop", list.get(0));
        assertEquals("Elstar", list.get(1));
        assertEquals("Apfel", list.get(2));
        assertEquals("bar2", list.get(3));
        assertEquals("baz2", list.get(4));
        assertEquals("foo2", list.get(5));
        assertEquals("bar", list.get(6));
        assertEquals("baz", list.get(7));
        assertEquals("foo", list.get(8));
    }

    @Test
    public void getMainWordOfAlternative() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        assertEquals("nicht enthalten", dictionary.getMainWordOfAlternative("nicht enthalten"));

        assertEquals("foo", dictionary.getMainWordOfAlternative("foo"));
        assertEquals("foo", dictionary.getMainWordOfAlternative("bar"));
        assertEquals("foo", dictionary.getMainWordOfAlternative("baz"));

        assertEquals("Apfel", dictionary.getMainWordOfAlternative("Apfel"));
        assertEquals("Apfel", dictionary.getMainWordOfAlternative("Boskop"));
        assertEquals("Apfel", dictionary.getMainWordOfAlternative("Elstar"));
    }

    @Test
    public void getMainWordOfAlternativeToEmptyIfNotFound() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        assertEquals("", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("nicht enthalten"));

        assertEquals("foo", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("foo"));
        assertEquals("foo", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("bar"));
        assertEquals("foo", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("baz"));

        assertEquals("Apfel", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("Apfel"));
        assertEquals("Apfel", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("Boskop"));
        assertEquals("Apfel", dictionary.getMainWordOfAlternativeToEmptyIfNotFound("Elstar"));
    }


    @Test
    public void getMainWordOfAlternativeWithoutError() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        assertEquals("foo", dictionary.getMainWordOfAlternativeWithError("foo", "doof"));
        assertEquals("foo", dictionary.getMainWordOfAlternativeWithError("bar", "doof"));
        assertEquals("foo", dictionary.getMainWordOfAlternativeWithError("baz", "doof"));

        assertEquals("Apfel", dictionary.getMainWordOfAlternativeWithError("Apfel", "doof"));
        assertEquals("Apfel", dictionary.getMainWordOfAlternativeWithError("Boskop", "doof"));
        assertEquals("Apfel", dictionary.getMainWordOfAlternativeWithError("Elstar", "doof"));
    }

    @Test (expected = RuntimeException.class)
    public void getMainWordOfAlternativeWithError() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        dictionary.getMainWordOfAlternativeWithError("nicht enthalten", "doof");
    }

    @Test
    public void createMapFromDictionary() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        Map<String, List<String>> map = dictionary.createMapFromDictionary();

        assertEquals(2, map.size());
        List<String> fooList = map.get("foo");
        assertEquals(3, fooList.size());
        assertEquals("foo", fooList.get(0));
        assertEquals("bar", fooList.get(1));
        assertEquals("baz", fooList.get(2));

        List<String> apfelList = map.get("Apfel");
        assertEquals(3, apfelList.size());
        assertEquals("Apfel", apfelList.get(0));
        assertEquals("Boskop", apfelList.get(1));
        assertEquals("Elstar", apfelList.get(2));
    }

    @Test
    public void createMapFromDictionaryMultipleMainWordsAreError() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        Map<String, List<String>> map =
                dictionary.createMapFromDictionaryMultipleMainWordsAreError();

        assertEquals(2, map.size());
        List<String> fooList = map.get("foo");
        assertEquals(3, fooList.size());
        assertEquals("foo", fooList.get(0));
        assertEquals("bar", fooList.get(1));
        assertEquals("baz", fooList.get(2));

        List<String> apfelList = map.get("Apfel");
        assertEquals(3, apfelList.size());
        assertEquals("Apfel", apfelList.get(0));
        assertEquals("Boskop", apfelList.get(1));
        assertEquals("Elstar", apfelList.get(2));
    }

    @Test (expected = RuntimeException.class)
    public void createMapFromDictionaryMultipleMainWordsAreErrorFailed() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        DictionaryEntry apfelEntry2 = new DictionaryEntry("Apfel");
        apfelEntry2.addAlternative("Boskop-2");
        apfelEntry2.addAlternative("Elstar-2");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);
        dictionary.addDictionaryEntry(apfelEntry2);

        dictionary.createMapFromDictionaryMultipleMainWordsAreError();
    }

    @Test
    public void sortAlternatives() {
        DictionaryEntry baumEntry = new DictionaryEntry("Baum");
        baumEntry.addAlternative("Eiche");
        baumEntry.addAlternative("Buche");
        baumEntry.addAlternative("Erle");
        baumEntry.addAlternative("Birke");
        baumEntry.addAlternative("Esche");

        DictionaryEntry tierEntry = new DictionaryEntry("Tier");
        tierEntry.addAlternative("Katze");
        tierEntry.addAlternative("Hund");
        tierEntry.addAlternative("Hase");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(baumEntry);
        dictionary.addDictionaryEntry(tierEntry);

        dictionary.sortAlternatives();

        assertEquals(2, dictionary.size());


        DictionaryEntry actualBaumEntry =  dictionary.get(0);
        DictionaryEntry actualTierEntry =  dictionary.get(1);

        assertEquals("Baum", actualBaumEntry.getMainWord());

        List<String> actualBaumAlternatives = actualBaumEntry.getAlternatives();
        assertEquals(5, actualBaumAlternatives.size());
        assertEquals("Birke", actualBaumAlternatives.get(0));
        assertEquals("Buche", actualBaumAlternatives.get(1));
        assertEquals("Eiche", actualBaumAlternatives.get(2));
        assertEquals("Erle", actualBaumAlternatives.get(3));
        assertEquals("Esche", actualBaumAlternatives.get(4));

        assertEquals("Tier", actualTierEntry.getMainWord());

        List<String> actualTierAlternatives = actualTierEntry.getAlternatives();
        assertEquals(3, actualTierAlternatives.size());
        assertEquals("Hase", actualTierAlternatives.get(0));
        assertEquals("Hund", actualTierAlternatives.get(1));
        assertEquals("Katze", actualTierAlternatives.get(2));
    }

    @Test
    public void addSameMultipleAndGetOneEntry() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("bar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        assertTrue(dictionary.containsEntryWithMainWord("foo"));
        assertTrue(dictionary.containsEntryWithMainWordOrAlternative("bar"));

        DictionaryEntry entry = dictionary.getEntryWithMainWord("foo");
        List<String> alternatives = entry.getAlternatives();
        assertEquals(1, alternatives.size());
        assertEquals("bar", alternatives.get(0));
    }

    @Test
    public void getMainWords() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        List<String> mainWords = dictionary.getMainWords();
        assertEquals(2, mainWords.size());
        assertEquals("foo", mainWords.get(0));
        assertEquals("Apfel", mainWords.get(1));
    }

    @Test
    public void checkAllMaiwordsAndAlternativesAreUnique() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        dictionary.checkAllMaiwordsAndAlternativesAreUnique();
    }

    @Test (expected = RuntimeException.class)
    public void checkAllMaiwordsAndAlternativesAreUniqueFailes() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");
        fooEntry.addAlternative("Apfel");
        fooEntry.addAlternative("Elstar");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");
        apfelEntry.addAlternative("bar");
        apfelEntry.addAlternative("foo");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        dictionary.checkAllMaiwordsAndAlternativesAreUnique();
    }

    @Test
    public void checkAllMaiwordsAndAlternativesAreUniqueFailesWithErrorMessage() {
        DictionaryEntry fooEntry = new DictionaryEntry("foo");
        fooEntry.addAlternative("bar");
        fooEntry.addAlternative("baz");
        fooEntry.addAlternative("Apfel");
        fooEntry.addAlternative("Elstar");

        DictionaryEntry apfelEntry = new DictionaryEntry("Apfel");
        apfelEntry.addAlternative("Boskop");
        apfelEntry.addAlternative("Elstar");
        apfelEntry.addAlternative("bar");
        apfelEntry.addAlternative("foo");

        Dictionary dictionary = new Dictionary();
        dictionary.addDictionaryEntry(fooEntry);
        dictionary.addDictionaryEntry(apfelEntry);

        try {
            dictionary.checkAllMaiwordsAndAlternativesAreUnique();
            fail();
        }
        catch (Exception exception) {
            assertEquals("Die folgenden Einträge kommen mehr als einmal im Dictionary vor:\n"
                    + "     1. [Apfel]" + Text.LINE_BREAK
                    + "     2. [Elstar]" + Text.LINE_BREAK
                    + "     3. [bar]" + Text.LINE_BREAK
                    + "     4. [foo]" + Text.LINE_BREAK,
                    exception.getMessage());
        }
    }

}
