#!/usr/bin/env python3
"""
Скрипт для скачивания тайлов карты OpenStreetMap для офлайн использования.
Соблюдает политику использования тайлов OpenStreetMap:
- Использует корректный User-Agent
- Соблюдает минимальную задержку 1 секунда между запросами
- Обрабатывает ошибки rate limiting

Использование:
    python download_tiles.py --min-zoom 5 --max-zoom 12 --bounds 55.5,37.5,56.0,38.0
"""

import os
import math
import argparse
import requests
from pathlib import Path
import time


def deg2num(lat_deg, lon_deg, zoom):
    """Преобразует координаты в номер тайла"""
    lat_rad = math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0) / 360.0 * n)
    ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
    return (xtile, ytile)


def num2deg(xtile, ytile, zoom):
    """Преобразует номер тайла в координаты"""
    n = 2.0 ** zoom
    lon_deg = xtile / n * 360.0 - 180.0
    lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
    lat_deg = math.degrees(lat_rad)
    return (lat_deg, lon_deg)


def download_tile(z, x, y, output_dir, retries=3, delay=1.0):
    """Скачивает один тайл с соблюдением политики использования OpenStreetMap"""
    tile_dir = os.path.join(output_dir, str(z), str(x))
    os.makedirs(tile_dir, exist_ok=True)
    
    tile_path = os.path.join(tile_dir, f"{y}.png")
    
    # Если тайл уже существует, пропускаем
    if os.path.exists(tile_path):
        return True
    
    # URL для скачивания тайла
    # Используем разные поддомены для распределения нагрузки
    subdomains = ['a', 'b', 'c']
    subdomain = subdomains[(x + y) % len(subdomains)]
    url = f"https://{subdomain}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    
    # Заголовки согласно политике использования OpenStreetMap
    headers = {
        'User-Agent': 'PythonAisMap/1.0 (Offline Map Tile Downloader; contact: localhost)',
        'Referer': 'http://localhost:8000/'
    }
    
    for attempt in range(retries):
        try:
            response = requests.get(url, headers=headers, timeout=30)
            
            if response.status_code == 200:
                with open(tile_path, 'wb') as f:
                    f.write(response.content)
                time.sleep(delay)  # Задержка между запросами (минимум 1 сек согласно политике)
                return True
            elif response.status_code == 404:
                # Тайл не существует (например, для океана)
                return False
            elif response.status_code == 429:
                # Too Many Requests - нужно увеличить задержку
                wait_time = delay * (2 ** attempt)
                print(f"  Превышен лимит запросов для {z}/{x}/{y}, ожидание {wait_time} сек...")
                time.sleep(wait_time)
                continue
            elif response.status_code == 403:
                # Forbidden - возможно, нарушена политика использования
                print(f"  Доступ запрещен для {z}/{x}/{y}. Проверьте User-Agent и задержки.")
                return False
            else:
                print(f"  Неожиданный статус {response.status_code} для {z}/{x}/{y}")
                if attempt < retries - 1:
                    time.sleep(delay * (attempt + 1))
                else:
                    return False
        except requests.exceptions.Timeout:
            if attempt < retries - 1:
                print(f"  Таймаут для {z}/{x}/{y}, повторная попытка...")
                time.sleep(delay * (attempt + 1))
            else:
                print(f"  Ошибка таймаута при скачивании тайла {z}/{x}/{y}")
                return False
        except Exception as e:
            if attempt < retries - 1:
                time.sleep(delay * (attempt + 1))
            else:
                print(f"  Ошибка при скачивании тайла {z}/{x}/{y}: {e}")
                return False
    
    return False


