#!/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>            #
#                                                                             #
#    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/>.    #
#                                                                             #
#    Project home: https://ubunix.pro/isida-ng                               #
# --------------------------------------------------------------------------- #

import re
import math
import json
import urllib.request
import urllib.parse

dist_max_search_limit = 100
dist_default_search_count = 10

# --------------------------------------------------------------------------- #
# OSM GEOCODER                                                               #
# --------------------------------------------------------------------------- #

def _geocode_osm(query, limit=1, offset=0):
    if not query:
        return []
    url = 'https://nominatim.openstreetmap.org/search?q=%s&format=json&limit=%s&offset=%s' % (
        urllib.parse.quote_plus(query), limit, offset
    )
    try:
        req = urllib.request.Request(url, headers={'User-Agent': 'Isida-NG-Bot/1.0'})
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read().decode('utf-8'))
        if not data:
            return []
        return [(item['display_name'], item['lon'], item['lat']) for item in data]
    except Exception as e:
        pprint('*** OSM geocoder error: %s' % str(e), 'red')
        return []

# --------------------------------------------------------------------------- #
# HELPERS                                                                     #
# --------------------------------------------------------------------------- #

def points2distance(start, end):
    start_latt = math.radians(start[0])
    start_long = math.radians(start[1])
    end_latt = math.radians(end[0])
    end_long = math.radians(end[1])
    d_long = end_long - start_long
    a = (math.cos(end_latt) * math.sin(d_long))**2 + (math.cos(start_latt)*math.sin(end_latt)-math.sin(start_latt)*math.cos(end_latt)*math.cos(d_long))**2
    b = math.sin(start_latt)*math.sin(end_latt) + math.cos(start_latt)*math.cos(end_latt)*math.cos(d_long)
    dist = math.atan2(math.sqrt(a), b) * 6372.795
    return int(dist + 0.5)

def city_capitalize(s):
    s = s.capitalize()
    for k in re.findall('[ -].', s):
        s = s.replace(k, k.upper())
    for tmp in [u'-На-', u' На ', u'-Де-', u' Де ']:
        s = s.replace(tmp, tmp.lower())
    return s

# --------------------------------------------------------------------------- #
# COMMANDS                                                                    #
# --------------------------------------------------------------------------- #

def city(type, jid, nick, text):
    parameters = text.strip().split(' ', 1)

    # --- ADD ---
    if parameters[0] == 'add' and get_level(jid, nick)[0] == 9:
        try:
            tmp = parameters[1].split('\n', 1)
            place = tmp[0].strip().lower().replace(' - ', '-')
            if len(tmp) == 2:
                coords = re.sub('[^-\.\d]+', ' ', tmp[1]).strip().split()
                if abs(float(coords[0])) < 90 and abs(float(coords[1])) < 180:
                    if not cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (place + '%',)):
                        cur_execute('INSERT INTO dist_user VALUES (?,?,?)', (place, coords[1], coords[0]))
                        msg = L('Added!', '%s/%s' % (jid, nick))
                    else:
                        msg = L('This point is in database!', '%s/%s' % (jid, nick))
            else:
                results = _geocode_osm(place, limit=1)
                if results:
                    display_name, lon, lat = results[0]
                    if not cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (place + '%',)):
                        cur_execute('INSERT INTO dist_user VALUES (?,?,?)', (place, lon, lat))
                        msg = L('Added: ', '%s/%s' % (jid, nick)) + display_name + L(' as ', '%s/%s' % (jid, nick)) + '"%s"' % place
                    else:
                        msg = L('This point is in database!', '%s/%s' % (jid, nick))
                else:
                    msg = L('City not found!', '%s/%s' % (jid, nick))
        except Exception as e:
            pprint('*** city add error: %s' % str(e), 'red')
            msg = L('Error!', '%s/%s' % (jid, nick))

    # --- DEL ---
    elif parameters[0] == 'del' and get_level(jid, nick)[0] == 9:
        place = parameters[1].strip().lower()
        if cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (place + '%',)):
            cur_execute('DELETE FROM dist_user WHERE point LIKE ?', (place + '%',))
            msg = L('Deleted!', '%s/%s' % (jid, nick))
        else:
            msg = L('This point isn\'t in database!', '%s/%s' % (jid, nick))

    # --- MAP ---
    elif parameters[0] == 'map':
        place = parameters[1].strip().lower()
        t = cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (place + '%',))
        if not t:
            t = cur_execute_fetchone('SELECT * FROM dist WHERE point LIKE ?', (place + '%',))
        if t:
            # t[1] - долгота, t[2] - широта
            tmp = 'https://www.openstreetmap.org/?mlat=%s&mlon=%s&zoom=14' % (t[2], t[1])
            try:
                msg = city_capitalize(place) + L(' on the map: ', '%s/%s' % (jid, nick)) + load_page(SHORT_TINYURL % enidna(tmp))
            except:
                msg = city_capitalize(place) + L(' on the map: ', '%s/%s' % (jid, nick)) + tmp
        else:
            msg = L('Not found!', '%s/%s' % (jid, nick))

    # --- SEARCH (через OSM) ---
    elif parameters[0] == 'search':
        try:
            text_tmp = parameters[1].split(' ', 1)
            if re.match('\d+$', text_tmp[0]):
                results = int(text_tmp[0])
                query = text_tmp[1]
                offset = 0
            elif re.match('\d+[-: |]\d+$', text_tmp[0]):
                tmp = re.sub('[-: |]', ' ', text_tmp[0]).split()
                results = int(tmp[1]) - int(tmp[0]) + 1
                offset = int(tmp[0]) - 1
                query = text_tmp[1]
            else:
                results = 5
                offset = 0
                query = parameters[1]
            results_list = _geocode_osm(query, limit=results, offset=offset)
            if results_list:
                msg = '\n'.join(['%s - (%s, %s)' % (name, lat, lon) for name, lon, lat in results_list])
            else:
                msg = L('Not found!', '%s/%s' % (jid, nick))
        except Exception as e:
            pprint('*** city search error: %s' % str(e), 'red')
            msg = L('Error!', '%s/%s' % (jid, nick))

    # --- DEFAULT: show coordinates ---
    else:
        place = text.strip().lower()
        t = cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (place + '%',))
        if not t:
            t = cur_execute_fetchone('SELECT * FROM dist WHERE point LIKE ?', (place + '%',))
        if t:
            msg = L(u'%s - latitude: %s, longitude: %s') % (city_capitalize(t[0]), t[2], t[1])
        else:
            msg = L('Not found!', '%s/%s' % (jid, nick))

    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# DISTANCE COMMAND                                                            #
