import sys
import math
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QToolBar, QPushButton, QComboBox, QGraphicsRectItem
)
from PyQt5.QtGui import QPen, QBrush, QColor, QPainterPath
from PyQt5.QtCore import Qt, QPointF, QRectF


class ResizeablePoint(QGraphicsRectItem):
    """A draggable point to modify shapes."""
    def __init__(self, x, y, size, update_callback):
        super().__init__(x - size / 2, y - size / 2, size, size)
        self.setBrush(QBrush(Qt.red))  # Red square (control point)
        self.setFlags(
            QGraphicsRectItem.ItemIsMovable |  # Enable dragging
            QGraphicsRectItem.ItemSendsGeometryChanges  # Notify on movement
        )
        self.update_callback = update_callback

    def itemChange(self, change, value):
        """Detect and notify shape updates when the control point is moved."""
        if change == QGraphicsRectItem.ItemPositionChange and self.update_callback:
            self.update_callback(self)  # Trigger callback to update the shape
        return super().itemChange(change, value)


class CADCAMWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # Window setup
        self.setWindowTitle("CAD-CAM Program")
        self.setGeometry(100, 100, 1200, 800)

        # Create a graphics rendering area
        self.scene = QGraphicsScene()
        self.graphics_view = QGraphicsView(self.scene)
        self.setCentralWidget(self.graphics_view)

        # Default workspace background
        self.scene.setBackgroundBrush(QBrush(QColor(240, 240, 240)))  # Light gray

        # Shape Properties
        self.current_shape = None  # Type of the shape ('line', 'rectangle', 'circle')
        self.start_point = None    # Start point of the shape
        self.preview_item = None   # Temporary shape for live preview
        self.snap_to_grid_enabled = True
        self.grid_size = 20        # Size of grid for snapping

        # Initialize Toolbar and Grid
        self.init_toolbar()
        self.draw_grid()

        # Enable mouse tracking
        self.graphics_view.setMouseTracking(True)
        self.graphics_view.viewport().installEventFilter(self)

    # ---------- Toolbar Setup ---------- #

    def init_toolbar(self):
        """Setup the toolbar with buttons for drawing shapes."""
        toolbar = QToolBar("Shape Tools", self)
        self.addToolBar(toolbar)

        # Toolbar Buttons
        buttons = [
            {"name": "Draw Line", "callback": self.start_line_drawing, "color": "#FFA07A"},
            {"name": "Draw Circle", "callback": self.start_circle_drawing, "color": "#20B2AA"},
            {"name": "Draw Rectangle", "callback": self.start_rectangle_drawing, "color": "#FFD700"},
            {"name": "Toggle Snap to Grid", "callback": self.toggle_snap_to_grid, "color": "#9370DB"}
        ]

        for button in buttons:
            btn = QPushButton(button["name"])
            btn.setStyleSheet(self.style_button(button["color"]))
            btn.clicked.connect(button["callback"])
            toolbar.addWidget(btn)

    def style_button(self, color):
        return f"""
        QPushButton {{
            background-color: {color};
            color: black;
            border-radius: 5px;
            padding: 5px;
        }}
        QPushButton:hover {{
            background-color: #E0E0E0;
        }}
        """

    def draw_grid(self):
        """Draw a snapping grid on the canvas."""
        grid_pen = QPen(QColor(200, 200, 200), 1, Qt.DashLine)
        self.scene.clear()  # Clear previous shapes and grid
        scene_width, scene_height = 2000, 2000

        # Vertical lines
        for x in range(0, scene_width, self.grid_size):
            self.scene.addLine(x, 0, x, scene_height, grid_pen)
        # Horizontal lines
        for y in range(0, scene_height, self.grid_size):
            self.scene.addLine(0, y, scene_width, y, grid_pen)

    # ---------- Event Handling for Drawing ---------- #

    def eventFilter(self, source, event):
        """Handle mouse events for drawing shapes."""
        if event.type() == event.MouseButtonPress and source is self.graphics_view.viewport():
            scene_pos = self.graphics_view.mapToScene(event.pos())
            if self.snap_to_grid_enabled:
                x, y = self.snap_to_grid(scene_pos.x()), self.snap_to_grid(scene_pos.y())
            else:
                x, y = scene_pos.x(), scene_pos.y()
            self.start_point = QPointF(x, y)
            return True

        elif event.type() == event.MouseMove and self.start_point and source is self.graphics_view.viewport():
            scene_pos = self.graphics_view.mapToScene(event.pos())
            x, y = scene_pos.x(), scene_pos.y()
            if self.snap_to_grid_enabled:
                x, y = self.snap_to_grid(x), self.snap_to_grid(y)
            self.update_preview(QPointF(x, y))
            return True

        elif event.type() == event.MouseButtonRelease and self.start_point and source is self.graphics_view.viewport():
            scene_pos = self.graphics_view.mapToScene(event.pos())
            x, y = scene_pos.x(), scene_pos.y()
            if self.snap_to_grid_enabled:
                x, y = self.snap_to_grid(x), self.snap_to_grid(y)
            end_point = QPointF(x, y)
            self.create_final_shape(end_point)
            self.start_point = None
            if self.preview_item:
                self.scene.removeItem(self.preview_item)
                self.preview_item = None
            return True

        return super().eventFilter(source, event)

    def update_preview(self, current_point):
        """Update the real-time preview while drawing the shape."""
        if self.preview_item:
            self.scene.removeItem(self.preview_item)

        if self.current_shape == "line":
            pen = QPen(Qt.red, 2)
            self.preview_item = self.scene.addLine(
                self.start_point.x(), self.start_point.y(),
                current_point.x(), current_point.y(), pen
            )
        elif self.current_shape == "rectangle":
            rect = QRectF(self.start_point, current_point).normalized()
            pen = QPen(Qt.green, 2)
            self.preview_item = self.scene.addRect(rect, pen)
        elif self.current_shape == "circle":
            radius = math.sqrt((current_point.x() - self.start_point.x())**2 +
                               (current_point.y() - self.start_point.y())**2)
            path = QPainterPath()
            path.addEllipse(self.start_point, radius, radius)
            pen = QPen(Qt.blue, 2)
            self.preview_item = self.scene.addPath(path, pen)

    def create_final_shape(self, end_point):
        """Finalize the shape when the mouse button is released."""
        if self.current_shape == "line":
            self.add_line(self.start_point, end_point)
        elif self.current_shape == "rectangle":
            self.add_rectangle(self.start_point, end_point)
        elif self.current_shape == "circle":
            self.add_circle(self.start_point, end_point)

    # ---------- Shape-Specific Drawing Logic ---------- #

    def add_line(self, start, end):
        """Draw a line with control points."""
        line = self.scene.addLine(start.x(), start.y(), end.x(), end.y(), QPen(Qt.red, 2))
        self.add_control_point(start.x(), start.y(), lambda p: self.update_line(line, p, "start"))
        self.add_control_point(end.x(), end.y(), lambda p: self.update_line(line, p, "end"))

    def add_circle(self, center, edge):
        """Draw a circle with four control points."""
        radius = math.sqrt((edge.x() - center.x())**2 + (edge.y() - center.y())**2)
        path = QPainterPath()
        path.addEllipse(center, radius, radius)
        circle_item = self.scene.addPath(path, QPen(Qt.blue, 2))

        # Add control points for top, bottom, left, right of the circle
        self.add_control_point(center.x(), center.y() - radius, lambda p: self.update_circle(circle_item, center, "top", p))
        self.add_control_point(center.x() + radius, center.y(), lambda p: self.update_circle(circle_item, center, "right", p))
        self.add_control_point(center.x(), center.y() + radius, lambda p: self.update_circle(circle_item, center, "bottom", p))
        self.add_control_point(center.x() - radius, center.y(), lambda p: self.update_circle(circle_item, center, "left", p))

    def add_rectangle(self, start, end):
        """Draw a rectangle with control points."""
        rect = QRectF(start, end).normalized()
        rect_item = self.scene.addRect(rect, QPen(Qt.green, 2))

        # Add control points for each corner
        self.add_control_point(rect.topLeft().x(), rect.topLeft().y(), lambda p: self.update_rectangle(rect_item, "topLeft", p))
        self.add_control_point(rect.topRight().x(), rect.topRight().y(), lambda p: self.update_rectangle(rect_item, "topRight", p))
        self.add_control_point(rect.bottomLeft().x(), rect.bottomLeft().y(), lambda p: self.update_rectangle(rect_item, "bottomLeft", p))
        self.add_control_point(rect.bottomRight().x(), rect.bottomRight().y(), lambda p: self.update_rectangle(rect_item, "bottomRight", p))

    # ---------- Update Callbacks for Shapes ---------- #

    def update_line(self, line_item, control_point, which_end):
        line = line_item.line()
        if which_end == "start":
            line.setP1(control_point.pos())
        elif which_end == "end":
            line.setP2(control_point.pos())
        line_item.setLine(line)

    def update_circle(self, circle_item, center, which_side, control_point):
        # Skip advanced updates for the circle for now (center position can be recalculated here)
        print("[DEBUG] Updated circle part:", which_side)

    def update_rectangle(self, rect_item, corner, control_point):
        rect = rect_item.rect()
        # Update rectangle corners dynamically
        if corner == "topLeft":
            rect.setTopLeft(control_point.pos())
        elif corner == "topRight":
            rect.setTopRight(control_point.pos())
        elif corner == "bottomLeft":
            rect.setBottomLeft(control_point.pos())
        elif corner == "bottomRight":
            rect.setBottomRight(control_point.pos())
        rect_item.setRect(rect)

    # ---------- Utility Methods ---------- #

    def add_control_point(self, x, y, callback):
        """Add a draggable control square."""
        point = ResizeablePoint(x, y, 8, callback)
        self.scene.addItem(point)

    def toggle_snap_to_grid(self):
        self.snap_to_grid_enabled = not self.snap_to_grid_enabled
        print(f"[INFO] Snap to Grid {'Enabled' if self.snap_to_grid_enabled else 'Disabled'}")

    def snap_to_grid(self, value):
        return round(value / self.grid_size) * self.grid_size

    def start_line_drawing(self):
        self.current_shape = "line"

    def start_circle_drawing(self):
        self.current_shape = "circle"

    def start_rectangle_drawing(self):
        self.current_shape = "rectangle"


# ---------- Main Application ---------- #
if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_window = CADCAMWindow()
    main_window.show()
    sys.exit(app.exec_())