"""
This module contains the KMLWriter class.
"""

import os
import warnings
import xml.etree.ElementTree as ET
from typing import Optional

from ..gpx_elements import Gpx
from .writer import Writer

DEFAULT_NORMAL_STYLE = {"color": "ff0000ff", "width": 2, "fill": 0}

DEFAULT_HIGHLIGHT_STYLE = {"color": "ff0000ff", "width": 2, "fill": 0}

DEFAULT_STYLES = [
    ("normal", DEFAULT_NORMAL_STYLE),
    ("highlight", DEFAULT_HIGHLIGHT_STYLE),
]


class KMLWriter(Writer):
    """
    KML file writer.
    """

    def __init__(
        self,
        gpx: Gpx = None,
        *,
        properties: bool = True,
        metadata: bool = True,  # unused
        waypoints: bool = True,
        routes: bool = True,
        extensions: bool = True,
        ele: bool = True,
        time: bool = True,
        precisions: dict = None,
        time_format: str = None,
        styles: list[tuple[str, dict]] = None,
    ) -> None:
        """
        Initialise GPXWriter instance.
        """
        super().__init__(gpx, precisions, time_format)

        # Parameters
        self.properties: bool = properties
        self.metadata: bool = metadata
        self.waypoints: bool = waypoints
        self.routes: bool = routes
        self.extensions: bool = extensions
        self.ele: bool = ele
        self.time: bool = time

        self.styles = DEFAULT_STYLES if styles is None else styles

        # Utility attributes
        self.kml_string: str = ""
        self.kml_root = None

    def add_pair(self, element: ET.Element, key: str, style_url: str) -> ET.Element:
        """
        Add StyleMap to KML element.

        Args:
            element (ET.Element): KML element.
            key (str): Key.
            style_url (str): Style URL.

        Returns:
            ET.Element: KML element.
        """
        pair_ = ET.SubElement(element, "Pair")
        pair_, _ = self.add_subelement(pair_, "key", key)
        pair_, _ = self.add_subelement(pair_, "styleUrl", style_url)
        return element

    def add_stylemap(self, element: ET.Element, id_: str) -> ET.Element:
        """
        Add StyleMap to KML element.

        Args:
            element (ET.Element): KML element.
            id_ (str): StyleMap element id.

        Returns:
            ET.Element: KML element.
        """
        stylemap_ = ET.SubElement(element, "StyleMap")
        self.set_not_none(stylemap_, "id", id_)
        style_id = 1
        for style_key, _ in self.styles:  # _ used to be called style
            stylemap_ = self.add_pair(stylemap_, style_key, "#style" + str(style_id))
            style_id += 1
        return element

    def add_polystyle(self, element: ET.Element, style: dict) -> ET.Element:
        """
        Add PolyStyle to KML element.

        Args:
            element (ET.Element): KML element.
            style (dict): Style.

        Returns:
            ET.Element: KML element.
        """
        polystyle_ = ET.SubElement(element, "PolyStyle")
        try:
            polystyle_, _ = self.add_subelement_number(
                polystyle_, "fill", style["fill"]
            )
        except KeyError:
            warnings.warn("No fill attribute in style")
            polystyle_, _ = self.add_subelement_number(polystyle_, "fill", 0)
        return element

    def add_linestyle(self, element: ET.Element, style: dict) -> ET.Element:
        """
        Add LineStyle to KML element.

        Args:
            element (ET.Element): KML element.
            style (dict): Style.

        Returns:
            ET.Element: KML element.
        """
        linestyle_ = ET.SubElement(element, "LineStyle")
        try:
            linestyle_, _ = self.add_subelement(linestyle_, "color", style["color"])
        except KeyError:
            warnings.warn("No color attribute in style")
            linestyle_, _ = self.add_subelement(linestyle_, "color", "ff0000ff")
        try:
            linestyle_, _ = self.add_subelement_number(
                linestyle_, "width", style["width"]
            )
        except KeyError:
            warnings.warn("No width attribute in style")
            linestyle_, _ = self.add_subelement_number(linestyle_, "width", 2)
        return element

    def add_style(
        self,
        element: ET.Element,
        id: str,  # pylint: disable=redefined-builtin
        style: dict,
    ) -> ET.Element:
        """
        Add Style to KML element.

        Args:
            element (ET.Element): KML element.
            id (str): Style element id.
            style (dict): Line style.

        Returns:
            ET.Element: KML element.
        """
        style_ = ET.SubElement(element, "Style")
        self.set_not_none(style_, "id", id)
        style_ = self.add_linestyle(style_, style)
        style_ = self.add_polystyle(style_, style)
        return element

    def add_linestring(self, element: ET.Element) -> ET.Element:
        """
        Add LineString to KML element.

        Args:
            element (ET.Element): KML element.

        Returns:
            ET.Element: KML element.
        """
        linestring_ = ET.SubElement(element, "LineString")
        linestring_, _ = self.add_subelement_number(linestring_, "tessellate", 1)
        if self.ele:
            coordinates = self.gpx.to_csv(
                dest=None, values=["lon", "lat", "ele"]
            ).replace("\n", " ")
        else:
            coordinates = self.gpx.to_csv(dest=None, values=["lon", "lat"]).replace(
                "\n", " "
            )
        linestring_, _ = self.add_subelement(linestring_, "coordinates", coordinates)
        return element

    def add_placemark(self, element: ET.Element) -> ET.Element:
        """
        Add Placemark to KML element.

        Args:
            element (ET.Element): KML element.

        Returns:
            ET.Element: KML element.
        """
        placemark_ = ET.SubElement(element, "Placemark")
        placemark_, _ = self.add_subelement(placemark_, "name", self.gpx.name())
        placemark_, _ = self.add_subelement(placemark_, "styleUrl", "#stylemap")
        placemark_ = self.add_linestring(placemark_)
        return element

    def add_document(self, element: ET.Element) -> ET.Element:
        """
        Add Document to KML element.

        Args:
            element (ET.Element): KML element.

        Returns:
            ET.Element: KML element.
        """
        document_ = ET.SubElement(element, "Document")
        document_, _ = self.add_subelement(
            document_, "name", os.path.basename(self.file_path)
        )
        id_ = 1
        for _, style in self.styles:  # _ used to be called style_key
            document_ = self.add_style(document_, "style" + str(id), style)
            id_ += 1
        document_ = self.add_stylemap(document_, "stylemap")
        document_ = self.add_placemark(document_)
        return element

    def add_root_document(self) -> None:
        """
        Add Document element to the KML root element.
        """
        self.kml_root = self.add_document(self.kml_root)

    def add_root_properties(self) -> None:
        """
        Add properties to the GPX root element.
        """
        # According to .kml file from Google Earth Pro
        self.kml_root.set("xmlns", "http://www.opengis.net/kml/2.2")
        self.kml_root.set("xmlns:gx", "http://www.google.com/kml/ext/2.2")
        self.kml_root.set("xmlns:kml", "http://www.opengis.net/kml/2.2")
        self.kml_root.set("xmlns:atom", "http://www.w3.org/2005/Atom")

    def gpx_to_string(self) -> str | None:
        """
        Convert Gpx instance to a string (the content of a .kml file).

        Returns:
            str | None: String corresponding to the Gpx instance.
        """
        if self.gpx is None:
            return None

        # Reset string
        self.kml_string = ""

        # Root
        self.kml_root = ET.Element("kml")

        # Properties
        if self.properties:
            self.add_root_properties()

        # Document
        self.add_root_document()

        # Convert data to string
        self.kml_string = ET.tostring(self.kml_root, encoding="unicode")
        # self.kml_string = ET.tostring(kml_root, encoding="utf-8")

        return self.kml_string

    def write_gpx(self):
        """
        Convert Gpx instance to string and write to .kml file.
        """
        # Open/create KML file
        try:
            f = open(self.file_path, "w", encoding="utf-8")
        except OSError:
            warnings.warn("Could not open/read file: %s", self.file_path)
            raise
        # Write KML file
        with f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>')
            f.write(self.kml_string)

    def write(
        self,
        file_path: str,
        styles: Optional[list[tuple[str, dict]]] = None,
        xml_schema: bool = False,
        xml_extensions_schemas: bool = False,
    ) -> bool:
        """
        Handle writing.

        Args:
            file_path (str): Path to write the KML file.
            styles (Optional[list[tuple[str, dict]]], optional): List of
                (style_id, style) tuples. Defaults to None.
            xml_schema (bool, optional): Toggle schema verification
                after writting. Defaults to False.
            xml_extensions_schemas (bool, optional): TODO.
                Defaults to False.

        Returns:
            bool: Return True if written file follows checked schemas,
                False otherwise.
        """
        # Handle path
        directory_path = os.path.dirname(os.path.realpath(file_path))
        if not os.path.exists(directory_path):
            warnings.warn("Provided path does not exist")
            return False
        self.file_path = file_path

        # Update style
        if styles is not None:
            self.styles = styles

        # Write .kml file
        self.gpx_to_string()
        self.write_gpx()

        # Check XML schemas
        res = True
        if xml_schema or xml_extensions_schemas:
            res = self.xml_schemas(xml_schema, xml_extensions_schemas)

        return res
