#!/usr/bin/python
# -*- coding: utf-8 -*-

# --------------------------------------------------------------------------- #
#    Isida-NG Jabber Bot (fork of iSida)                                      #
#    Original iSida Copyright (C) 2009-2015 diSabler <dsy@dsy.name>           #
#    Isida-NG Copyright (C) 2026 Luciferus <luciferus@ubunix.pro>             #
#    Project home: https://git.ubunix.pro/Luciferus/isida-ng                  #
# --------------------------------------------------------------------------- #

import re
import os

turn_base = []

# --- ЗАГРУЗКА СЛОВАРЯ ИСКЛЮЧЕНИЙ ИЗ ФАЙЛА ---
IGNORE_FILE = data_folder % 'ignore_turn.txt'
_IGNORE_WORDS = set()

# --- ПРЕДЛОГИ, АРТИКЛИ, МЕЖДОМЕТИЯ ---
STOP_WORDS = {
    'a', 'an', 'the', 'and', 'or', 'but', 'for', 'nor', 'on', 'at', 'to', 'by', 'in',
    'of', 'off', 'over', 'under', 'up', 'down', 'into', 'through', 'across', 'between',
    'among', 'without', 'within', 'upon', 'toward', 'until', 'since', 'after', 'before',
    'about', 'against', 'along', 'around', 'behind', 'below', 'beneath', 'beside',
    'beyond', 'during', 'except', 'from', 'like', 'near', 'onto', 'opposite', 'outside',
    'per', 'plus', 'round', 'save', 'since', 'than', 'till', 'unto', 'via', 'with',
    'so', 'as', 'than', 'that', 'these', 'those', 'some', 'any', 'no', 'all', 'each',
    'every', 'both', 'neither', 'either', 'then', 'now', 'just', 'only', 'very',
    'too', 'also', 'well', 'oh', 'ah', 'ok', 'okay', 'yes', 'yeah', 'no', 'nope',
    'maybe', 'perhaps', 'probably', 'definitely', 'certainly'
}

# --- АНТИ-БИГРАММЫ ---
ANTI_BIGRAMS = {
    'yfg', 'hbvth', 'pf,sd', 'ftim', 'crbq', 'zpsr', 'кфт', 'вщь', 'цщ', 'квы',
    'gbitim', 'gbit', 'yfghb', 'djn', 'nfr', 'lfkmit', 'ghjcnj', 'sdftim',
    'gthtrk', 'xbnmcz', 'heccrbq', 'zpsr', 'yfghbvth', 'ghb', 'gtht', 'gtrk',
    'xbn', 'cz', 'hecc', 'rbq', 'zps', 'yfg', 'hbv', 'th', 'zpsr', 'pf,sd',
    'zxc', 'qwe', 'asd', 'rty', 'fgh', 'vbn', 'uio', 'jkl', 'xcv', 'bnm',
    'егы', 'шщз', 'щзх', 'зхъ', 'фыв', 'джэ', 'ячс', 'бюё', 'nmcz', 'k.xb',
    'gthtr', 'кфт', 'вщь', 'цщ', 'квы', 'pfrjy', 'xbkcz', 'kbvbn', ',scnh',
    'jltq', 'cndbt', 'pf,', 'sdft', 'im?', 'cvtyb', 'nm&', 'heccr', 'ccrbq',
    'vexf', 'xf.cm', '.cm', 'cj cv', 'tifyy', 'yysv', 'utyf', 'kexituj', ',tp',
    'tot', 'to`', ',kz'
}

# --- ЗАГРУЗКА СЛОВАРЯ ---
def _load_ignore_words():
    global _IGNORE_WORDS
    ignore_set = set()
    if os.path.exists(IGNORE_FILE):
        try:
            with open(IGNORE_FILE, 'r', encoding='utf-8') as f:
                for line in f:
                    word = line.strip().lower()
                    if word and not word.startswith('#'):
                        ignore_set.add(word)
        except Exception as e:
            pprint('*** ERROR loading ignore_turn.txt: %s' % str(e), 'red')
    _IGNORE_WORDS = ignore_set
    return ignore_set

_load_ignore_words()

def _reload_ignore():
    return _load_ignore_words()

