import pygame, sys, math












#! Note: Broken



# ---------------------------
# Configuration & Constants
# ---------------------------
CELL_SIZE = 50
GRID_WIDTH = 16
GRID_HEIGHT = 12
SCREEN_WIDTH = CELL_SIZE * GRID_WIDTH
SCREEN_HEIGHT = CELL_SIZE * GRID_HEIGHT
FPS = 60

MIN_DISTANCE = 20  # minimum spacing (in pixels) between items on a belt

# Direction vectors for movement
DIRECTIONS = {
    "right": (1, 0),
    "left": (-1, 0),
    "up": (0, -1),
    "down": (0, 1),
}

# ---------------------------
# Simulation Object Classes
# ---------------------------
class Item:
    def __init__(self, x, y, dx, dy, speed=60):
        # Position in pixels; starts at the center of a grid cell.
        self.x = x
        self.y = y
        self.dx = dx  # normalized direction vector (e.g., from DIRECTIONS)
        self.dy = dy
        self.speed = speed

    def draw(self, surface):
        # Draw the item as a small circle (gold color)
        pygame.draw.circle(surface, (255, 215, 0), (int(self.x), int(self.y)), 10)


class Conveyor:
    def __init__(self, grid_x, grid_y, direction):
        self.grid_x = grid_x
        self.grid_y = grid_y
        self.direction = direction  # e.g. "right", "down", etc.
        self.rect = pygame.Rect(grid_x * CELL_SIZE, grid_y * CELL_SIZE, CELL_SIZE, CELL_SIZE)

    def update(self, dt):
        # Conveyors in this demo do not update objects directly.
        pass

    def draw(self, surface, debug=False):
        # Draw the cell as a light gray block
        pygame.draw.rect(surface, (169, 169, 169), self.rect)
        # Draw an arrow indicating the conveyor's direction
        center = self.rect.center
        offset = 15
        dx, dy = DIRECTIONS[self.direction]
        end_point = (center[0] + dx * offset, center[1] + dy * offset)
        pygame.draw.line(surface, (0, 0, 0), center, end_point, 3)
        if debug:
            font = pygame.font.SysFont("monospace", 12)
            text = font.render(f"{self.grid_x},{self.grid_y}", True, (0, 0, 0))
            surface.blit(text, (self.rect.x + 2, self.rect.y + 2))


