# -*- coding: utf-8 -*-
"""
Created on Wed Dec  1 14:22:26 2021

@author: Tymaa
"""

from nlptools.utils.parser import arStrip
from nlptools.utils.implication import Implication


def normalize_word(word: str, ignore_all_diacritics_but_not_shadda: bool, ignore_shadda_diacritic: bool) -> str:
    """
    Normalize a given Arabic word by removing diacritics and/or shadda diacritic based on the provided flags.

    Args:
        word: A string representing an Arabic word to be normalized.
        ignore_all_diacritics_but_not_shadda: A boolean flag indicating whether to remove all diacritics except shadda.
        ignore_shadda_diacritic: A boolean flag indicating whether to remove shadda diacritic.

    Returns:
        A string representing the normalized Arabic word.
    """
    if ignore_all_diacritics_but_not_shadda:
        # Remove all diacritics except shadda
        word = arStrip(word, True, True, False, False, False, False)
        
    if ignore_shadda_diacritic:
        # Remove shadda diacritic
        word = arStrip(word, False, False, True, False, False, False)
    
    return word

    
def get_preferred_word(word1, word2):
    """
    Returns the preferred word among two given words based on their implication.

    Args:
        word1 (:obj:str): The first word to be compared.
        word2 (:obj:str): The second word to be compared.

    Returns:
        str: The preferred word among the two given words.
    """
    implication = Implication(word1, word2)
    
    direction = implication.getDirection()
    
    if direction in (0, 2):
        return word1
       
    elif direction == 1:
        return word2
       
    elif direction == 3:
        if not word1.endswith("َ") and not word1.endswith("ُ"):
            return word2
        return word1

           
    
def get_non_preferred_word(word1, word2):
    """
    Find the non-preferred word between the two input words.

    Args:
        word1 (:obj:str): The first word.
        word2 (:obj:str): The second word.

    Returns:
        str: The non-preferred word. If there is no non-preferred word, returns '#'.

    """
    # Find non-preferred word if the distance between the two words input is less than 15.
    implication = Implication(word1, word2)
    if implication.getDistance() < 15:
        direction = implication.getDirection()
        if direction == 0 or direction == 1:
            return word1
        elif direction == 2:
            return word2
        elif direction == 3:
            if not word1.endswith("َ") and not word1.endswith("ُ"):
                return word1
            return word2
    return "#"

def get_intersection(list1, list2, ignore_all_diacritics_but_not_shadda=False, ignore_shadda_diacritic=False):
    """
    Get the intersection of two lists after normalization and ignoring diacritics based on input flags.

    Args:
        - list1 (:obj:`list`): The first list.
        - list2 (:obj:`list`): The second list.
        - ignore_all_diacritics_but_not_shadda (:obj:`bool`, optional): A flag to ignore all diacritics except for the shadda. Defaults to False.
        - ignore_shadda_diacritic (:obj:`bool`, optional): A flag to ignore the shadda diacritic. Defaults to False.

    Returns:
        - (:obj:`list`): The intersection of the two lists after normalization and ignoring diacritics.

    """

    # Remove all None and empty values from first list
    list1 = [str(i) for i in list1 if i not in (None, ' ', '')]
    list1 = [str(i.strip()) for i in list1]

    # Remove all None and empty values from second list
    list2 = [str(i) for i in list2 if i not in (None, ' ', '')]
    list2 = [str(i.strip()) for i in list2]

    interection_list = []

    # Add all Common words between the two list1 and list2 to interectionList
    for list1_word in list1:
        for list2_word in list2:
            word1 = normalize_word(list1_word, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic)
            word2 = normalize_word(list2_word, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic)

            implication = Implication(word1, word2)
            if implication.get_direction() >= 0 and implication.get_distance() < 15:
                interection_list.append(get_preferred_word(word1, word2))

    i = 0
    while i < len(interection_list):
        j = i + 1
        while j < len(interection_list):
            non_preferred_word = get_non_preferred_word(interection_list[i], interection_list[j])
            if non_preferred_word != "#":
                interection_list.remove(non_preferred_word)
            j += 1
        i += 1

    return interection_list
             