def download_tiles(min_lat, min_lon, max_lat, max_lon, min_zoom, max_zoom, output_dir="static/tiles", delay=1.0):
    """Скачивает тайлы для указанной области и уровней масштабирования"""
    print(f"Начинаем скачивание тайлов...")
    print(f"Область: ({min_lat}, {min_lon}) - ({max_lat}, {max_lon})")
    print(f"Уровни масштабирования: {min_zoom} - {max_zoom}")
    print(f"Выходная папка: {output_dir}")
    
    total_tiles = 0
    downloaded_tiles = 0
    skipped_tiles = 0
    
    for z in range(min_zoom, max_zoom + 1):
        print(f"\nОбработка уровня масштабирования {z}...")
        
        # Определяем диапазон тайлов для данного уровня
        min_tile_x, max_tile_y = deg2num(min_lat, min_lon, z)
        max_tile_x, min_tile_y = deg2num(max_lat, max_lon, z)
        
        # Убеждаемся, что координаты в правильном порядке
        if min_tile_x > max_tile_x:
            min_tile_x, max_tile_x = max_tile_x, min_tile_x
        if min_tile_y > max_tile_y:
            min_tile_y, max_tile_y = max_tile_y, min_tile_y
        
        level_tiles = (max_tile_x - min_tile_x + 1) * (max_tile_y - min_tile_y + 1)
        total_tiles += level_tiles
        
        print(f"  Тайлов на уровне {z}: {level_tiles} ({min_tile_x}-{max_tile_x}, {min_tile_y}-{max_tile_y})")
        
        level_downloaded = 0
        level_skipped = 0
        
        for x in range(min_tile_x, max_tile_x + 1):
            for y in range(min_tile_y, max_tile_y + 1):
                if download_tile(z, x, y, output_dir, delay=delay):
                    level_downloaded += 1
                    downloaded_tiles += 1
                else:
                    level_skipped += 1
                    skipped_tiles += 1
                
                # Показываем прогресс каждые 10 тайлов
                if (level_downloaded + level_skipped) % 10 == 0:
                    print(f"  Прогресс: {level_downloaded + level_skipped}/{level_tiles} тайлов")
        
        print(f"  Уровень {z} завершен: скачано {level_downloaded}, пропущено {level_skipped}")
    
    print(f"\n=== Итоги ===")
    print(f"Всего тайлов: {total_tiles}")
    print(f"Скачано: {downloaded_tiles}")
    print(f"Пропущено: {skipped_tiles}")
    print(f"Готово!")


def main():
    parser = argparse.ArgumentParser(
        description="Скачивание тайлов OpenStreetMap для офлайн использования"
    )
    parser.add_argument(
        "--min-zoom",
        type=int,
        default=5,
        help="Минимальный уровень масштабирования (по умолчанию: 5)"
    )
    parser.add_argument(
        "--max-zoom",
        type=int,
        default=12,
        help="Максимальный уровень масштабирования (по умолчанию: 12)"
    )
    parser.add_argument(
        "--delay",
        type=float,
        default=1.0,
        help="Задержка между запросами в секундах (по умолчанию: 1.0, минимум рекомендуется 1.0)"
    )
    parser.add_argument(
        "--bounds",
        type=str,
        help="Границы области в формате: min_lat,min_lon,max_lat,max_lon (например: 55.5,37.5,56.0,38.0)"
    )
    parser.add_argument(
        "--center",
        type=str,
        help="Центр области и радиус в формате: lat,lon,radius_km (например: 55.75,37.62,50)"
    )
    parser.add_argument(
        "--output",
        type=str,
        default="static/tiles",
        help="Выходная папка для тайлов (по умолчанию: static/tiles)"
    )
    
    args = parser.parse_args()
    
    # Определяем границы области
    if args.bounds:
        parts = args.bounds.split(",")
        if len(parts) != 4:
            print("Ошибка: --bounds должен содержать 4 значения: min_lat,min_lon,max_lat,max_lon")
            return
        min_lat, min_lon, max_lat, max_lon = map(float, parts)
    elif args.center:
        parts = args.center.split(",")
        if len(parts) != 3:
            print("Ошибка: --center должен содержать 3 значения: lat,lon,radius_km")
            return
        center_lat, center_lon, radius_km = map(float, parts)
        # Приблизительно вычисляем границы на основе радиуса
        # 1 градус широты ≈ 111 км
        lat_delta = radius_km / 111.0
        # 1 градус долготы зависит от широты
        lon_delta = radius_km / (111.0 * math.cos(math.radians(center_lat)))
        min_lat = center_lat - lat_delta
        max_lat = center_lat + lat_delta
        min_lon = center_lon - lon_delta
        max_lon = center_lon + lon_delta
    else:
        # По умолчанию: Москва и окрестности
        print("Используются границы по умолчанию: Москва и окрестности")
        min_lat, min_lon = 55.5, 37.3
        max_lat, max_lon = 56.0, 37.9
    
    # Проверяем валидность границ
    if min_lat >= max_lat or min_lon >= max_lon:
        print("Ошибка: неверные границы области")
        return
    
    if args.min_zoom < 0 or args.max_zoom > 19 or args.min_zoom > args.max_zoom:
        print("Ошибка: неверные уровни масштабирования (должны быть от 0 до 19, min <= max)")
        return
    
    # Создаем выходную папку
    os.makedirs(args.output, exist_ok=True)
    
    # Проверяем задержку (рекомендуется минимум 1 секунда)
    if args.delay < 1.0:
        print("ВНИМАНИЕ: Задержка менее 1 секунды может привести к блокировке!")
        print("Рекомендуется использовать задержку не менее 1.0 секунды.")
        response = input("Продолжить с текущей задержкой? (y/n): ")
        if response.lower() != 'y':
            return
    
    # Скачиваем тайлы
    download_tiles(
        min_lat, min_lon, max_lat, max_lon,
        args.min_zoom, args.max_zoom,
        args.output,
        args.delay
    )


if __name__ == "__main__":
    main()