def _save_ignore_word(word, action='add'):
    if not word:
        return False
    lines = []
    if os.path.exists(IGNORE_FILE):
        try:
            with open(IGNORE_FILE, 'r', encoding='utf-8') as f:
                lines = f.readlines()
        except:
            pass
    word_lower = word.lower()
    if action == 'add':
        for line in lines:
            if line.strip().lower() == word_lower:
                return False
        lines.append(word_lower + '\n')
    else:
        lines = [line for line in lines if line.strip().lower() != word_lower]
    try:
        with open(IGNORE_FILE, 'w', encoding='utf-8') as f:
            f.writelines(lines)
        _reload_ignore()
        return True
    except:
        return False

# --------------------------------------------------------------------------- #
# ТАБЛИЦЫ РАСКЛАДОК                                                           #
# --------------------------------------------------------------------------- #

EN_TO_RU = {
    'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н',
    'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', 'a': 'ф', 's': 'ы',
    'd': 'в', 'f': 'а', 'g': 'п', 'h': 'р', 'j': 'о', 'k': 'л',
    'l': 'д', 'z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и',
    'n': 'т', 'm': 'ь', '[': 'х', ']': 'ъ', ';': 'ж', "'": 'э',
    ',': 'б', '.': 'ю', '/': '.', '\\': '/', '~': 'Ё', '{': 'Х',
    '+': 'Ё', ':': 'Ж', '"': 'Э', '<': 'Б', '>': 'Ю', '?': ',',
    '&': '?', '@': '"', '#': '№', '$': ';', '^': ':', '`': 'ё'
}

RU_TO_EN = {v: k for k, v in EN_TO_RU.items()}

# --------------------------------------------------------------------------- #
# ПРОВЕРКА НА ОСМЫСЛЕННОСТЬ (БИГРАММЫ + АНТИ)                                 #
# --------------------------------------------------------------------------- #

RUSSIAN_COMMON = {
    'ст', 'но', 'то', 'на', 'ен', 'ра', 'во', 'ко', 'ка', 'де', 'по', 'не', 'ре',
    'пр', 'ов', 'ол', 'ал', 'ил', 'ел', 'ан', 'ин', 'он', 'ен', 'ят', 'ит', 'ат',
    'ет', 'ют', 'ут', 'ать', 'ить', 'еть', 'овать', 'евать', 'ешь', 'ваешь', 'ишь',
    'яз', 'друг', 'ой', 'ду', 'пере', 'ключ', 'пох', 'ху', 'еба', 'мер', 'при', 'про',
    'еньк', 'ере', 'дер', 'мо', 'ма', 'вы', 'ме', 'му', 'ми', 'прим', 'гой', 'дру',
    'мер', 'мон', 'тор', 'кол', 'нка', 'фон', 'сма', 'арт', 'оч', 'бут', 'мы', 'шь',
    'ча', 'ща', 'жи', 'ши', 'чу', 'щу', 'ру', 'рус', 'кий', 'ки', 'кри', 'со', 'сов',
    'ябл', 'ок', 'вин', 'гра', 'тык', 'ва', 'ба', 'ара', 'кла', 'ату', 'укр', 'шен',
    'ие', 'ия', 'бат', 'льо', 'взв', 'во', 'ву', 'ве', 'ню', 'пре', 'пру', 'мат',
    'ерщ', 'ина', 'ста', 'анк', 'ее', 'её', 'лес', 'су', 'пья', 'ых', 'ных', 'ге', 'ген',
    'еще', 'ещё', 'че', 'онь', 'ос', 'пч', 'шм', 'го', 'ог', 'ом', 'тра', 'га', 'аг',
    'уг', 'шг', 'бе', 'без', 'бес', 'ерк', 'ла', 'лу', 'ло', 'лю', 'бля', 'ять'
}

ENGLISH_COMMON = {
    'th', 'he', 'in', 'en', 'an', 're', 'er', 'on', 'at', 'nd', 'st', 'or', 'nt',
    'ea', 'ti', 'is', 'ou', 'ar', 'es', 'te', 'of', 'it', 'al', 'ri', 'ha', 've',
    'ed', 'se', 'me', 'de', 'le', 'co', 'ma', 'li', 'ca', 'be', 'ne', 'ra', 'ro',
    've', 'el', 'me', 'ta', 'te', 'ic', 'al', 'om', 'ut', 'ur', 'et', 'em', 'ol',
    'tion', 'ing', 'ent', 'ion', 'tio', 'and', 'her', 'for', 'you', 'tha', 'ter'
}

