import re
import unicodedata
from itertools import groupby

def between(c, a, b):
    return ord(a) <= ord(c) <= ord(b)

LATIN = lambda c: between(c, '\u0000', '\u024F')
KATAKANA = lambda c: between(c, '\u30A0', '\u30FF')
HIRAGANA = lambda c: between(c, '\u3040', '\u309F')
CJK = lambda c: between(c, '\u4E00', '\u9FFF')

modes = [
    "LATIN",
    "ARABIC",
    "HEBREW",
    "SPACE",
    "CJK",
    "KATAKANA"
]

def segment(txt, mode="LATIN", includeNames=False):
    current_mode = mode
    runs = [[mode]]
    for i, c in enumerate(txt):
        try:
            n = unicodedata.name(c)
        except ValueError:
            n = "PRIVATE"
        for m in modes:
            if m in n:
                if current_mode != m:
                    current_mode = m
                    runs.append([m])
        runs[-1].append((n, c) if includeNames else c)
    if len(runs[0]) == 0:
        runs = runs[1:]
    
    for idx, (mode, *run) in enumerate(runs):
        p = runs[idx-1] if idx > 0 else None
        n = runs[idx+1] if len(runs) > idx+1 else None
        if p and n:
            if mode == "SPACE" and p[0] == n[0]:
                runs[idx][0] = p[0]
    
    grouped_runs = []
    for k, g in groupby(runs, lambda e: e[0]):
        txt = "".join(["".join(g[1:]) for g in list(g)])
        if txt:
            found_modes = set()
            for c in txt:
                try:
                    n = unicodedata.name(c)
                except ValueError:
                    n = "PRIVATE"
                for m in modes:
                    if m in n:
                        found_modes.add(m)
            grouped_runs.append((found_modes, txt))
    
    # reverse number ranges in arabic
    for idx, (cats, line) in enumerate(grouped_runs):
        if "ARABIC" in cats or "HEBREW" in cats:
            grouped_runs[idx] = (cats, re.sub("[0-9]+", lambda m: m.group()[::-1], line))

    return grouped_runs

if __name__ == "__main__":
    from pprint import pprint
    s = "ABC (جاف + رطب (ما قبل"
    s = "(رطب (ما قبل"
    #s = "Ali الملخبط Boba"
    s = "وصل الإستيرِوﬂLim/Satلل"
    runs = segment(s, "LATIN")
    pprint(runs)