"""
Draw test nodes, ways, and relations.
"""
import logging
from pathlib import Path
from typing import Optional

import numpy as np
from svgwrite import Drawing

from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.constructor import Constructor
from map_machine.geometry.flinger import Flinger
from map_machine.pictogram.icon import ShapeExtractor
from map_machine.map_configuration import MapConfiguration
from map_machine.mapper import Map
from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay
from map_machine.scheme import Scheme
from map_machine.workspace import Workspace

workspace: Workspace = Workspace(Path("temp"))

SCHEME: Scheme = Scheme.from_file(workspace.DEFAULT_SCHEME_PATH)
SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor(
    workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
)
DEFAULT_ZOOM: float = 18.0


HIGHWAY_VALUES: list[str] = [
    "motorway",
    "trunk",
    "primary",
    "secondary",
    "tertiary",
    "unclassified",
    "residential",
    "service",
    "service_minor",
    "road",
    "pedestrian",
    "living_street",
    "bridleway",
    "cycleway",
    "footway",
    "steps",
    "path",
    "track",
    "raceway",
]

AEROWAY_VALUES: list[str] = [
    "runway",
    "taxiway",
]

RAILWAY_TAGS: list[dict[str, str]] = [
    {"railway": "rail"},
    {"railway": "light_rail"},
    {"railway": "monorail"},
    {"railway": "funicular"},
    {"railway": "narrow_gauge"},
    {"railway": "subway"},
    {"railway": "subway", "color": "red"},
    {"railway": "subway", "color": "blue"},
]

ROAD_WIDTHS_AND_FEATURES: list[dict[str, str]] = [
    {"width": "4"},
    {"width": "8"},
    {"width": "12"},
    {"width": "16"},
    {"bridge": "yes", "width": "4"},
    {"bridge": "yes", "width": "8"},
    {"tunnel": "yes", "width": "4"},
    {"tunnel": "yes", "width": "8"},
    {"ford": "yes", "width": "4"},
    {"ford": "yes", "width": "8"},
    {"embankment": "yes", "width": "4"},
    {"embankment": "yes", "width": "8"},
]


ROAD_LANES_AND_FEATURES: list[dict[str, str]] = [
    {"lanes": "1"},
    {"lanes": "2"},
    {"lanes": "3"},
    {"lanes": "4"},
    {"bridge": "yes", "lanes": "1"},
    {"bridge": "yes", "lanes": "2"},
    {"tunnel": "yes", "lanes": "1"},
    {"tunnel": "yes", "lanes": "2"},
    {"ford": "yes", "lanes": "1"},
    {"ford": "yes", "lanes": "2"},
    {"embankment": "yes", "lanes": "1"},
    {"embankment": "yes", "lanes": "2"},
]


# See https://wiki.openstreetmap.org/wiki/Proposed_features/placement

PLACEMENT_FEATURES_1: list[dict[str, str]] = [
    {"lanes": "1"},
    {"lanes": "2", "placement": "middle_of:1"},
    {"lanes": "4", "placement": "middle_of:2"},
    {"placement": "transition"},
    {"lanes": "3", "placement": "right_of:1"},  # or placement=left_of:2
]

PLACEMENT_FEATURES_2: list[dict[str, str]] = [
    {"lanes": "2"},
    # or placement:backward=left_of:1
    {"lanes": "3", "placement:forward": "left_of:1"},
    {"lanes": "3", "placement": "transition"},
    {"lanes": "4", "placement:backward": "middle_of:1"},
    {"lanes": "3"},
]


class Grid:
    """Creating map with elements ordered in grid."""

    def __init__(self) -> None:
        self.x_step: float = 0.0002
        self.y_step: float = 0.0003
        self.x_start: float = 0.0
        self.index: int = 0
        self.nodes: dict[OSMNode, tuple[int, int]] = {}
        self.max_j: float = 0
        self.max_i: float = 0

    def add_node(self, tags: dict[str, str], i: int, j: int) -> OSMNode:
        """Add OSM node to the grid."""
        self.index += 1
        node: OSMNode = OSMNode(
            tags,
            self.index,
            np.array((-i * self.y_step, j * self.x_step)),
        )
        self.nodes[node] = (j, i)
        self.max_j = max(self.max_j, j * self.x_step)
        self.max_i = max(self.max_i, i * self.y_step)
        return node

    def get_boundary_box(self) -> BoundaryBox:
        """Compute resulting boundary box with margin of one grid step."""
        return BoundaryBox(
            -self.x_step,
            -self.max_i - self.y_step,
            self.max_j + self.x_step,
            self.y_step,
        )


def road_features(
    types: list[dict[str, str]], features: list[dict[str, str]], path: Path
) -> None:
    """Draw test image with different road features."""
    osm_data: OSMData = OSMData()
    grid: Grid = Grid()

    for i, type_ in enumerate(types):
        previous: Optional[OSMNode] = None

        for j in range(len(features) + 1):
            node: OSMNode = grid.add_node({}, i, j)

            if previous:
                tags: dict[str, str] = dict(type_)
                tags |= dict(features[j - 1])
                way: OSMWay = OSMWay(
                    tags, i * (len(features) + 1) + j, [previous, node]
                )
                osm_data.add_way(way)
            previous = node

    draw(osm_data, path, grid.get_boundary_box())


def draw(
    osm_data: OSMData,
    output_path: Path,
    boundary_box: BoundaryBox,
    zoom: float = DEFAULT_ZOOM,
) -> None:
    """Draw map."""
    configuration: MapConfiguration = MapConfiguration(level="all")

    flinger: Flinger = Flinger(boundary_box, zoom, osm_data.equator_length)
    svg: Drawing = Drawing(output_path.name, flinger.size)
    constructor: Constructor = Constructor(
        osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration
    )
    constructor.construct()
    map_: Map = Map(flinger, svg, SCHEME, configuration)
    map_.draw(constructor)

    with output_path.open("w") as output_file:
        svg.write(output_file)
        logging.info(f"Map is drawn to {output_path}.")


if __name__ == "__main__":
    logging.basicConfig(format="%(levelname)s %(message)s", level=logging.INFO)

    highway_tags: list[dict[str, str]] = [
        {"highway": value} for value in HIGHWAY_VALUES
    ]
    aeroway_tags: list[dict[str, str]] = [
        {"aeroway": value} for value in AEROWAY_VALUES
    ]

    road_features(
        highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg"
    )
    road_features(
        highway_tags + RAILWAY_TAGS + aeroway_tags,
        ROAD_WIDTHS_AND_FEATURES,
        Path("out") / "width.svg",
    )
    road_features(
        highway_tags,
        PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2,
        Path("out") / "placement.svg",
    )