def _is_meaningful_word(word):
    if len(word) < 3:
        return True

    word_lower = word.lower()

    # Проверяем анти-биграммы
    for anti in ANTI_BIGRAMS:
        if anti in word_lower:
            return False

    # Проверяем обычные биграммы
    bigrams = [word_lower[i:i+2] for i in range(len(word_lower)-1)]
    ru_score = sum(1 for bg in bigrams if bg in RUSSIAN_COMMON)
    en_score = sum(1 for bg in bigrams if bg in ENGLISH_COMMON)

    return ru_score > 0 or en_score > 0

# --------------------------------------------------------------------------- #
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ                                                     #
# --------------------------------------------------------------------------- #

def _is_command(word):
    if word.startswith(('.', '/', '#', '@')):
        return True
    if re.match(r'(svn|http[s]?|ftp)://|magnet:\?', word, re.S|re.I|re.U):
        return True
    return False

def _is_abbreviation(word):
    if len(word) >= 2 and word.isupper() and word.isalpha():
        return True
    return False

def _should_skip(word):
    if len(word) < 2:
        return True
    if _is_command(word) or _is_abbreviation(word):
        return True
    if word.lower() in _IGNORE_WORDS:
        return True
    if word.lower() in STOP_WORDS:
        return True
    return False

def fix_layout(text, direction='auto'):
    if not text:
        return text
    if direction == 'auto':
        en_count = sum(1 for c in text if c.lower() in EN_TO_RU)
        ru_count = sum(1 for c in text if c in RU_TO_EN)
        direction = 'en2ru' if en_count >= ru_count else 'ru2en'

    result = []
    for char in text:
        if direction == 'en2ru':
            if char.lower() in EN_TO_RU:
                replacement = EN_TO_RU[char.lower()]
                if char.isupper():
                    replacement = replacement.upper()
                result.append(replacement)
            else:
                result.append(char)
        else:
            if char in RU_TO_EN:
                replacement = RU_TO_EN[char]
                if char.isupper():
                    replacement = replacement.upper()
                result.append(replacement)
            else:
                result.append(char)
    return ''.join(result)

# --------------------------------------------------------------------------- #
# ОСНОВНАЯ ЛОГИКА ПЕРЕКЛЮЧЕНИЯ                                                #
# --------------------------------------------------------------------------- #

def turner_raw(text, jid, nick):
    global turn_base
    to_turn = text

    if not text:
        for tmp in turn_base:
            if tmp[0] == jid and tmp[1] == nick:
                turn_base.remove(tmp)
                to_turn = tmp[2]
                break

    if not to_turn:
        return None

    msg = ''
    if to_turn[:3] == '/me':
        msg = '*%s' % nick
        to_turn = to_turn[3:]
    elif ': ' in to_turn and to_turn.split(': ', 1)[0] not in ['http', 'https', 'ftp']:
        parts = to_turn.split(': ', 1)
        msg = '%s:' % parts[0]
        to_turn = parts[1]

    words = re.findall(r'\S+', to_turn)
    result_parts = []

    for word in words:
        if _should_skip(word):
            result_parts.append(word)
            continue

        ru_letters = all(c in RU_TO_EN for c in word)
        en_letters = all(c.lower() in EN_TO_RU for c in word)

        if not ru_letters and not en_letters:
            result_parts.append(word)
            continue

        if ru_letters:
            switched = fix_layout(word, 'ru2en')
        else:
            switched = fix_layout(word, 'en2ru')

        if _is_meaningful_word(switched):
            result_parts.append(switched)
        else:
            result_parts.append(word)

    result = ' '.join(result_parts)

    if msg:
        result = '%s %s' % (msg, result)

    result = result.strip()

    if get_config(getRoom(jid), 'censor'):
        result = to_censore(result, jid)

    return result

# --------------------------------------------------------------------------- #
# КОМАНДЫ НАСТРОЙКИ                                                           #
# --------------------------------------------------------------------------- #

def turner(type, jid, nick, text):
    if not text and type != 'groupchat':
        send_msg(type, jid, nick, L('Not allowed in private!', '%s/%s' % (jid, nick)))
        return
    to_turn = turner_raw(text, jid, nick)
    if to_turn:
        send_msg(type, jid, ['', nick][type == 'groupchat'], to_turn)
    else:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))

