import re
from fractions import Fraction

from abcforge.vendor.abctoolkit.utils import (
    SquareBracket_re,
    remove_square_bracket_information_field,
    remove_wrapped_content,
)


def calculate_single_note_duration(note_text: str):
    note_text = note_text.strip()
    dur_text = re.sub(r'[a-zA-Z]', '', note_text)
    if dur_text == '':
        dur = Fraction('1')
    elif dur_text == '/':
        dur = Fraction('1/2')
    elif dur_text.startswith('/'):
        dur = Fraction('1' + dur_text)
    else:
        dur = Fraction(dur_text)
    return dur


def round_fraction(f: Fraction, tolerance=Fraction(1, 64)):
    nearest_int = round(float(f))
    if abs(f - nearest_int) <= tolerance:
        return Fraction(nearest_int)
    else:
        return f


def calculate_bartext_duration(bar_text: str):
    original_bartext = bar_text

    note_set = ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g', 'X', 'x', 'Z', 'z']
    useless_char_set = ['P', 'S', 'O', 's', 'o', 'u', 'v', 'U', 'V', 'T', 'M', '.', '-', ')']
    pitch_sign_set = ['_', '=', '^', '\'', ',', '~']  # FIX 2025-05-22: added '~'

    bar_text = remove_wrapped_content(bar_text, ['!!', '{}', '""'])
    bar_text = remove_square_bracket_information_field(bar_text)

    bracket_contents = re.findall(SquareBracket_re, bar_text)
    if len(bracket_contents) > 0:
        for bracket_content in bracket_contents:
            for char in bracket_content:
                if char not in note_set + pitch_sign_set + ['[', ']', '-']:
                    raise Exception('Illegal symbol in [] {}'.format(bracket_content))
            bar_text = bar_text.replace(bracket_content, 'C')

    bar_text = bar_text.replace('x', 'z')
    bar_text = bar_text.replace('X', 'z')

    try:
        for char in useless_char_set + pitch_sign_set:
            bar_text = bar_text.replace(char, '')
        index_to_detele = []
        for i, char in enumerate(bar_text):
            if bar_text[i] == '(' and (len(bar_text) == i + 1 or not bar_text[i + 1].isnumeric()):
                index_to_detele.append(i)
        bar_text_list = list(bar_text)
        for index in reversed(index_to_detele):
            bar_text_list.pop(index)

        bar_text = ''.join(bar_text_list)

        ele_list = []
        index = 0
        for i, char in enumerate(bar_text):
            if i < index:
                continue
            if char in note_set:
                index = i + 1
                while index < len(bar_text):
                    if bar_text[index] in note_set + ['(', '<', '>']:
                        break
                    index += 1
                ele_list.append(bar_text[i:index])
            elif char == '(':
                index = i + 1
                while bar_text[index].isnumeric() or bar_text[index] == ':':
                    index += 1
                ele_list.append(bar_text[i:index])
            elif char in ['<', '>']:
                index = i + 1
                while bar_text[index] in ['<', '>']:
                    index += 1
                ele_list.append(bar_text[i:index])

        ele_list = [ele.strip() for ele in ele_list]
        dur_list = [-1] * len(ele_list)

        for i, ele in enumerate(ele_list):
            if ele[0].isalpha():
                dur = calculate_single_note_duration(ele)
                dur_list[i] = dur

        for i, ele in enumerate(ele_list):
            if ele[0] in ['<', '>']:
                dur_list[i] = dur_list[i - 1] + dur_list[i + 1]
                dur_list[i - 1] = 0
                dur_list[i + 1] = 0

        uncertain_flag = True if -1 in dur_list else False

        while uncertain_flag:
            uncertain_flag = False
            for i, ele in enumerate(ele_list):
                if dur_list[i] == -1 and ele[0] == '(':
                    uncertain_flag = True
                    if ele == '(3':
                        p, q, r = 3, 2, 3
                    else:
                        p = int(ele.lstrip('(').split(':')[0])
                        q = int(ele.lstrip('(').split(':')[1])
                        r = int(ele.lstrip('(').split(':')[2])
                    if p == 0 or q == 0 or r == 0:
                        raise Exception('Illegal (p:q:r ' + '({}:{}:{}'.format(p, q, r))
                    left_bound = i + 1
                    index = i + 1
                    note_count = 0
                    local_uncertain_flag = False
                    while note_count < r:
                        if ele_list[index][0].isalpha():
                            note_count += 1
                        elif dur_list[index] == -1:
                            local_uncertain_flag = True
                            break
                        index += 1
                    right_bound = index
                    if local_uncertain_flag:
                        continue
                    else:
                        dur_list[i] = sum(dur_list[left_bound:right_bound]) * Fraction(numerator=q, denominator=p)
                        dur_list[i] = round_fraction(dur_list[i])
                        dur_list[left_bound:right_bound] = [0] * (right_bound - left_bound)
                elif dur_list[i] == -1:
                    raise Exception('Uncertain element')

        bar_duration = sum(dur_list)

    except Exception as e:
        print(original_bartext, e)
        return None

    return bar_duration
