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

# --------------------------------------------------------------------------- #
#                                                                             #
#    Isida-NG Jabber Bot (fork of iSida)                                      #
#    Isida-NG Copyright (C) 2026 Luciferus <luciferus@ubunix.pro>             #
#    Project home: https://git.ubunix.pro/Luciferus/Isida-NG                  #
#                                                                             #
#    Tools plugin                                                             #
#                                                                             #
#    This program is free software: you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation, either version 3 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
#                                                                             #
# --------------------------------------------------------------------------- #

import re
import string
import math
import random
import uuid

calc_last_res = {}

# --------------------------------------------------------------------------- #
# ОПРЕДЕЛЕНИЕ ЯЗЫКА ПО НАСТРОЙКАМ КОМНАТЫ                                     #
# --------------------------------------------------------------------------- #

def _get_room_lang(room):
    try:
        lang = get_config(room, 'set_default_locale')
        if lang in ('ru', 'ua', 'en'):
            return lang
    except:
        pass
    return 'en'

# --------------------------------------------------------------------------- #
# ОТПРАВКА В ПРИВАТ (РАБОТАЕТ КАК В ДРУГИХ ПЛАГИНАХ)                          #
# --------------------------------------------------------------------------- #

def _send_private(type, jid, nick, msg):
    if type == 'groupchat':
        send_msg('chat', jid, nick, msg)
    else:
        send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# СПРАВКА                                                                     #
# --------------------------------------------------------------------------- #