def turn_reload(type, jid, nick, text):
    if get_level(jid, nick)[0] < 9:
        send_msg(type, jid, nick, L('You need bot owner access level.', '%s/%s' % (jid, nick)))
        return
    count = len(_reload_ignore())
    msg = L('Ignore list reloaded. %s words loaded from %s', '%s/%s' % (jid, nick)) % (count, IGNORE_FILE)
    send_msg(type, jid, nick, msg)

def turn_ignore(type, jid, nick, text):
    if get_level(jid, nick)[0] < 9:
        send_msg(type, jid, nick, L('You need bot owner access level.', '%s/%s' % (jid, nick)))
        return

    parts = text.strip().split(' ', 1)
    if len(parts) != 2:
        send_msg(type, jid, nick, L('Usage: turn_ignore add|del <word>', '%s/%s' % (jid, nick)))
        return

    action, word = parts[0].lower(), parts[1].strip()
    if not word:
        send_msg(type, jid, nick, L('Word cannot be empty.', '%s/%s' % (jid, nick)))
        return

    if action not in ['add', 'del']:
        send_msg(type, jid, nick, L('Usage: turn_ignore add|del <word>', '%s/%s' % (jid, nick)))
        return

    if _save_ignore_word(word, action):
        if action == 'add':
            msg = L('Word "%s" added to ignore list.', '%s/%s' % (jid, nick)) % word
        else:
            msg = L('Word "%s" removed from ignore list.', '%s/%s' % (jid, nick)) % word
    else:
        if action == 'add':
            msg = L('Word "%s" already in ignore list.', '%s/%s' % (jid, nick)) % word
        else:
            msg = L('Word "%s" not found in ignore list.', '%s/%s' % (jid, nick)) % word

    send_msg(type, jid, nick, msg)

def append_to_turner(room, jid, nick, type, text):
    global turn_base
    for tmp in turn_base:
        if tmp[0] == room and tmp[1] == nick:
            try:
                turn_base.remove(tmp)
            except:
                pass
            break
    turn_base.append((room, nick, text))

def remove_from_turner(room, jid, nick, type, text):
    global turn_base
    if type == 'unavailable':
        for tmp in turn_base:
            if tmp[0] == room and tmp[1] == nick:
                try:
                    turn_base.remove(tmp)
                except:
                    pass
                break

def autoturn(room, jid, nick, type, text):
    if get_config(room, 'autoturn') and type == 'groupchat':
        if cur_execute_fetchone('select * from commonoff where room=%s and cmd=%s', (room, 'turn')):
            return

        nowname = get_xnick(room)
        if nick == nowname:
            return

        text = re.sub('^%s[,:]\ ' % re.escape(nowname), '', text.strip())

        tmp = text.lower()
        if ': ' in tmp:
            tmp = tmp.split(': ', 1)[1]

        en_count = sum(1 for c in tmp if c in 'qwertyuiopasdfghjklzxcvbnm[];\',./')
        ru_count = sum(1 for c in tmp if c in 'йцукенгшщзхъфывапролджэячсмитьбюё')
        total = en_count + ru_count

        if total > 0 and en_count / total > 0.3:
            to_turn = turner_raw(text, room, nick)
            if to_turn and to_turn != text:
                pprint('Autoturn text: %s/%s [%s] %s > %s' % (room, nick, jid, text, to_turn), 'dark_gray')
                send_msg(type, room, '', to_turn)
                return True

# --------------------------------------------------------------------------- #
# РЕГИСТРАЦИЯ                                                                #
# --------------------------------------------------------------------------- #

global execute, message_control, presence_control, message_act_control

message_control = [append_to_turner]
presence_control = [remove_from_turner]
message_act_control = [autoturn]

execute = [
    (3, 'turn', turner, 2, 'Turn text from one layout to another.\nturn <text> - switch layout for text\nturn - switch layout for last message'),
    (9, 'turn_reload', turn_reload, 2, 'Reload ignore list from data/ignore_turn.txt'),
    (9, 'turn_ignore', turn_ignore, 2, 'Add or remove word from ignore list.\nturn_ignore add|del <word>'),
]