def get_union(list1, list2, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic):
    """
    Finds the union of two lists by removing duplicates and normalizing words.

    Args:
        - list1 (:obj:`list`): The first list.
        - list2 (:obj:`list`): The second list.
        - ignore_all_diacritics_but_not_shadda (:obj:`bool`): Whether to ignore all diacritics except shadda or not.
        - ignore_shadda_diacritic (:obj:`bool`): Whether to ignore shadda diacritic or not.

    Returns:
        - :obj:`list`: The union of the two lists after removing duplicates and normalizing words.
    """
    # Remove all None and empty values from the first set
    list1 = [str(i) for i in list1 if i not in (None, ' ', '')]

    # Remove all None and empty values from the second set
    list2 = [str(i) for i in list2 if i not in (None, ' ', '')]

    union_list = []

    # Add all words found in set#1 to union_list after normalization
    for list1_word in list1:
        word1 = normalize_word(list1_word, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic)
        union_list.append(word1)

    # Add all words found in set#2 to union_list after normalization
    for list2_word in list2:
        word2 = normalize_word(list2_word, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic)
        union_list.append(word2)

    # Remove redundant words by using get_non_preferred_word function
    i = 0
    while i < len(union_list):
        j = i + 1
        while j < len(union_list):
            non_preferred_word = get_non_preferred_word(union_list[i], union_list[j])
            if (non_preferred_word != "#"):
                union_list.remove(non_preferred_word)
            j = j + 1
        i = i + 1

    return union_list
      


def jaccard_similarity(list1: list, list2: list, ignore_all_diacritics_but_not_shadda: bool, ignore_shadda_diacritic: bool) -> float:
    """
    Calculates the Jaccard similarity coefficient between two lists.

    Args:
        - list1 (:obj:`list`): The first list.
        - list2 (:obj:`list`): The second list.
        - ignore_all_diacritics_but_not_shadda (:obj:`bool`): A flag indicating whether to ignore all diacritics except for shadda.
        - ignore_shadda_diacritic (:obj:`bool`): A flag indicating whether to ignore the shadda diacritic.

    Returns:
        float: The Jaccard similarity coefficient between the two lists.
    """
    # Find the intersection between two sets
    intersection_list = get_intersection(list1, list2, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic)
    
    # Find the union between two sets
    union_list = get_union(list1, list2, ignore_all_diacritics_but_not_shadda, ignore_shadda_diacritic)
    
    # Calculate the Jaccard similarity coefficient by dividing the length of the intersectionList by the length of the unionList
    return float(len(intersection_list)) / float(len(union_list))
   



def jaccard(delimiter, str1, str2, selection, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic):
    """
    Compute the Jaccard similarity, union, or intersection of two sets of strings.

    Args:
        delimiter (:obj:`str`): The delimiter used to split the input strings.
        str1 (:obj:`str`): The first input string to compare.
        str2 (:obj:`str`): The second input string to compare.
        selection (:obj:`str`): The desired operation to perform on the two sets of strings. 
                         Must be one of "intersection", "union", "jaccardSimilarity", or "jaccardAll".
        ignoreAllDiacriticsButNotShadda (:obj:`bool`): If True, ignore all diacritics except for the Shadda diacritic.
        ignoreShaddaDiacritic (:obj:`bool`): If True, ignore the Shadda diacritic.

    Returns:
        The Jaccard similarity, union, or intersection of the two sets of strings, 
        depending on the value of the `selection` argument.

        If `selection` is "jaccardAll", a list of the intersection, union, and Jaccard similarity 
        of the two sets of strings will be returned.

        If an error occurs, the method will return the string "An error has occurred".
    """
    try:
        list1 = str1.split(delimiter)
        list2 = str2.split(delimiter)

        if selection == "intersection":
            intersection = get_intersection(list1, list2, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic)
            return intersection
        elif selection == "union":
            union = get_union(list1, list2, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic)
            return union
        elif selection == "jaccardSimilarity":      
            similarity = jaccard_similarity(list1, list2, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic)
            return similarity
        elif selection == "jaccardAll":    
            intersection = get_intersection(list1, list2, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic)
            union = get_union(list1, list2, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic)
            similarity = jaccard_similarity(list1, list2, ignoreAllDiacriticsButNotShadda, ignoreShaddaDiacritic)
            output_list = ["intersection:", intersection, "union:", union, "similarity:", similarity]
            return output_list
        else:
            return 'Invalid selection option'

    except AttributeError as ae:
        print(f"Attribute error occurred: {str(ae)}")
        return 'Invalid input type'
    except Exception as e:
        print(f"Error occurred: {str(e)}")
        return 'An error has occurred'
