# Copyright (C) 2007-2026 Ahmet Öztürk (aoz_2@yahoo.com)
#
# This file is part of Lifeograph.
#
# Lifeograph 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.
#
# Lifeograph 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 Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

from LoG import *
from pathlib import Path
import xml.etree.ElementTree as ET

NAME="Import KML (Experimental)"
VERSION="0.1"
CATEGORY=SCtg.IMPORTER


def run(path):
    _import_kml_file(path)

    refresh_panels()

    return True


# XML HELPERS (namespace-agnostic) =================================================================
# KML files declare namespaces like xmlns="http://www.opengis.net/kml/2.2" (2.0/2.1/2.3 variants
# also exist), so we match on local tag name rather than building a namespace map.

def _local(tag):
    return tag.split('}', 1)[-1] if '}' in tag else tag

def _find_child(elem, name):
    for child in elem:
        if _local(child.tag) == name:
            return child
    return None

def _get_text(elem, name):
    child = _find_child(elem, name)
    if child is not None and child.text:
        text = child.text.strip()
        return text if text else None
    return None

def _parse_coords(text):
    # KML coordinate tuples are "lon,lat[,alt]", whitespace-separated for multi-point geometries
    coords = []
    for tok in text.split():
        parts = tok.split(',')
        if len(parts) >= 2:
            try:
                lon = float(parts[0])
                lat = float(parts[1])
            except ValueError:
                continue
            coords.append((lat, lon))
    return coords


# GEOMETRY IMPORT ==================================================================================
def _add_description(entry, description):
    if description:
        d = entry.append_paragraph(description)
        d.set_indent_level(1)

def _import_point(entry, point_elem, name, description):
    coords_text = _get_text(point_elem, 'coordinates')
    if not coords_text:
        return
    coords = _parse_coords(coords_text)
    if not coords:
        return

    lat, lon = coords[0]
    p = entry.append_paragraph(name if name else "")
    p.set_location(lat, lon)

    _add_description(entry, description)

def _import_path(entry, coords_elem_parent, name, description):
    # used for LineString and Polygon rings. Consecutive paragraphs at the same indent level
    # with locations set are rendered as a path on the map, so this covers both.
    coords_text = _get_text(coords_elem_parent, 'coordinates')
    if not coords_text:
        return
    coords = _parse_coords(coords_text)
    if not coords:
        return

    first = True
    for lat, lon in coords:
        p = entry.append_paragraph(name if first and name else "")
        p.set_location(lat, lon)
        first = False

    _add_description(entry, description)

def _import_polygon(entry, polygon_elem, name, description):
    outer = _find_child(polygon_elem, 'outerBoundaryIs')
    if outer is None:
        return
    ring = _find_child(outer, 'LinearRing')
    if ring is None:
        return

    _import_path(entry, ring, name, description)
    # TODO: inner boundaries (holes) are not represented (no equivalent concept in Lifeograph paths)

def _import_placemark(entry, placemark):
    name = _get_text(placemark, 'name')
    description = _get_text(placemark, 'description')

    point = _find_child(placemark, 'Point')
    linestring = _find_child(placemark, 'LineString')
    polygon = _find_child(placemark, 'Polygon')
    multigeom = _find_child(placemark, 'MultiGeometry')

    if point is not None:
        _import_point(entry, point, name, description)
    elif linestring is not None:
        _import_path(entry, linestring, name, description)
    elif polygon is not None:
        _import_polygon(entry, polygon, name, description)
    elif multigeom is not None:
        # Import each sub-geometry; only the first gets the shared name/description label
        for geom in multigeom:
            local = _local(geom.tag)
            if local == 'Point':
                _import_point(entry, geom, name, description)
            elif local == 'LineString':
                _import_path(entry, geom, name, description)
            elif local == 'Polygon':
                _import_polygon(entry, geom, name, description)
            else:
                continue
            name, description = None, None
    elif name or description:
        # No geometry at all, still record the label/description as plain paragraphs
        entry.append_paragraph(name if name else "")
        _add_description(entry, description)


# CONTAINER TRAVERSAL ==============================================================================
def _process_container(container, title):
    """Placemarks that sit directly under a Document (or the kml root, or a nested <Document>)
    without belonging to any Folder are collected into one entry named after the container
    (or the file title). Each Folder becomes its own separate entry."""
    loose_entry = None

    def _get_loose_entry():
        nonlocal loose_entry
        if loose_entry is None:
            name = _get_text(container, 'name') or title
            loose_entry = Diary.get_main().create_entry(VT.ETS.NAME_ONLY, name)
        return loose_entry

    for child in container:
        local = _local(child.tag)
        if local == 'Placemark':
            _import_placemark(_get_loose_entry(), child)
        elif local == 'Folder':
            _process_folder(child)
        elif local == 'Document':
            _process_container(child, title)

def _process_folder(folder):
    name = _get_text(folder, 'name') or "Untitled Folder"
    entry = Diary.get_main().create_entry(VT.ETS.NAME_ONLY, name)

    for child in folder:
        local = _local(child.tag)
        if local == 'Placemark':
            _import_placemark(entry, child)
        elif local == 'Folder':
            _process_folder(child)  # nested folder -> its own separate entry, not nested in `entry`

def _import_kml_file(filepath):
    tree = ET.parse(filepath)
    root = tree.getroot()
    title = Path(filepath).stem

    document = _find_child(root, 'Document')
    _process_container(document if document is not None else root, title)
