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_walls(doc):
    """
    Delete all geometry belonging to Wall1, Wall2, Wall3 from the document.
    Uses Name/Label prefixes to find everything.
    """
    to_delete = []
    prefixes = ("Wall1", "Wall2", "Wall3")

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

    # Remove from document
    for obj in to_delete:
        doc.removeObject(obj.Name)


def build_wall_module_2x6(name, spacing_in, height_in, width_in, base_vec):
    """
    Build a 2x6 wall named `name` at a given base_vec (FreeCAD.Vector).
    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)
    b = doc.addObject("Part::Feature", f"{name}_BottomPlate")
    b.Shape = bottom
    grp.addObject(b)

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

    # studs
    stud_len = height - 2.0 * stud_t
    positions = [0.0]

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

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

    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))
        obj = doc.addObject("Part::Feature", f"{name}_Stud_{i:02d}")
        obj.Shape = s
        grp.addObject(obj)

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

    doc.recompute()
    return width


def build_header_for_wall(wall_name,
                          header_height_in,
                          header_length_in,
                          wall_height_in,
                          wall_base_vec,
                          wall_width_mm):
    """
    Build a header module adjacent to the wall, in the same group as the wall.

    - Header total height selectable (header_height_in).
    - Header length selectable (header_length_in).
    - Top of header aligns with top of wall.
    - Header is located along +X, directly after the wall.

    Returns header length in mm.
    """
    doc = FreeCAD.ActiveDocument
    if doc is None:
        doc = FreeCAD.newDocument("WallModuleDoc")

    header_h = inch_to_mm(header_height_in)
    header_len = inch_to_mm(header_length_in)
    wall_height_mm = inch_to_mm(wall_height_in)

    stud_t = inch_to_mm(1.5)      # plate thickness
    stud_d = inch_to_mm(5.5)      # wall/header thickness in Y (5.5")

    grp = doc.getObject(wall_name)
    if grp is None:
        return 0.0

    # Base of header: start after the wall in X,
    # and place bottom so that header top = wall top.
    header_base = wall_base_vec + FreeCAD.Vector(
        wall_width_mm,   # start right after wall
        0,
        wall_height_mm - header_h
    )

    # Bottom plate
    bottom = Part.makeBox(header_len, stud_d, stud_t)
    bottom.translate(header_base)
    b = doc.addObject("Part::Feature", f"{wall_name}_Header_BottomPlate")
    b.Shape = bottom
    grp.addObject(b)

    # Top plate
    top = Part.makeBox(header_len, stud_d, stud_t)
    top.translate(header_base + FreeCAD.Vector(0, 0, header_h - stud_t))
    t = doc.addObject("Part::Feature", f"{wall_name}_Header_TopPlate")
    t.Shape = top
    grp.addObject(t)

    # Solid block between plates
    block_h = header_h - 2.0 * stud_t
    if block_h > 0:
        block = Part.makeBox(header_len, stud_d, block_h)
        block.translate(header_base + FreeCAD.Vector(0, 0, stud_t))
        blk = doc.addObject("Part::Feature", f"{wall_name}_Header_Block")
        blk.Shape = block
        grp.addObject(blk)

    doc.recompute()
    return header_len


def rotate_wall_group(doc, group_name, origin_vec, angle_deg):
    """
    Rotate all objects in group `group_name` around Z axis by `angle_deg`
    about the given origin_vec (FreeCAD.Vector).
    """
    if abs(angle_deg) < 1e-6:
        return  # nothing to do

    grp = doc.getObject(group_name)
    if grp is None:
        return

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

    for obj in getattr(grp, "Group", []):
        pl = obj.Placement
        base = pl.Base
        rel = base - origin_vec
        new_base = origin_vec + rot.multVec(rel)
        pl.Base = new_base
        pl.Rotation = rot * pl.Rotation
        obj.Placement = pl

    doc.recompute()


class ThreeWallDialog(QtGui.QDialog):
    """
    Live-update UI for up to 3 walls.
    Any parameter change:
      - deletes existing walls
      - rebuilds the current configuration

    Walls are placed side-by-side along +X, with optional headers per wall.
    Each wall (including its header, if present) is rotated as a unit about
    the wall base point.
    """

    def __init__(self, parent=None):
        super(ThreeWallDialog, self).__init__(parent)
        self.setWindowTitle("Three Wall Modules – Live (Delete & Redraw)")

        layout = QtGui.QFormLayout(self)

        # ---- Wall 1 ----
        layout.addRow(QtGui.QLabel("------ WALL 1 ------"))
        self.w1enable = QtGui.QCheckBox("Enable Wall 1"); self.w1enable.setChecked(True)
        layout.addRow(self.w1enable)

        self.w1spacing = QtGui.QComboBox()
        self.w1spacing.addItem("16 in", 16.0)
        self.w1spacing.addItem("24 in", 24.0)
        layout.addRow("Wall 1 Spacing", self.w1spacing)

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

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

        self.w1angle = QtGui.QDoubleSpinBox()
        self.w1angle.setRange(-180.0, 180.0)
        self.w1angle.setValue(0.0)
        self.w1angle.setSuffix(" deg")
        layout.addRow("Wall 1 Rotation", self.w1angle)

        self.w1_header_enable = QtGui.QCheckBox("Enable Header after Wall 1")
        self.w1_header_enable.setChecked(False)
        layout.addRow(self.w1_header_enable)

        self.w1_header_height = QtGui.QDoubleSpinBox()
        self.w1_header_height.setRange(6.0, 48.0)
        self.w1_header_height.setValue(19.0)   # default 19"
        self.w1_header_height.setSuffix(" in")
        layout.addRow("Wall 1 Header Height", self.w1_header_height)

        self.w1_header_length = QtGui.QDoubleSpinBox()
        self.w1_header_length.setRange(12.0, 600.0)
        self.w1_header_length.setValue(144.0)  # default 12 ft
        self.w1_header_length.setSuffix(" in")
        layout.addRow("Wall 1 Header Length", self.w1_header_length)

        # ---- Wall 2 ----
        layout.addRow(QtGui.QLabel("------ WALL 2 ------"))
        self.w2enable = QtGui.QCheckBox("Enable Wall 2"); self.w2enable.setChecked(True)
        layout.addRow(self.w2enable)

        self.w2spacing = QtGui.QComboBox()
        self.w2spacing.addItem("16 in", 16.0)
        self.w2spacing.addItem("24 in", 24.0)
        layout.addRow("Wall 2 Spacing", self.w2spacing)

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

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

        self.w2angle = QtGui.QDoubleSpinBox()
        self.w2angle.setRange(-180.0, 180.0)
        self.w2angle.setValue(0.0)
        self.w2angle.setSuffix(" deg")
        layout.addRow("Wall 2 Rotation", self.w2angle)

        self.w2_header_enable = QtGui.QCheckBox("Enable Header after Wall 2")
        self.w2_header_enable.setChecked(False)
        layout.addRow(self.w2_header_enable)

        self.w2_header_height = QtGui.QDoubleSpinBox()
        self.w2_header_height.setRange(6.0, 48.0)
        self.w2_header_height.setValue(19.0)
        self.w2_header_height.setSuffix(" in")
        layout.addRow("Wall 2 Header Height", self.w2_header_height)

        self.w2_header_length = QtGui.QDoubleSpinBox()
        self.w2_header_length.setRange(12.0, 600.0)
        self.w2_header_length.setValue(144.0)
        self.w2_header_length.setSuffix(" in")
        layout.addRow("Wall 2 Header Length", self.w2_header_length)

        # ---- Wall 3 ----
        layout.addRow(QtGui.QLabel("------ WALL 3 ------"))
        self.w3enable = QtGui.QCheckBox("Enable Wall 3"); self.w3enable.setChecked(True)
        layout.addRow(self.w3enable)

        self.w3spacing = QtGui.QComboBox()
        self.w3spacing.addItem("16 in", 16.0)
        self.w3spacing.addItem("24 in", 24.0)
        layout.addRow("Wall 3 Spacing", self.w3spacing)

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

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

        self.w3angle = QtGui.QDoubleSpinBox()
        self.w3angle.setRange(-180.0, 180.0)
        self.w3angle.setValue(0.0)
        self.w3angle.setSuffix(" deg")
        layout.addRow("Wall 3 Rotation", self.w3angle)

        self.w3_header_enable = QtGui.QCheckBox("Enable Header after Wall 3")
        self.w3_header_enable.setChecked(False)
        layout.addRow(self.w3_header_enable)

        self.w3_header_height = QtGui.QDoubleSpinBox()
        self.w3_header_height.setRange(6.0, 48.0)
        self.w3_header_height.setValue(19.0)
        self.w3_header_height.setSuffix(" in")
        layout.addRow("Wall 3 Header Height", self.w3_header_height)

        self.w3_header_length = QtGui.QDoubleSpinBox()
        self.w3_header_length.setRange(12.0, 600.0)
        self.w3_header_length.setValue(144.0)
        self.w3_header_length.setSuffix(" in")
        layout.addRow("Wall 3 Header Length", self.w3_header_length)

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

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

    def _connect_live(self):
        controls = [
            self.w1enable, self.w1spacing, self.w1height, self.w1width, self.w1angle,
            self.w1_header_enable, self.w1_header_height, self.w1_header_length,

            self.w2enable, self.w2spacing, self.w2height, self.w2width, self.w2angle,
            self.w2_header_enable, self.w2_header_height, self.w2_header_length,

            self.w3enable, self.w3spacing, self.w3height, self.w3width, self.w3angle,
            self.w3_header_enable, self.w3_header_height, self.w3_header_length,
        ]

        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 all existing wall geometry
        clear_old_walls(doc)

        current_x = 0.0

        # Track bases for rotation per wall index
        bases = {}

        def maybe_build(idx,
                        enable,
                        spacing,
                        height,
                        width,
                        header_enable,
                        header_height_ctrl,
                        header_length_ctrl):
            nonlocal current_x
            if not enable:
                return
            s = spacing.itemData(spacing.currentIndex())
            h = height.value()
            w = width.value()
            name = f"Wall{idx}"
            base = FreeCAD.Vector(current_x, 0, 0)
            width_mm = build_wall_module_2x6(name, s, h, w, base)
            bases[idx] = (name, base, width_mm, h)
            current_x += width_mm

            # Optional header for this wall
            if header_enable:
                header_h_in = header_height_ctrl.value()
                header_len_in = header_length_ctrl.value()
                header_len_mm = build_header_for_wall(
                    name,
                    header_h_in,
                    header_len_in,
                    h,
                    base,
                    width_mm
                )
                current_x += header_len_mm

        # Build walls + optional headers in sequence (unrotated)
        maybe_build(
            1,
            self.w1enable.isChecked(),
            self.w1spacing,
            self.w1height,
            self.w1width,
            self.w1_header_enable.isChecked(),
            self.w1_header_height,
            self.w1_header_length
        )
        maybe_build(
            2,
            self.w2enable.isChecked(),
            self.w2spacing,
            self.w2height,
            self.w2width,
            self.w2_header_enable.isChecked(),
            self.w2_header_height,
            self.w2_header_length
        )
        maybe_build(
            3,
            self.w3enable.isChecked(),
            self.w3spacing,
            self.w3height,
            self.w3width,
            self.w3_header_enable.isChecked(),
            self.w3_header_height,
            self.w3_header_length
        )

        # Now apply per-wall rotation about each wall's base point
        if 1 in bases:
            name, base, _, _ = bases[1]
            rotate_wall_group(doc, name, base, self.w1angle.value())

        if 2 in bases:
            name, base, _, _ = bases[2]
            rotate_wall_group(doc, name, base, self.w2angle.value())

        if 3 in bases:
            name, base, _, _ = bases[3]
            rotate_wall_group(doc, name, base, self.w3angle.value())


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

main()