# --------------------------------------------------------------------------- #

def dist(type, jid, nick, text):
    text = text.strip()
    splitter = [' - ', '|', '\n', ' ']

    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return

    if ' ' in text and text.split(' ', 1)[0].lower() == 'search':
        try:
            parts = text.split(' ', 2)
            dist_count = dist_default_search_count
            if len(parts) > 2:
                dist_count = int(parts[2])
                dist_count = max(1, min(dist_max_search_limit, dist_count))
            query = parts[1]
            tmp = cur_execute_fetchall('SELECT point FROM dist_user WHERE point LIKE ? ORDER BY point LIMIT ?', ('%' + query + '%', dist_count))
            if not tmp:
                tmp = cur_execute_fetchall('SELECT point FROM dist WHERE point LIKE ? ORDER BY point LIMIT ?', ('%' + query + '%', dist_count))
            if tmp:
                msg = L('Found: %s', '%s/%s' % (jid, nick)) % ', '.join([city_capitalize(t[0]) for t in tmp])
            else:
                msg = L('City %s not found', '%s/%s' % (jid, nick)) % city_capitalize(query)
        except Exception as e:
            pprint('*** dist search error: %s' % str(e), 'red')
            msg = L('Error!', '%s/%s' % (jid, nick))
    else:
        points = None
        for sep in splitter:
            if sep in text:
                points = text.split(sep)
                break
        if points and len(points) == 2:
            p1 = points[0].strip().lower()
            p2 = points[1].strip().lower()
            t1 = cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (p1 + '%',))
            if not t1:
                t1 = cur_execute_fetchone('SELECT * FROM dist WHERE point LIKE ?', (p1 + '%',))
            t2 = cur_execute_fetchone('SELECT * FROM dist_user WHERE point LIKE ?', (p2 + '%',))
            if not t2:
                t2 = cur_execute_fetchone('SELECT * FROM dist WHERE point LIKE ?', (p2 + '%',))
            if t1 and t2:
                dist_km = points2distance((float(t1[2]), float(t1[1])), (float(t2[2]), float(t2[1])))
                msg = L('%s km', '%s/%s' % (jid, nick)) % dist_km
            elif t1:
                msg = L('City %s not found', '%s/%s' % (jid, nick)) % city_capitalize(p2)
            elif t2:
                msg = L('City %s not found', '%s/%s' % (jid, nick)) % city_capitalize(p1)
            else:
                msg = L('Cities not found', '%s/%s' % (jid, nick))
        else:
            msg = L('Error in parameters. Read the help about command.', '%s/%s' % (jid, nick))

    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# REGISTER COMMANDS                                                           #
# --------------------------------------------------------------------------- #

execute = [
    (3, 'dist', dist, 2, 'Distance between cities.\ndist search city [count of cities]\ndist city1 - city2'),
    (3, 'city', city, 2, 'Cities and other place-name of the world. Examples:\ncity add place-name\n[latitude, longtitude] - add city to database\ncity del place-name - delete city from database\ncity search [count of results|range of results] place-name - search city\ncity map place-name - city on the map\ncity place-name - coordinates of city'),
]