class Arm:
    def __init__(self, grid_x, grid_y, pick_pos, place_pos):
        self.grid_x = grid_x
        self.grid_y = grid_y
        self.pick_pos = pick_pos   # grid cell from which to pick an item
        self.place_pos = place_pos # grid cell where the item is dropped
        self.state = "idle"        # states: "idle", "picking", "placing"
        self.holding_item = None
        self.timer = 0

    def update(self, dt, items):
        # When idle, scan the pick cell for an item to pick up.
        if self.state == "idle":
            for item in items:
                cell_x = int(item.x // CELL_SIZE)
                cell_y = int(item.y // CELL_SIZE)
                if (cell_x, cell_y) == self.pick_pos:
                    self.holding_item = item
                    items.remove(item)
                    self.state = "picking"
                    self.timer = 0.5  # time to pick up
                    break
        elif self.state == "picking":
            self.timer -= dt
            if self.timer <= 0:
                self.state = "placing"
                self.timer = 0.5  # time to place the item
        elif self.state == "placing":
            self.timer -= dt
            if self.timer <= 0:
                # Drop the item at the center of the place cell, with a new direction.
                if self.holding_item:
                    self.holding_item.x = self.place_pos[0] * CELL_SIZE + CELL_SIZE / 2
                    self.holding_item.y = self.place_pos[1] * CELL_SIZE + CELL_SIZE / 2
                    # For this layout, the vertical belt moves items downward.
                    self.holding_item.dx, self.holding_item.dy = DIRECTIONS["down"]
                    items.append(self.holding_item)
                    self.holding_item = None
                self.state = "idle"

    def draw(self, surface, debug=False):
        # Base center of the arm (drawn as a circle)
        base_center = (self.grid_x * CELL_SIZE + CELL_SIZE // 2, self.grid_y * CELL_SIZE + CELL_SIZE // 2)
        pygame.draw.circle(surface, (135, 206, 235), base_center, CELL_SIZE // 3)
        
        # Compute centers for pick and place zones
        pick_center = (self.pick_pos[0] * CELL_SIZE + CELL_SIZE // 2, self.pick_pos[1] * CELL_SIZE + CELL_SIZE // 2)
        place_center = (self.place_pos[0] * CELL_SIZE + CELL_SIZE // 2, self.place_pos[1] * CELL_SIZE + CELL_SIZE // 2)
        
        # Draw the arm's "arm" as a line indicating its action.
        if self.state == "picking":
            pygame.draw.line(surface, (255, 0, 0), base_center, pick_center, 4)
        elif self.state == "placing":
            pygame.draw.line(surface, (0, 255, 0), base_center, place_center, 4)
        else:
            # Idle: draw faint guide lines toward both pick and place positions.
            pygame.draw.line(surface, (100, 100, 100), base_center, pick_center, 2)
            pygame.draw.line(surface, (100, 100, 100), base_center, place_center, 2)
        
        if debug:
            font = pygame.font.SysFont("monospace", 12)
            text = font.render(f"Arm: {self.state}", True, (0, 0, 0))
            surface.blit(text, (self.grid_x * CELL_SIZE, self.grid_y * CELL_SIZE))
            # Draw outlines for the pick (green) and place (red) zones.
            pick_rect = pygame.Rect(self.pick_pos[0] * CELL_SIZE, self.pick_pos[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE)
            place_rect = pygame.Rect(self.place_pos[0] * CELL_SIZE, self.place_pos[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE)
            pygame.draw.rect(surface, (0, 255, 0), pick_rect, 2)
            pygame.draw.rect(surface, (255, 0, 0), place_rect, 2)


class Factory:
    def __init__(self, grid_x, grid_y, output_direction="right"):
        self.grid_x = grid_x
        self.grid_y = grid_y
        self.output_direction = output_direction
        self.rect = pygame.Rect(grid_x * CELL_SIZE, grid_y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
        self.production_timer = 1.0  # seconds until next item production
        self.production_interval = 0.5

    def update(self, dt, items):
        self.production_timer -= dt
        if self.production_timer <= 0:
            # Create a new item at the center of the factory cell.
            center_x = self.grid_x * CELL_SIZE + CELL_SIZE / 2
            center_y = self.grid_y * CELL_SIZE + CELL_SIZE / 2
            dx, dy = DIRECTIONS[self.output_direction]
            new_item = Item(center_x, center_y, dx, dy, speed=60)
            items.append(new_item)
            self.production_timer = self.production_interval

    def draw(self, surface, debug=False):
        pygame.draw.rect(surface, (192, 192, 192), self.rect)
        if debug:
            font = pygame.font.SysFont("monospace", 12)
            text = font.render(f"{self.production_timer:.1f}", True, (0, 0, 0))
            surface.blit(text, (self.rect.x + 2, self.rect.y + 2))


# ---------------------------
# Helper: Draw Grid Lines
# ---------------------------
def draw_grid(surface):
    for x in range(0, SCREEN_WIDTH, CELL_SIZE):
        pygame.draw.line(surface, (200, 200, 200), (x, 0), (x, SCREEN_HEIGHT))
    for y in range(0, SCREEN_HEIGHT, CELL_SIZE):
        pygame.draw.line(surface, (200, 200, 200), (0, y), (SCREEN_WIDTH, y))


# ---------------------------
# Update Items with Collision (Queueing) Behavior
# ---------------------------
def update_items(items, dt):
    for item in items:
        # Compute the proposed new position.
        new_x = item.x + item.dx * item.speed * dt
        new_y = item.y + item.dy * item.speed * dt
        can_move = True
        # Check all other items to see if one is ahead and too close.
        for other in items:
            if other is item:
                continue
            # Calculate vector from this item to the other.
            rel_x = other.x - item.x
            rel_y = other.y - item.y
            # Dot product to see if other is ahead along the item's movement direction.
            if (rel_x * item.dx + rel_y * item.dy) > 0:
                dist = math.hypot(rel_x, rel_y)
                if dist < MIN_DISTANCE:
                    can_move = False
                    break
        if can_move:
            item.x = new_x
            item.y = new_y


# ---------------------------
# Main Simulation Loop
# ---------------------------
def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Factorio-like Simulation with Queuing & Mechanical Arm")
    clock = pygame.time.Clock()
    debug_mode = True

    # Lists to hold simulation objects
    conveyors = []
    items = []
    arms = []
    factories = []

    # -- Layout Setup --
    # Horizontal conveyor belt from (2,5) to (10,5)
    for i in range(2, 11):
        conveyors.append(Conveyor(i, 5, "right"))
    # Vertical conveyor belt from (10,5) to (10,9)
    for j in range(5, 10):
        conveyors.append(Conveyor(10, j, "down"))
    # Factory at (1,5) outputs items to the right (onto the horizontal belt)
    factories.append(Factory(1, 5, output_direction="right"))
    # Arm at (10,4): picks from cell (10,5) and places to cell (10,6)
    arms.append(Arm(10, 4, (10, 5), (10, 6)))

    running = True
    while running:
        dt = clock.tick(FPS) / 1000.0  # Delta time in seconds

        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            # Toggle debug mode with the D key.
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_d:
                    debug_mode = not debug_mode

        # Update simulation objects.
        for factory in factories:
            factory.update(dt, items)
        for arm in arms:
            arm.update(dt, items)
        # Update items with queuing/collision behavior.
        update_items(items, dt)

        # ---------------------------
        # Drawing Phase
        # ---------------------------
        screen.fill((50, 50, 50))  # Clear the screen with a dark background.

        # Draw grid lines if debug mode is on.
        if debug_mode:
            draw_grid(screen)

        # Draw conveyors.
        for conveyor in conveyors:
            conveyor.draw(screen, debug_mode)
        # Draw factories.
        for factory in factories:
            factory.draw(screen, debug_mode)
        # Draw arms.
        for arm in arms:
            arm.draw(screen, debug_mode)
        # Draw items.
        for item in items:
            item.draw(screen)

        # Global debug info.
        if debug_mode:
            font = pygame.font.SysFont("monospace", 18)
            debug_text = font.render("DEBUG MODE ON - Press D to toggle", True, (255, 255, 255))
            screen.blit(debug_text, (10, SCREEN_HEIGHT - 30))

        pygame.display.flip()

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()
