"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Sunday, 28th Apr., 2024

    OBJECTIVE:

        To provide a convenient way to perform RegEx operations like finding patterns and substituting them.

    REFERENCES:

        1) https://www.w3schools.com/python/python_regex.asp

    DOWNLOADS:

        N/A

"""

#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")

# To work with RegEx:
import re


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# Common RegEx patterns:
REGEX_EMAIL_ID = r"[\d\w\-_.+]*@[\d\w\-_]*.[\d\w]{2,}"
REGEX_URL = r"^http[s]?:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[a-zA-Z0-9-_.~%]*)*(?:\?[a-zA-Z0-9-_&=%.]*)?$"
REGEX_HTTPS_URL = r"^https:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[a-zA-Z0-9-_.~%]*)*(?:\?[a-zA-Z0-9-_&=%.]*)?$"
REGEX_PASSWORD = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[\d])(?=.*[!@#$%^&*()_+{}\[\]:;<>,.?~\\\/-]).{8,}$"
REGEX_NAME = r"^[\d\w .\-]{1,30}$"
REGEX_USERNAME = r"^[\d\w_]{8,25}$"
REGEX_CONTACT_NUMBER = r"\+?\d{0,3}\s*\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}"
REGEX_DATE = r"\b(?:\d{4}-\d{2}-\d{2}|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4}|\d{1,2}\/\d{1,2}\/\d{4}|\d{1,2}-\d{1,2}-\d{2}|\d{1,2}(?:st|nd|rd|th)\s+(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?),?\s+\d{4})\b"
REGEX_GSTIN = r"[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}"
REGEX_PAN = r"[A-Z]{5}[0-9]{4}[A-Z]{1}"
REGEX_IPV4 = (r"[0-9]{1,3}\." * 3) + r"[0-9]{1,3}"
REGEX_IPV6 = (r"[0-9a-fA-F]{1,4}:" * 7) + r"[0-9a-fA-F]{1,4}"
REGEX_IFSC = r"[A-Z]{4}0[A-Z0-9]{6}"
REGEX_UPI = r"[a-zA-Z0-9\.\-]{2,256}@[a-zA-Z][a-zA-Z]{2,64}"
REGEX_MAC_ADDRESS = r"([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})"
REGEX_METRIC_WEIGHT = r"[\d\.]+[ ]?[k]?g"


# RegEx chars (append them to the patterns if needed):
REGEX_START = "^"
REGEX_END = "$"


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


def find(text, pattern, case_sensitive = True, dot_all = False):

    """
    Returns a list of substrings that match the given RegEx pattern in the input text.
    :param text: The text in which the pattern needs to be found.
    :param pattern: The RegEx pattern to look for.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: An array (list) of substring that match the pattern. Can be an empty list as well.
    """

    # Prepare the flags:
    flags = 0
    if not case_sensitive: flags |= re.IGNORECASE
    if dot_all: flags |= re.DOTALL

    # Perform the RegEx operation, and clean the results:
    matches = [match if type(match) is str else match[1] for match in re.findall(pattern, text, flags = flags)]
    matches = [match for match in matches if len(match) > 0]

    # Return the results:
    return matches


# ---------------------------------------------------------------------------------------------------------------------


def find_first(text, pattern, case_sensitive = True, dot_all = False):

    """
    Returns the first substring that matches the given RegEx pattern in the input text.
    :param text: The text in which the pattern needs to be found.
    :param pattern: The RegEx pattern to look for.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: The first match as a string, or None if no match was found..
    """

    matches = find(
        text = text,
        pattern = pattern,
        case_sensitive = case_sensitive,
        dot_all = dot_all
    )

    if not matches: return None
    else: return matches[0]


# ---------------------------------------------------------------------------------------------------------------------


def replace(text, pattern, substitute_text, case_sensitive = True, dot_all = False):

    """
    Replaces any substring in the text that matches the RegEx pattern.
    :param text: The text in which the substitutions need to be made.
    :param pattern: The RegEx pattern that needs to be substituted.
    :param substitute_text: The text that will replace the matches that were found.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: The text with the substitutions. If no matches are found, the original string is returned.
    """

    # Prepare the flags:
    flags = 0
    if not case_sensitive: flags |= re.IGNORECASE
    if dot_all: flags |= re.DOTALL

    # Perform the RegEx operation, and return the results:
    return re.sub(pattern, substitute_text, text, flags = flags)


# ---------------------------------------------------------------------------------------------------------------------


def search(text, pattern, case_sensitive = True, dot_all = False):

    """
    Checks if the given RegEx pattern occurs ANYWHERE in the text that was provided.
    :param text: The text that needs to be matched against the pattern.
    :param pattern: The RegEx pattern to look for.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: True if the pattern matches, else False.
    """

    # Prepare the flags:
    flags = 0
    if not case_sensitive: flags |= re.IGNORECASE
    if dot_all: flags |= re.DOTALL

    # Perform the RegEx operation, and return the results:
    if re.search(pattern, text, flags = flags): return True
    else: return False


# ---------------------------------------------------------------------------------------------------------------------


def match(text, pattern, case_sensitive = True, dot_all = False):

    """
    Checks if the given text matches the RegEx pattern that was provided. The check is made only at the start of the
    input string.
    :param text: The text that needs to be matched against the pattern.
    :param pattern: The RegEx pattern to look for.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: True if the pattern matches, else False.
    """

    # Prepare the flags:
    flags = 0
    if not case_sensitive: flags |= re.IGNORECASE
    if dot_all: flags |= re.DOTALL

    # Perform the RegEx operation, and return the results:
    if re.match(pattern, text, flags = flags): return True
    else: return False


# ---------------------------------------------------------------------------------------------------------------------


def split(text, pattern, case_sensitive = True, dot_all = False):

    """
    Splits an input string based on the pattern that is being matched.
    :param text: The text that needs to be matched against the pattern.
    :param pattern: The RegEx pattern to look for.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: True if the pattern matches, else False.
    """

    # Prepare the flags:
    flags = 0
    if not case_sensitive: flags |= re.IGNORECASE
    if dot_all: flags |= re.DOTALL

    # Perform the RegEx operation, and return the results:
    substrings = re.split(pattern, text, flags = flags)
    if len(substrings) > 0 and substrings[0] == "": substrings.pop(0)
    return substrings


# ---------------------------------------------------------------------------------------------------------------------


def to_json(text, pattern, case_sensitive = True, dot_all = False):

    """
    Gives out a dict from the extracted features in a string. It is based on the concept of Named Groups.
    Consider the following example (assuming the search is case-insensitive):
    TEXT: "UPI/309258561479/14:17:35/UPI/omsainurses@okhdfc"
    PATTERN: "upi/.*/(?P<time>.*)/.*/(?P<ref>.*)"
    RESULT: {'time': '14:17:35', 'ref': 'omsainurses@okhdfc'}
    :param text: The text that needs to be matched against the pattern.
    :param pattern: The RegEx pattern to look for.
    :param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
    :param dot_all: Allow all characters to be matched in ".".
    :return: A dict with all the extracted features.
    """

    # Prepare the flags:
    flags = 0
    if not case_sensitive: flags |= re.IGNORECASE
    if dot_all: flags |= re.DOTALL

    # Perform the RegEx operation, and return the results:
    matches = re.search(pattern, text, flags = flags)
    regex_json = matches.groupdict() if matches else {}
    return regex_json


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    import json_utils

    for narr in [
        # r"UPI/309258561479/14:17:35/UPI/omsainurses@okhdf cb",
        # r"UPI/309258561479/14:17:35/omsainurses@okhdf cb",
        # r"NEFT-N095232403538009-RELIGARE BROKING LIMITED MAI",
        r"Product listing - My product - 75g - Super combo pack",
        r"Product listing - My product - 750 g",
        r"0.5 kgs mini pack"
    ]:

        result = to_json(
            narr,
            r"upi/.*/(?P<time>.*)/.*/(?P<ref>.*)",
            case_sensitive = False
        )
        print(result)
