from dataclasses import dataclass
import math
import cadquery as Cq
from typing import Optional
from nhf import Item, Role
import nhf.utils

@dataclass(frozen=True)
class FlatHeadBolt(Item):
    diam_head: float
    height_head: float
    diam_thread: float
    height_thread: float

    @property
    def name(self) -> str:
        return f"Bolt M{int(self.diam_thread)} h{int(self.height_thread)}mm"

    @property
    def role(self) -> Role:
        return Role.CONNECTION


    def generate(self) -> Cq.Assembly:
        head = Cq.Solid.makeCylinder(
            radius=self.diam_head / 2,
            height=self.height_head,
        )
        rod = (
            Cq.Workplane('XY')
            .cylinder(
                radius=self.diam_thread/ 2,
                height=self.height_thread,
                centered=(True, True, False))
        )
        rod.faces("<Z").tag("tip")
        rod.faces(">Z").tag("root")
        rod = rod.union(head.located(Cq.Location((0, 0, self.height_thread))))
        return rod


@dataclass(frozen=True)
class ThreaddedKnob(Item):
    """
    A threaded rod with knob on one side
    """
    diam_thread: float
    height_thread: float
    diam_knob: float

    diam_neck: float
    height_neck: float
    height_knob: float

    @property
    def name(self) -> str:
        return f"Knob M{int(self.diam_thread)} h{int(self.height_thread)}mm"

    def generate(self) -> Cq.Assembly:
        knob = Cq.Solid.makeCylinder(
            radius=self.diam_knob / 2,
            height=self.height_knob,
        )
        neck = Cq.Solid.makeCylinder(
            radius=self.diam_neck / 2,
            height=self.height_neck,
        )
        thread = (
            Cq.Workplane('XY')
            .cylinder(
                radius=self.diam_thread / 2,
                height=self.height_thread,
                centered=(True, True, False))
        )
        thread.faces("<Z").tag("tip")
        thread.faces(">Z").tag("root")

        return (
            Cq.Assembly()
            .addS(thread, name="thread", role=Role.CONNECTION)
            .addS(neck, name="neck", role=Role.HANDLE,
                  loc=Cq.Location((0, 0, self.height_thread)))
            .addS(knob, name="knob", role=Role.HANDLE,
                  loc=Cq.Location((0, 0, self.height_thread + self.height_neck)))
        )


@dataclass(frozen=True)
class HexNut(Item):
    diam_thread: float
    pitch: float

    thickness: float
    width: float

    def __post_init__(self):
        assert self.width > self.diam_thread

    @property
    def name(self):
        return f"HexNut M{int(self.diam_thread)}-{self.pitch}"

    @property
    def role(self) -> Role:
        return Role.CONNECTION

    @property
    def radius(self) -> float:
        return self.width / math.sqrt(3)

    def generate(self) -> Cq.Workplane:
        result = (
            Cq.Workplane("XY")
            .sketch()
            .regularPolygon(r=self.radius, n=6)
            .circle(r=self.diam_thread/2, mode='s')
            .finalize()
            .extrude(self.thickness)
        )
        result.faces("<Z").tag("bot")
        result.faces(">Z").tag("top")
        result.copyWorkplane(Cq.Workplane('XY')).tagPlane("dirX", direction="+X")
        return result

    def cutting_face(self) -> Cq.Face:
        return (
            Cq.Sketch()
            .regularPolygon(r=self.radius, n=6)
            ._faces
        )

@dataclass(frozen=True)
class Washer(Item):
    diam_thread: float
    diam_outer: float
    thickness: float
    material_name: Optional[float] = None

    def __post_init__(self):
        assert self.diam_outer > self.diam_thread
    @property
    def name(self):
        suffix = (" " + self.material_name) if self.material_name else ""
        return f"Washer M{int(self.diam_thread)}{suffix}"

    @property
    def role(self) -> Role:
        return Role.CONNECTION

    def generate(self) -> Cq.Workplane:
        result = (
            Cq.Workplane('XY')
            .cylinder(
                radius=self.diam_outer/2,
                height=self.thickness,
            )
            .faces(">Z")
            .hole(self.diam_thread)
        )
        result.faces("<Z").tag("bot")
        result.faces(">Z").tag("top")
        return result