from nhf.build import Model, TargetKind, target, assembly, submodel
from nhf.materials import Role, Material
import nhf.utils
from nhf.parts.fasteners import FlatHeadBolt, HexNut, Washer
from nhf.parts.electronics import ArduinoUnoR3, BatteryBox18650

from typing import Optional, Union
import math
from dataclasses import dataclass, field
import cadquery as Cq

NUT_COMMON = HexNut(
    # FIXME: weigh
    mass=0.0,
    diam_thread=6.0,
    pitch=1.0,
    thickness=5.0,
    width=9.89,
)
BOLT_COMMON = FlatHeadBolt(
    # FIXME: weigh
    mass=0.0,
    diam_head=12.8,
    height_head=2.8,
    diam_thread=6.0,
    height_thread=30.0,
    pitch=1.0,
)

@dataclass
class Shimenawa(Model):
    """
    The ring
    """

    diam_inner: float = 43.0
    diam_outer: float = 43.0 + 9 * 2

    diam_hole_outer: float = 8.0
    hole_ext: float = 2.0
    hole_z: float = 15.0

    pipe_fitting_angle_span: float = 6.0

    pipe_joint_length: float = 120.0
    pipe_joint_outer_thickness: float = 5.0
    pipe_joint_inner_thickness: float = 4.0

    pipe_joint_inner_angle_span: float = 120.0
    pipe_joint_taper: float = 5.0
    pipe_joint_taper_length: float = 10.0

    ear_dr: float = 6.0
    ear_hole_diam: float = 10.0
    ear_radius: float = 15.0
    ear_thickness: float = 10.0

    main_circumference: float = 3600.0

    material_fitting: Material = Material.PLASTIC_PLA

    def __post_init__(self):
        assert self.diam_inner < self.diam_outer

    @property
    def main_radius(self) -> float:
        return self.main_circumference / (2 * math.pi)

    @target(name="pipe-fitting-curved")
    def pipe_fitting_curved(self) -> Cq.Workplane:
        r_minor = self.diam_outer/2 + self.pipe_joint_outer_thickness
        a1 = self.pipe_fitting_angle_span
        outer = Cq.Solid.makeTorus(
            radius1=self.main_radius,
            radius2=r_minor,
        )
        inner = Cq.Solid.makeTorus(
            radius1=self.main_radius,
            radius2=self.diam_outer/2,
        )
        angle_intersector = Cq.Solid.makeCylinder(
            radius=self.main_radius + r_minor,
            height=r_minor*2,
            angleDegrees=a1,
            pnt=(0,0,-r_minor)
        ).rotate((0,0,0),(0,0,1),-a1/2)
        result = (outer - inner) * angle_intersector

        ear_outer = Cq.Solid.makeCylinder(
            radius=self.ear_radius,
            height=self.ear_thickness,
            pnt=(0,-self.ear_thickness/2,0),
            dir=(0,1,0),
        )
        ear_hole = Cq.Solid.makeCylinder(
            radius=self.ear_hole_diam/2,
            height=self.ear_thickness,
            pnt=(-self.ear_dr,-self.ear_thickness/2,0),
            dir=(0,1,0),
        )
        ear = (ear_outer - ear_hole).moved(self.main_radius - r_minor, 0, 0)
        result += ear - inner
        return result
    @target(name="pipe-joint-outer")
    def pipe_joint_outer(self) -> Cq.Workplane:
        """
        Used to joint two pipes together (outside)
        """
        r1 = self.diam_outer / 2 + self.pipe_joint_outer_thickness
        h = self.pipe_joint_length
        result = (
            Cq.Workplane()
            .cylinder(
                radius=r1,
                height=self.pipe_joint_length,
            )
        )
        cut_interior = Cq.Solid.makeCylinder(
            radius=self.diam_outer/2,
            height=h,
            pnt=(0, 0, -h/2)
        )
        rh = r1 + self.hole_ext
        add_hole = Cq.Solid.makeCylinder(
            radius=self.diam_hole_outer/2,
            height=rh*2,
            pnt=(-rh, 0, 0),
            dir=(1, 0, 0),
        )
        cut_hole = Cq.Solid.makeCylinder(
            radius=BOLT_COMMON.diam_thread/2,
            height=rh*2,
            pnt=(-rh, 0, 0),
            dir=(1, 0, 0),
        )
        z = self.hole_z
        result = (
            result
            + add_hole.moved(0, 0, -z)
            + add_hole.moved(0, 0, z)
            - cut_hole.moved(0, 0, -z)
            - cut_hole.moved(0, 0, z)
            - cut_interior
        )
        ear_outer = Cq.Solid.makeCylinder(
            radius=self.ear_radius,
            height=self.ear_thickness,
            pnt=(0, r1, -self.ear_thickness/2),
        )
        ear_hole = Cq.Solid.makeCylinder(
            radius=self.ear_hole_diam/2,
            height=self.ear_thickness,
            pnt=(0,r1+self.ear_dr,-self.ear_thickness/2),
        )
        ear = ear_outer - ear_hole - cut_interior
        return result + ear

    @target(name="pipe-joint-inner")
    def pipe_joint_inner(self) -> Cq.Workplane:
        """
        Used to joint two pipes together (inside)
        """
        r1 = self.diam_inner / 2
        r2 = r1 - self.pipe_joint_taper
        r3 = r2 - self.pipe_joint_inner_thickness
        h = self.pipe_joint_length
        h0 = h - self.pipe_joint_taper_length*2
        core = Cq.Solid.makeCylinder(
            radius=r2,
            height=h0/2,
        )
        centre_cut = Cq.Solid.makeCylinder(
            radius=r3,
            height=h0/2,
        )
        taper = Cq.Solid.makeCone(
            radius1=r2,
            radius2=r1,
            height=(h - h0) / 2,
            pnt=(0, 0, h0/2),
        )
        centre_cut_taper = Cq.Solid.makeCone(
            radius1=r3,
            radius2=r3 + self.pipe_joint_taper,
            height=(h - h0) / 2,
            pnt=(0, 0, h0/2),
        )
        angle_intersector = Cq.Solid.makeCylinder(
            radius=r1,
            height=h,
            angleDegrees=self.pipe_joint_inner_angle_span
        ).rotate((0,0,0), (0,0,1), -self.pipe_joint_inner_angle_span/2)
        result = (taper + core - centre_cut - centre_cut_taper) * angle_intersector

        result += result.mirror("XY")

        add_hole = Cq.Solid.makeCylinder(
            radius=self.diam_hole_outer/2,
            height=self.hole_ext,
            pnt=(r3, 0, 0),
            dir=(-1, 0, 0),
        )
        cut_hole = Cq.Solid.makeCylinder(
            radius=BOLT_COMMON.diam_thread/2,
            height=r1,
            pnt=(0, 0, 0),
            dir=(r1, 0, 0),
        )
        z = self.hole_z
        # avoid collisions
        nut_x = r3 - self.hole_ext - NUT_COMMON.thickness
        nut = NUT_COMMON.generate().val().rotate((0,0,0),(0,1,0),90)
        result = (
            result
            + add_hole.moved(0, 0, z)
            + add_hole.moved(0, 0, -z)
            - cut_hole.moved(0, 0, z)
            - cut_hole.moved(0, 0, -z)
            - nut.moved(nut_x, 0, z)
            - nut.moved(nut_x, 0, -z)
        )
        return result
    @assembly()
    def assembly_pipe_joint(self) -> Cq.Assembly:
        a = (
            Cq.Assembly()
            .addS(
                self.pipe_joint_outer(),
                name="joint_outer",
                material=self.material_fitting,
                role=Role.STRUCTURE,
            )
            .addS(
                self.pipe_joint_inner(),
                name="joint_inner1",
                material=self.material_fitting,
                role=Role.STRUCTURE,
            )
            .addS(
                self.pipe_joint_inner(),
                name="joint_inner2",
                material=self.material_fitting,
                role=Role.STRUCTURE,
                loc=Cq.Location.rot2d(180),
            )
        )
        return a

    @assembly()
    def assembly(self) -> Cq.Assembly:
        a = (
            Cq.Assembly()
            .addS(
                self.pipe_fitting_curved(),
                name="fitting1",
                material=self.material_fitting,
                role=Role.STRUCTURE,
            )
            .add(
                self.assembly_pipe_joint(),
                name="pipe_joint",
            )
        )
        return a