_CMD_HELP = {
    'calc': {
        'ru': '"calc" - калькулятор.\nФормат: calc выражение\nПримеры: calc 2+2, calc sqrt(16), calc 10!, calc Ans*2',
        'ua': '"calc" - калькулятор.\nФормат: calc вираз\nПриклади: calc 2+2, calc sqrt(16), calc 10!, calc Ans*2',
        'en': '"calc" - calculator.\nFormat: calc expression\nExamples: calc 2+2, calc sqrt(16), calc 10!, calc Ans*2'
    },
    'dpi': {
        'ru': '"dpi" - расчёт DPI экрана.\nФормат: dpi ширина высота диагональ\nПример: dpi 1920 1080 24',
        'ua': '"dpi" - розрахунок DPI екрану.\nФормат: dpi ширина висота діагональ\nПриклад: dpi 1920 1080 24',
        'en': '"dpi" - calculate screen DPI.\nFormat: dpi width height diagonal\nExample: dpi 1920 1080 24'
    },
    'cap': {
        'ru': '"cap" - конвертер единиц цифровой информации между B, KB, MB, GB, TB.\nФормат: cap число единица > единица\nПримеры: cap 1 MB > B, cap 1024 B > KB, cap 2.5 GB > MB',
        'ua': '"cap" - конвертер одиниць цифрової інформації між B, KB, MB, GB, TB.\nФормат: cap число одиниця > одиниця\nПриклади: cap 1 MB > B, cap 1024 B > KB, cap 2.5 GB > MB',
        'en': '"cap" - convert bytes between B, KB, MB, GB, TB.\nFormat: cap number unit > unit\nExamples: cap 1 MB > B, cap 1024 B > KB, cap 2.5 GB > MB'
    },
    'rate': {
        'ru': '"rate" - конвертер скорости передачи данных между bps, Kbps, Mbps, Gbps.\nФормат: rate число единица > единица\nПримеры: rate 1 Mbps > Kbps, rate 8 M > K',
        'ua': '"rate" - конвертер швидкості передачі даних між bps, Kbps, Mbps, Gbps.\nФормат: rate число одиниця > одиниця\nПриклади: rate 1 Mbps > Kbps, rate 8 M > K',
        'en': '"rate" - convert data rate between bps, Kbps, Mbps, Gbps.\nFormat: rate number unit > unit\nExamples: rate 1 Mbps > Kbps, rate 8 M > K'
    },
    'eff': {
        'ru': '"eff" - конвертер скорости ↔ объёма данных.\nФормат: eff число единица [время_в_сек]\nЕдиницы: MB, KB, GB, TB (байты) | Mb, Kb, Gb, bit (биты)\nПримеры: eff 60 MB, eff 60 MB 10, eff 600 Mb, eff 600 Mb 10',
        'ua': '"eff" - конвертер швидкості ↔ обʼєму даних.\nФормат: eff число одиниця [час_в_сек]\nОдиниці: MB, KB, GB, TB (байти) | Mb, Kb, Gb, bit (біти)\nПриклади: eff 60 MB, eff 60 MB 10, eff 600 Mb, eff 600 Mb 10',
        'en': '"eff" - data rate ⇄ volume converter.\nFormat: eff number unit [time_in_sec]\nUnits: MB, KB, GB, TB (bytes) | Mb, Kb, Gb, bit (bits)\nExamples: eff 60 MB, eff 60 MB 10, eff 600 Mb, eff 600 Mb 10'
    },
    'hex': {
        'ru': '"hex" - перевод числа в шестнадцатеричную систему (HEX).\nФормат: hex число\nПример: hex 255 → 0xFF',
        'ua': '"hex" - переведення числа в шістнадцяткову систему (HEX).\nФормат: hex число\nПриклад: hex 255 → 0xFF',
        'en': '"hex" - convert number to hexadecimal.\nFormat: hex number\nExample: hex 255 → 0xFF'
    },
    'dec': {
        'ru': '"dec" - перевод HEX в десятичное число.\nФормат: dec HEX\nПримеры: dec FF → 255, dec 0xFF → 255',
        'ua': '"dec" - переведення HEX у десяткове число.\nФормат: dec HEX\nПриклади: dec FF → 255, dec 0xFF → 255',
        'en': '"dec" - convert HEX to decimal.\nFormat: dec HEX\nExamples: dec FF → 255, dec 0xFF → 255'
    },
    'bin': {
        'ru': '"bin" - перевод числа в двоичную систему.\nФормат: bin число\nПример: bin 15 → 0b1111',
        'ua': '"bin" - переведення числа в двійкову систему.\nФормат: bin число\nПриклад: bin 15 → 0b1111',
        'en': '"bin" - convert number to binary.\nFormat: bin number\nExample: bin 15 → 0b1111'
    },
    'percent': {
        'ru': '"percent" - вычисление процента от числа.\nФормат: percent процент число\nПример: percent 30 200 → 60',
        'ua': '"percent" - обчислення відсотка від числа.\nФормат: percent відсоток число\nПриклад: percent 30 200 → 60',
        'en': '"percent" - calculate percentage.\nFormat: percent percent number\nExample: percent 30 200 → 60'
    },
    'random': {
        'ru': '"random" - генерация случайного целого числа в диапазоне.\nФормат: random мин макс\nПример: random 1 100 → 42',
        'ua': '"random" - генерація випадкового цілого числа в діапазоні.\nФормат: random мін макс\nПриклад: random 1 100 → 42',
        'en': '"random" - generate random integer in range.\nFormat: random min max\nExample: random 1 100 → 42'
    },
    'uuid': {
        'ru': '"uuid" - генерация UUID v4.\nФормат: uuid\nПример: uuid → f47ac10b-58cc-4372-a567-0e02b2c3d479',
        'ua': '"uuid" - генерація UUID v4.\nФормат: uuid\nПриклад: uuid → f47ac10b-58cc-4372-a567-0e02b2c3d479',
        'en': '"uuid" - generate UUID v4.\nFormat: uuid\nExample: uuid → f47ac10b-58cc-4372-a567-0e02b2c3d479'
    }
}

def tools_help(type, jid, nick, text):
    room = getRoom(jid)
    lang = _get_room_lang(room)
    if lang not in ('ru', 'ua', 'en'):
        lang = 'en'

    if lang == 'ru':
        title = 'Инструменты (tools.py) — список команд:'
        footer = 'Отправлено в приват.'
    elif lang == 'ua':
        title = 'Інструменти (tools.py) — список команд:'
        footer = 'Відправлено в приват.'
    else:
        title = 'Tools (tools.py) — command list:'
        footer = 'Sent to private message.'

    lines = [title, '']

    for cmd in sorted(_CMD_HELP.keys()):
        desc = _CMD_HELP[cmd].get(lang, _CMD_HELP[cmd]['en'])
        lines.append(desc)

    lines.append('')
    lines.append(footer)

    help_text = '\n'.join(lines)

    _send_private(type, jid, nick, help_text)

