import FreeCAD
import FreeCADGui
import Part
from PySide import QtGui
import math

INCH_TO_MM = 25.4

def inch_to_mm(v):
    return float(v) * INCH_TO_MM


def clear_old_wall(doc):
    """
    Delete all geometry belonging to Wall1 from the document.
    Uses Name/Label prefixes to find everything.
    """
    to_delete = []
    prefixes = ("Wall1",)

    for obj in list(doc.Objects):
        name = getattr(obj, "Name", "")
        label = getattr(obj, "Label", "")
        if name.startswith(prefixes) or label.startswith(prefixes):
            to_delete.append(obj)

    for obj in to_delete:
        try:
            doc.removeObject(obj.Name)
        except Exception:
            pass


def build_wall_module_2x6_base(name, spacing_in, height_in, width_in, base_vec):
    """
    Build a 2x6 wall named `name` at a given base_vec (FreeCAD.Vector).
    NO ROTATION. Wall runs along +X from base_vec.

    Returns the wall width in mm.
    """

    doc = FreeCAD.ActiveDocument
    if doc is None:
        doc = FreeCAD.newDocument("WallModuleDoc")

    # convert to mm
    spacing = inch_to_mm(spacing_in)
    height  = inch_to_mm(height_in)
    width   = inch_to_mm(width_in)

    stud_t = inch_to_mm(1.5)      # 2x6 actual thickness (1.5")
    stud_d = inch_to_mm(5.5)      # wall thickness (5.5")
    osb_t  = inch_to_mm(0.4375)   # 7/16" OSB

    grp = doc.addObject("App::DocumentObjectGroup", name)

    # bottom plate
    bottom = Part.makeBox(width, stud_d, stud_t)
    bottom.translate(base_vec)
    bottom_obj = doc.addObject("Part::Feature", f"{name}_BottomPlate")
    bottom_obj.Shape = bottom
    grp.addObject(bottom_obj)

    # top plate
    top = Part.makeBox(width, stud_d, stud_t)
    top.translate(base_vec + FreeCAD.Vector(0, 0, height - stud_t))
    top_obj = doc.addObject("Part::Feature", f"{name}_TopPlate")
    top_obj.Shape = top
    grp.addObject(top_obj)

    # studs
    stud_len = height - 2.0 * stud_t
    if stud_len <= 0:
        FreeCAD.Console.PrintError(f"{name}: wall height too small for plates + studs.\n")
        doc.recompute()
        return width

    positions = [0.0]  # left end stud

    # interior studs
    n_max = int(math.floor(width_in / spacing_in))
    for n in range(1, n_max + 1):
        x_pos = inch_to_mm(n * spacing_in)
        if x_pos <= (width - stud_t):
            positions.append(x_pos)

    # right end stud
    right_pos = width - stud_t
    if all(abs(right_pos - p) > 1.0 for p in positions):
        positions.append(right_pos)

    positions = sorted(positions)

    for i, x in enumerate(positions):
        s = Part.makeBox(stud_t, stud_d, stud_len)
        s.translate(base_vec + FreeCAD.Vector(x, 0, stud_t))
        stud_obj = doc.addObject("Part::Feature", f"{name}_Stud_{i:02d}")
        stud_obj.Shape = s
        grp.addObject(stud_obj)

    # OSB on +Y side
    osb = Part.makeBox(width, osb_t, height)
    osb.translate(base_vec + FreeCAD.Vector(0, stud_d, 0))
    osb_obj = doc.addObject("Part::Feature", f"{name}_OSB")
    osb_obj.Shape = osb
    grp.addObject(osb_obj)

    doc.recompute()
    return width


def rotate_wall_group(name, angle_deg):
    """
    Rotate all children of wall group `name` about global Z axis at origin by angle_deg.
    """
    doc = FreeCAD.ActiveDocument
    grp = doc.getObject(name)
    if grp is None:
        return

    rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle_deg)
    base = FreeCAD.Vector(0, 0, 0)
    placement_rot = FreeCAD.Placement(base, rot)

    for obj in grp.OutList:
        # compose new placement as rotation * existing placement
        obj.Placement = placement_rot.multiply(obj.Placement)


class SingleWallDialog(QtGui.QDialog):
    """
    Live-update UI for a single wall (Wall1) with rotation.
    Any parameter change:
      - deletes previous Wall1 geometry
      - rebuilds Wall1 at origin (unrotated)
      - rotates Wall1 as a rigid body about Z by the given angle.
    """

    def __init__(self, parent=None):
        super(SingleWallDialog, self).__init__(parent)
        self.setWindowTitle("Single Wall Module – Live with Angle")

        layout = QtGui.QFormLayout(self)

        # Enable
        self.enable = QtGui.QCheckBox("Enable Wall 1")
        self.enable.setChecked(True)
        layout.addRow(self.enable)

        # Spacing
        self.spacing = QtGui.QComboBox()
        self.spacing.addItem("16 in", 16.0)
        self.spacing.addItem("24 in", 24.0)
        layout.addRow("Stud Spacing", self.spacing)

        # Height
        self.height = QtGui.QDoubleSpinBox()
        self.height.setRange(24.0, 168.0)
        self.height.setValue(96.0)
        self.height.setSuffix(" in")
        layout.addRow("Wall Height", self.height)

        # Width
        self.width = QtGui.QDoubleSpinBox()
        self.width.setRange(12.0, 600.0)
        self.width.setValue(48.0)
        self.width.setSuffix(" in")
        layout.addRow("Wall Width", self.width)

        # Angle
        self.angle = QtGui.QDoubleSpinBox()
        self.angle.setRange(-360.0, 360.0)
        self.angle.setValue(0.0)
        self.angle.setSuffix(" °")
        layout.addRow("Wall Angle (deg about Z)", self.angle)

        # Close button
        closebtn = QtGui.QPushButton("Close")
        closebtn.clicked.connect(self.close)
        layout.addRow(closebtn)

        # Wire live updates
        self._connect_live()
        self.update_model()

    def _connect_live(self):
        controls = [self.enable, self.spacing, self.height, self.width, self.angle]
        for c in controls:
            if hasattr(c, "stateChanged"):
                c.stateChanged.connect(self.update_model)
            elif hasattr(c, "currentIndexChanged"):
                c.currentIndexChanged.connect(self.update_model)
            elif hasattr(c, "valueChanged"):
                c.valueChanged.connect(self.update_model)

    def update_model(self, *args):
        doc = FreeCAD.ActiveDocument
        if doc is None:
            doc = FreeCAD.newDocument("WallModuleDoc")

        # Delete previous Wall1
        clear_old_wall(doc)

        if not self.enable.isChecked():
            doc.recompute()
            return

        s = self.spacing.itemData(self.spacing.currentIndex())
        h = self.height.value()
        w = self.width.value()
        a = self.angle.value()

        # Build unrotated wall at origin
        build_wall_module_2x6_base("Wall1", s, h, w, FreeCAD.Vector(0, 0, 0))
        # Rotate the entire wall assembly
        rotate_wall_group("Wall1", a)


def main():
    mw = FreeCADGui.getMainWindow()
    dlg = SingleWallDialog(mw)
    dlg.show()

main()
