#!/usr/bin/env python

import argparse
import pathlib
from xml.dom import minidom

import gmsh
import numpy as np
from svg.path import parse_path
from svg.path import Close, CubicBezier, Line, Move


def parse_svg_into_steps(path: str) -> list:
    path_str = minidom.parse(path).getElementsByTagName("path")[0].getAttribute("d")
    return parse_path(path_str)


if __name__ == "__main__":

    parser = argparse.ArgumentParser(description="Convert an SVG into an STL.")
    parser.add_argument("svg_path", type=str, help="path towards an SVG file")
    parser.add_argument("--thickness", default=1, type=float)
    parser.add_argument("--show", dest="show", action="store_true", default=False)
    args = parser.parse_args()

    steps = parse_svg_into_steps(args.svg_path)

    # Interpolate points

    shapes = []
    shape = []

    for step in steps:

        if isinstance(step, Line):
            shape.append([step.start.real, step.start.imag])

        elif isinstance(step, CubicBezier):

            x0 = step.start.real
            y0 = step.start.imag
            x1 = step.control1.real
            y1 = step.control1.imag
            x2 = step.control2.real
            y2 = step.control2.imag
            x3 = step.end.real
            y3 = step.end.imag

            t = np.linspace(0, 1, 5, endpoint=False)
            x = (
                x0 * (1 - t) ** 3
                + x1 * (1 - t) ** 2 * 3 * t
                + x2 * (1 - t) * 3 * t ** 2
                + x3 * t ** 3
            )
            y = (
                y0 * (1 - t) ** 3
                + y1 * (1 - t) ** 2 * 3 * t
                + y2 * (1 - t) * 3 * t ** 2
                + y3 * t ** 3
            )

            shape.extend(list(zip(x, y)))

        elif isinstance(step, Close):
            shapes.append(shape)
            shape = []

    x_min, y_min = np.vstack(shapes).min(axis=0)
    x_max, y_max = np.vstack(shapes).max(axis=0)
    x_pad = 0.1 * (x_max - x_min)
    y_pad = 0.1 * (y_max - y_min)
    corners = [
        [x_min - x_pad, y_min - y_pad],
        [x_min - x_pad, y_max + y_pad],
        [x_max + x_pad, y_max + y_pad],
        [x_max + x_pad, y_min - y_pad],
    ]
    shapes.append(corners)

    # Build walls

    gmsh.initialize()
    gmsh.model.add("test")

    z_floor = 0
    z_ceiling = args.thickness

    factory = gmsh.model.geo
    floor_lines = []
    ceiling_lines = []
    wall_lines = []

    for shape in shapes:

        floor_lines.append([])
        floor_points = [factory.addPoint(*shape[0], z_floor)]
        for vertex in shape[1:]:
            floor_points.append(factory.addPoint(*vertex, z_floor))
            floor_lines[-1].append(factory.addLine(floor_points[-2], floor_points[-1]))
        floor_lines[-1].append(factory.addLine(floor_points[-1], floor_points[0]))

        ceiling_lines.append([])
        ceiling_points = [factory.addPoint(*shape[0], z_ceiling)]
        for vertex in shape[1:]:
            ceiling_points.append(factory.addPoint(*vertex, z_ceiling))
            ceiling_lines[-1].append(
                factory.addLine(ceiling_points[-2], ceiling_points[-1])
            )
        ceiling_lines[-1].append(factory.addLine(ceiling_points[-1], ceiling_points[0]))

        wall_lines.append([])
        for floor_point, ceiling_point in zip(floor_points, ceiling_points):
            wall_line = factory.addLine(floor_point, ceiling_point)
            wall_lines[-1].append(wall_line)

        for i in range(1, len(floor_lines[-1])):
            wall = factory.addCurveLoop(
                [
                    floor_lines[-1][i - 1],
                    wall_lines[-1][i],
                    -ceiling_lines[-1][i - 1],
                    -wall_lines[-1][i - 1],
                ]
            )
            factory.addPlaneSurface([wall])
        wall = factory.addCurveLoop(
            [
                floor_lines[-1][-1],
                wall_lines[-1][0],
                -ceiling_lines[-1][-1],
                -wall_lines[-1][-1],
            ]
        )
        factory.addPlaneSurface([wall])

    floor = []
    for lines in floor_lines:
        hole = factory.addCurveLoop(lines)
        floor.append(hole)
    factory.addPlaneSurface(floor)

    ceiling = []
    for lines in ceiling_lines:
        hole = factory.addCurveLoop(lines)
        ceiling.append(hole)
    factory.addPlaneSurface(ceiling)

    gmsh.model.geo.synchronize()
    gmsh.model.mesh.generate()

    gmsh.write(str(pathlib.Path(args.svg_path).with_suffix(".stl")))

    if args.show:
        gmsh.fltk.run()