# --------------------------------------------------------------------------- #
# ОСТАЛЬНЫЕ КОМАНДЫ                                                          #
# --------------------------------------------------------------------------- #
def calc(type, jid, nick, text):
    global calc_last_res
    rn = '%s/%s' % (jid, nick)
    text = text.strip()
    if not text:
        send_msg(type, jid, nick, L('Usage: calc <expression>', rn))
        return

    # Ans — case-insensitive, хранится per (jid, nick).
    if re.search(r'\b[Aa][Nn][Ss]\b', text):
        prev = calc_last_res.get(jid, {}).get(nick)
        if not prev:
            send_msg(type, jid, nick, L('No previous result (Ans is empty)', rn))
            return
        text = re.sub(r'\b[Aa][Nn][Ss]\b', lambda mm: str(prev), text)

    # Факториал через !  (5! -> math.factorial(5)) — ПОСЛЕ math.-префикса,
    # чтобы не дать ему снова обрамить math.factorial.
    legal = string.digits + string.ascii_letters + '*/+-()=^.!, '
    if any(c not in legal for c in text) or '**' in text or '//' in text:
        send_msg(type, jid, nick, L('Expression unacceptable!', rn))
        return

    try:
        # Префикс math. для функций: sqrt, cbrt, sin, cos, tan, log, log10, pow, factorial, ...
        expr = re.sub(r'([^a-zA-Z0-9_.]|\A)([a-zA-Z]\w*)',
                      lambda mm: mm.group(1) + 'math.' + mm.group(2).lower(), text)
        expr = re.sub(r'([0-9.]+)!', r'math.factorial(\1)', expr)
        result = eval(expr)
        result_str = str(result)
        calc_last_res.setdefault(jid, {})[nick] = result_str
        send_msg(type, jid, nick, result_str)
    except Exception:
        send_msg(type, jid, nick, L('I can\'t calculate it', rn))

def dpi_calc(type, jid, nick, text):
    text = text.strip().replace(',','.')
    if text:
        tupl = re.findall('([0-9.]+)',text)[:3]
        if len(tupl) == 3:
            if '.' in tupl[0] or '.' in tupl[1]: msg = L('Width and height must be integer!','%s/%s'%(jid,nick))
            elif not float(tupl[2]): msg = L('Incorrect diagonal value!','%s/%s'%(jid,nick))
            else:
                dpi_type = [0,'L'],[160,'M'],[240,'H'],[320,'XH']
                dpi = int((math.sqrt(int(tupl[0])**2+int(tupl[1])**2))/float(tupl[2]))
                dpi_name = 'unknown'
                for t in dpi_type:
                    if dpi > t[0]: dpi_name = '%sDPI' % t[1]
                msg = u'%s %s×%s×%s\" - %sdpi [%s]' % (L('Screen','%s/%s'%(jid,nick)),tupl[0],tupl[1],tupl[2],dpi,dpi_name)
        else: msg = L('Not enough parameters!','%s/%s'%(jid,nick))
    else: msg = L('What?','%s/%s'%(jid,nick))
    send_msg(type, jid, nick, msg)

_UNITS_BYTES = {
    'B': 1,
    'KB': 1024,
    'MB': 1024**2,
    'GB': 1024**3,
    'TB': 1024**4,
}

def _parse_cap(text):
    text = text.strip().upper()
    match = re.match(r'^\s*([0-9.]+)\s*([A-Z]+)\s*>\s*([A-Z]+)\s*$', text)
    if not match:
        return None, None, None
    value = float(match.group(1))
    from_unit = match.group(2)
    to_unit = match.group(3)
    if from_unit not in _UNITS_BYTES or to_unit not in _UNITS_BYTES:
        return None, None, None
    return value, from_unit, to_unit

def cap_cmd(type, jid, nick, text):
    if not text.strip():
        send_msg(type, jid, nick, L('Usage: cap число единица > единица. Пример: cap 1 MB > B', '%s/%s' % (jid, nick)))
        return

    value, from_unit, to_unit = _parse_cap(text)
    if value is None:
        send_msg(type, jid, nick, L('Неверный формат. Используйте: cap число единица > единица. Пример: cap 1 MB > B', '%s/%s' % (jid, nick)))
        return

    bytes_value = value * _UNITS_BYTES[from_unit]
    result = bytes_value / _UNITS_BYTES[to_unit]

    if result == int(result):
        result_str = str(int(result))
    else:
        result_str = '%.4f' % result
        result_str = result_str.rstrip('0').rstrip('.')

    send_msg(type, jid, nick, '%s %s = %s %s' % (value, from_unit, result_str, to_unit))

_UNITS_RATE = {
    'BPS': 1,
    'KBPS': 1024,
    'MBPS': 1024**2,
    'GBPS': 1024**3,
    'BIT': 1,
    'K': 1024,
    'M': 1024**2,
    'G': 1024**3,
}

