/*
 * Copyright (C) 2025-2026  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.ui.util;

import org.aglais.zms.util.Log;
import org.jxmapviewer.viewer.GeoPosition;

import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Utility class for importing geographical coordinates from Google Maps URLs.
 * <p>
 * This class attempts to extract latitude and longitude
 * coordinates and convert them into {@link GeoPosition} objects.
 * Supports the following providers:
 * <ul>
 *     <li>Google Maps (search, places, short redirects)</li>
 *     <li>OpenStreetMap (search)</li>
 *     <li>Geo URI scheme</li>
 * </ul>
 * </p>
 *
 * <p>
 * Example supported URLs:
 * <ul>
 *     <li><a href="https://www.google.com/maps/search/37.7749,+-122.4194">https://www.google.com/maps/search/37.7749,+-122.4194</a></li>
 *     <li><a href="https://maps.app.goo.gl/Qiqhft1rxsa1qnDB8">https://maps.app.goo.gl/Qiqhft1rxsa1qnDB8</a></li>
 *     <li><a href="https://www.openstreetmap.org/search?lat=37.7749&lon=-122.4194&zoom=7#map=13/37.78455/-122.39697">https://www.openstreetmap.org/search?lat=37.7749&lon=-122.4194&zoom=7#map=13/37.78455/-122.39697"</a></li>
 *     <li><a href="geo:37.7749,-122.4194?z=17.0">geo:37.7749,-122.4194?z=17.0</a></li>
 * </ul>
 * </p>
 *
 * <p>
 * This class is primarily intended for UI-based utilities that require conversion
 * of external Google Maps/OpenStreetMap links into mappable positions.
 * </p>
 */
public class GeoURLImporter {
    private static final String LOG_TAG = GeoURLImporter.class.getSimpleName();
    private static final Map<Pattern, BiFunction<String, Matcher, GeoPosition>> IMPORTERS = new HashMap<>();

    private static final String MAPS_COMMON_REGEX = "https://(?:[a-z0-9]+\\.)?google\\.com/maps";
    private static final String OSM_REGEX = "https://(?:[a-z0-9]+\\.)?openstreetmap\\.org/search\\?lat=(-?[0-9]+\\.[0-9]+)"
            + "&lon=(-?[0-9]+\\.[0-9]+).*";

    static {
        IMPORTERS.put(Pattern.compile(
                        MAPS_COMMON_REGEX + "/place/.*/data=.*8m2!3d(-?[0-9]+\\.[0-9]+)!4d(-?[0-9]+\\.[0-9]+).*"
                ),
                GeoURLImporter::importSimpleLatLng);
        IMPORTERS.put(Pattern.compile(
                        MAPS_COMMON_REGEX + "/search/(-?[0-9]+\\.[0-9]+),\\+?(-?[0-9]+\\.[0-9]+).*"
                ),
                GeoURLImporter::importSimpleLatLng);
        IMPORTERS.put(Pattern.compile("https://maps\\.app\\.goo\\.gl/.+"), GeoURLImporter::importFromGoogleMapsShort);
        IMPORTERS.put(Pattern.compile(OSM_REGEX), GeoURLImporter::importSimpleLatLng);
        IMPORTERS.put(Pattern.compile("geo:(-?[0-9]+\\.[0-9]+),(-?[0-9]+\\.[0-9]+).*"),
                GeoURLImporter::importSimpleLatLng);
    }

    /**
     * Extracts a {@link GeoPosition} from a regex matcher containing latitude and longitude.
     * Latitude must be group 1 and longitude must be group 2.
     *
     * @param url     The full URL
     * @param matcher The regex matcher containing the matched groups
     * @return A {@link GeoPosition} with the extracted latitude and longitude
     */
    private static GeoPosition importSimpleLatLng(String url, Matcher matcher) {
        double lat = Double.parseDouble(matcher.group(1));
        double lng = Double.parseDouble(matcher.group(2));

        return new GeoPosition(lat, lng);
    }

    /**
     * Resolves a short Google Maps URL by following the redirect to the full URL,
     * then extracting the coordinates.
     *
     * @param url     The shortened Google Maps URL
     * @param matcher Unused matcher (required by method signature)
     * @return a {@link GeoPosition} if resolution and extraction succeed, or {@code null} if failed
     */
    private static GeoPosition importFromGoogleMapsShort(String url, Matcher matcher) {
        try {
            URL maps = new URI(url).toURL();
            HttpsURLConnection conn = (HttpsURLConnection) maps.openConnection();

            conn.setInstanceFollowRedirects(false);

            if (conn.getResponseCode() == 302)
                return importFrom(conn.getHeaderField("location"));
        } catch (URISyntaxException | IOException e) {
            Log.e(LOG_TAG, "Failed to fetch google maps", e);
        }

        return null;
    }

    /**
     * Attempts to extract a {@link GeoPosition} from a given URL using known
     * patterns and extractors.
     *
     * @param url The URL to parse
     * @return A {@link GeoPosition} if a matching pattern is found and parsing succeeds,
     * or {@code null} otherwise
     */
    public static GeoPosition importFrom(String url) {
        for (Pattern pattern : IMPORTERS.keySet()) {
            Matcher matcher = pattern.matcher(url);
            if (matcher.matches())
                return IMPORTERS.get(pattern).apply(url, matcher);
        }

        Log.e(LOG_TAG, "No matching importer found!");

        return null;
    }
}