#!/usr/bin/env python3

from pathlib import Path
import re
from typing import Optional, Tuple

from attrs import define


OPTION_PATTERN = re.compile("; (?P<key>[a-z0-9_]+) = (?P<value>.*?)\n")


def parse_prusa_config_str(content: str) -> dict:
    kvs = {}
    iter = re.finditer(OPTION_PATTERN, content)
    while m := next(iter, None):
        if m.group("key") == "prusaslicer_config" and m.group("value") == "begin":
            break
    while m := next(iter, None):
        if m.group("key") == "prusaslicer_config" and m.group("value") == "end":
            break
        else:
            kvs[m.group("key")] = m.group("value")

    return kvs


def parse_prusa_config(p: Path):
    with open(p) as fp:
        return parse_prusa_config_str(fp.read())


@define
class GcodeAnalysis:
    max_x: int
    max_y: int
    max_z: int
    max_bed: int
    max_end: int
    filament: str
    nozzle: int


def parse_point(point: str) -> Tuple[int, int]:
    a, b = point.split("x")
    return int(a), int(b)


def parse_bed_shape(coords: str) -> Tuple[int, int]:
    # Note, these are clockwise from 0, 0
    a, b, c, d = coords.split(",")
    dx, _ = parse_point(a)
    _, dy = parse_point(b)
    x, y = parse_point(c)
    return x + dx, y + dy


def analyze_gcode_str(text: str) -> Optional[GcodeAnalysis]:
    opts = parse_prusa_config_str(text)

    kwargs = {}
    if "bed_shape" in opts:
        max_x, max_y = parse_bed_shape(opts["bed_shape"])
        kwargs["max_x"] = max_x
        kwargs["max_y"] = max_y
    else:
        return None

    if "max_print_height" in opts:
        kwargs["max_z"] = int(opts["max_print_height"])
    else:
        return None

    if "first_layer_bed_temperature" in opts:
        kwargs["max_bed"] = int(opts["first_layer_bed_temperature"])
    elif "bed_temperature" in opts:
        kwargs["max_bed"] = int(opts["bed_temperature"])
    else:
        return None

    if "first_layer_temperature" in opts:
        kwargs["max_end"] = int(opts["first_layer_temperature"])
    elif "temperature" in opts:
        kwargs["max_end"] = int(opts["temperature"])
    else:
        return None

    if "filament_type" in opts:
        kwargs["filament"] = opts["filament_type"]
    else:
        return None

    if "nozzle_diameter" in opts:
        kwargs["nozzle"] = float(opts["nozzle_diameter"])
    else:
        return None

    return GcodeAnalysis(**kwargs)


def analyze_gcode_file(p: Path) -> Optional[GcodeAnalysis]:
    with open(p) as fp:
        return analyze_gcode_str(fp.read())