def _parse_rate(text):
    text = text.strip().upper()
    text = re.sub(r'B?PS\b', '', text)
    match = re.match(r'^\s*([0-9.]+)\s*([A-Z]+)\s*>\s*([A-Z]+)\s*$', text)
    if not match:
        return None, None, None
    value = float(match.group(1))
    from_unit = match.group(2)
    to_unit = match.group(3)
    if from_unit not in _UNITS_RATE or to_unit not in _UNITS_RATE:
        return None, None, None
    return value, from_unit, to_unit

def rate_cmd(type, jid, nick, text):
    if not text.strip():
        send_msg(type, jid, nick, L('Usage: rate число единица > единица. Пример: rate 1 Mbps > Kbps', '%s/%s' % (jid, nick)))
        return

    value, from_unit, to_unit = _parse_rate(text)
    if value is None:
        send_msg(type, jid, nick, L('Неверный формат. Используйте: rate число единица > единица. Пример: rate 1 Mbps > Kbps', '%s/%s' % (jid, nick)))
        return

    bps_value = value * _UNITS_RATE[from_unit]
    result = bps_value / _UNITS_RATE[to_unit]

    if result == int(result):
        result_str = str(int(result))
    else:
        result_str = '%.4f' % result
        result_str = result_str.rstrip('0').rstrip('.')

    to_display = to_unit + 'bps' if to_unit in ('K', 'M', 'G') else to_unit.lower()
    from_display = from_unit + 'bps' if from_unit in ('K', 'M', 'G') else from_unit.lower()

    send_msg(type, jid, nick, '%s %s = %s %s' % (value, from_display, result_str, to_display))

