from dataclasses import dataclass
import cadquery as Cq
from nhf import Material, Role
from nhf.build import Model, target, assembly, TargetKind, submodel
from nhf.parts.box import MountingBox, Hole
from nhf.parts.electronics import ArduinoUnoR3
import nhf.utils

@dataclass
class LightPanel(Model):

    # Dimensions of the base panel
    length: float = 300.0
    width: float = 200.0

    attach_height: float = 20.0
    attach_diam: float = 8.0
    attach_depth: float = 12.7

    grid_height: float = 20.0
    grid_top_height: float = 5.0
    # Distance from grid to edge
    grid_margin: float = 20.0
    # Number of holes in each row of the grid
    grid_holes: int = 9
    grid_layers: int = 6
    grid_hole_width: float = 15.0

    base_thickness: float = 25.4/16
    grid_thickness: float = 25.4/4
    base_material: Material = Material.WOOD_BIRCH
    grid_material: Material = Material.ACRYLIC_TRANSPARENT

    controller: ArduinoUnoR3 = ArduinoUnoR3()

    def __post_init__(self):
        assert self.grid_holes >= 2
        super().__init__(name="light-panel")

    @property
    def grid_spacing_y(self) -> float:
        return (self.width - 2 * self.grid_margin - self.grid_thickness) / (self.grid_layers - 1)

    @target(name="grid", kind=TargetKind.DXF)
    def grid_profile(self):
        w = self.length - self.grid_margin * 2
        h = self.grid_height + self.grid_top_height

        # The width of one hole (w0) satisfies
        # n * w0 + (n+1) t = w
        # where t is the thickness of the edge
        n = self.grid_holes
        w0 = self.grid_hole_width
        t = (w - n * w0) / (n + 1)
        # The spacing is such that the first and last holes are a distance `margin`
        # away from the edges, so it satisfies
        # t + w0/2 + (n-1) * s + w0/2 + t = w
        step = (w - t*2 - w0) / (n - 1)
        return (
            Cq.Sketch()
            .push([(0, h/2)])
            .rect(w, h)
            .push([
                (i * step + t + w0/2 - w/2, self.grid_height/2)
                for i in range(0, n)
            ])
            .rect(w0, self.grid_height, mode='s')
        )

    def grid(self) -> Cq.Workplane:
        return (
            Cq.Workplane('XY')
            .placeSketch(self.grid_profile())
            .extrude(self.grid_thickness)
        )

    @submodel(name="base")
    def base(self) -> MountingBox:
        xshift = self.length / 2 - self.controller.length - self.grid_margin / 2
        yshift = self.grid_margin / 2
        holes = [
            Hole(
                x=x + xshift, y=y + yshift,
                diam=self.controller.hole_diam,
                tag=f"controller_conn{i}",
            )
            for i, (x, y) in enumerate(self.controller.holes)
        ]
        return MountingBox(
            holes=holes,
            hole_diam=self.controller.hole_diam,
            length=self.length,
            width=self.width,
            centred=(True, False),
            thickness=self.base_thickness,
        )

    @target(name="attachment")
    def attachment(self) -> Cq.Workplane:
        l = self.length / 2
        w = self.width / 2
        return (
            Cq.Workplane('XY')
            .box(
                l, w, self.attach_height,
                centered=(True, True, False),
            )
            .faces(">Z")
            .hole(self.attach_diam, self.attach_depth)
        )


    def assembly(self) -> Cq.Assembly:
        assembly = (
            Cq.Assembly()
            .addS(
                self.base().generate(),
                name="base",
                role=Role.STRUCTURE,
                material=self.base_material,
            )
        )
        # Grid thickness t is fixed, so the spacing of the grid satisfies
        # margin + t + (n-1) * spacing + margin = width
        spacing = self.grid_spacing_y
        shift = self.grid_margin + self.grid_thickness / 2
        for i in range(self.grid_layers):
            assembly = assembly.addS(
                self.grid(),
                name=f"grid_{i}",
                role=Role.STRUCTURE,
                material=self.grid_material,
                loc=Cq.Location(0, spacing * i + shift, self.base_thickness, 90, 0, 0),
            )
        return assembly


if __name__ == '__main__':
    import sys

    p = LightPanel()
    print(p.grid_spacing_y)

    if len(sys.argv) == 1:
        p.build_all()
        sys.exit(0)