_EFF_BYTE_UNITS = {'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4}
_EFF_BIT_UNITS = {'BIT': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3}

def _eff_num(v):
    if v == int(v):
        return str(int(v))
    s = '%.2f' % v
    return s.rstrip('0').rstrip('.')

def eff_cmd(type, jid, nick, text):
    rn = '%s/%s' % (jid, nick)
    text = text.strip()
    if not text:
        send_msg(type, jid, nick, L('Usage: eff число единица [время_в_сек]. Пример: eff 60 MB 10', rn))
        return

    parts = text.split()
    if len(parts) < 2:
        send_msg(type, jid, nick, L('Неверный формат. Пример: eff 60 MB', rn))
        return

    try:
        value = float(parts[0])
    except ValueError:
        send_msg(type, jid, nick, L('Ошибка: введите число.', rn))
        return

    unit = parts[1]
    time_sec = None
    if len(parts) > 2:
        try:
            time_sec = float(parts[2])
        except ValueError:
            time_sec = None

    # Байты (B) vs биты (b/BIT) различяем по регистру последней буквы.
    if unit.endswith('B'):
        kind = 'bytes'
        factor = _EFF_BYTE_UNITS.get(unit.upper())
    else:
        kind = 'bits'
        factor = _EFF_BIT_UNITS.get(unit.upper())

    if factor is None:
        send_msg(type, jid, nick,
                 L('Неизвестная единица. Используйте: Mb, Kb, Gb, bit (биты) или MB, KB, GB, TB (байты).', rn))
        return

    if kind == 'bytes':
        bytes_total = value * factor
        mbit_total = (bytes_total * 8) / (1024.0 ** 2)
        t = time_sec if time_sec else 1.0
        Mbps = mbit_total / t
        val_str = _eff_num(value)
        if time_sec:
            msg = L('%s %s за %s сек → скорость %s Mbps', rn) % (
                val_str, unit, _eff_num(time_sec), _eff_num(Mbps))
        else:
            msg = L('%s %s → скорость %s Mbps', rn) % (val_str, unit, _eff_num(Mbps))
    else:
        mbit_total = (value * factor) / (1024.0 ** 2)
        t = time_sec if time_sec else 1.0
        MB_vol = (mbit_total * t) / 8.0
        if time_sec:
            msg = L('%s Mbps за %s сек → скачается %s MB', rn) % (
                _eff_num(mbit_total), _eff_num(time_sec), _eff_num(MB_vol))
        else:
            msg = L('%s %s → скачается %s MB за 1 секунду', rn) % (
                _eff_num(value), unit, _eff_num(MB_vol))

    send_msg(type, jid, nick, msg)

def hex_cmd(type, jid, nick, text):
    if not text.strip():
        send_msg(type, jid, nick, L('Usage: hex число. Пример: hex 255 → 0xFF', '%s/%s' % (jid, nick)))
        return
    try:
        val = int(text.strip())
        send_msg(type, jid, nick, '0x%X' % val)
    except:
        send_msg(type, jid, nick, L('Ошибка: введите целое число.', '%s/%s' % (jid, nick)))

def dec_cmd(type, jid, nick, text):
    if not text.strip():
        send_msg(type, jid, nick, L('Usage: dec HEX. Пример: dec FF → 255', '%s/%s' % (jid, nick)))
        return
    try:
        val = int(text.strip(), 16)
        send_msg(type, jid, nick, str(val))
    except:
        send_msg(type, jid, nick, L('Ошибка: введите HEX (например, FF или 0xFF).', '%s/%s' % (jid, nick)))

def bin_cmd(type, jid, nick, text):
    if not text.strip():
        send_msg(type, jid, nick, L('Usage: bin число. Пример: bin 15 → 0b1111', '%s/%s' % (jid, nick)))
        return
    try:
        val = int(text.strip())
        send_msg(type, jid, nick, bin(val))
    except:
        send_msg(type, jid, nick, L('Ошибка: введите целое число.', '%s/%s' % (jid, nick)))

def percent_cmd(type, jid, nick, text):
    parts = text.strip().split()
    if len(parts) != 2:
        send_msg(type, jid, nick, L('Usage: percent процент число. Пример: percent 30 200 → 60', '%s/%s' % (jid, nick)))
        return
    try:
        pct = float(parts[0])
        num = float(parts[1])
        result = (pct / 100) * num
        if result == int(result):
            result_str = str(int(result))
        else:
            result_str = '%.4f' % result
            result_str = result_str.rstrip('0').rstrip('.')
        send_msg(type, jid, nick, '%s%% от %s = %s' % (pct, num, result_str))
    except ValueError:
        send_msg(type, jid, nick, L('Ошибка: введите числа.', '%s/%s' % (jid, nick)))

def random_cmd(type, jid, nick, text):
    parts = text.strip().split()
    if len(parts) != 2:
        send_msg(type, jid, nick, L('Usage: random мин макс. Пример: random 1 100 → 42', '%s/%s' % (jid, nick)))
        return
    try:
        a = int(parts[0])
        b = int(parts[1])
        if a > b:
            a, b = b, a
        result = random.randint(a, b)
        send_msg(type, jid, nick, str(result))
    except ValueError:
        send_msg(type, jid, nick, L('Ошибка: введите целые числа.', '%s/%s' % (jid, nick)))

def uuid_cmd(type, jid, nick, text):
    send_msg(type, jid, nick, str(uuid.uuid4()))

def calc_clear(room, jid, nick, type, arr):
    if type == 'unavailable' and (room in calc_last_res) and nick in calc_last_res[room]:
        del calc_last_res[room][nick]

# --------------------------------------------------------------------------- #
# РЕГИСТРАЦИЯ КОМАНД                                                         #
# --------------------------------------------------------------------------- #

global execute, presence_control

presence_control = [calc_clear]

execute = [
    (3, 'tools', tools_help, 2, 'Show this help in private message / Показать справку в личку'),
    (3, 'calc', calc, 2, 'Calculator / Калькулятор'),
    (3, 'dpi', dpi_calc, 2, 'DPI calculator / Расчёт DPI экрана'),
    (3, 'cap', cap_cmd, 2, 'Convert bytes / Конвертер байт'),
    (3, 'rate', rate_cmd, 2, 'Convert speed / Конвертер скорости'),
    (3, 'eff', eff_cmd, 2, 'Convert bits ↔ bytes (with speed) / Конвертер бит ↔ байт'),
    (3, 'hex', hex_cmd, 2, 'Convert to HEX / Перевод в HEX'),
    (3, 'dec', dec_cmd, 2, 'Convert HEX to decimal / Перевод HEX в десятичное'),
    (3, 'bin', bin_cmd, 2, 'Convert to binary / Перевод в двоичную систему'),
    (3, 'percent', percent_cmd, 2, 'Calculate percentage / Вычисление процента'),
    (3, 'random', random_cmd, 2, 'Random number in range / Случайное число'),
    (3, 'uuid', uuid_cmd, 2, 'Generate UUID v4 / Генерация UUID v4'),
]
