package main

import (
	"fmt"
	"sync"
	"time"
)

// section: queue
// Queue is a thread-safe queue using sync.Cond
type Queue struct {
	mu    sync.Mutex
	cond  *sync.Cond
	items []string
}

// NewQueue creates a new queue
func NewQueue() *Queue {
	q := &Queue{
		items: make([]string, 0),
	}
	q.cond = sync.NewCond(&q.mu)
	return q
}

// Enqueue adds an item to the queue and signals waiting consumers
func (q *Queue) Enqueue(item string) {
	q.mu.Lock()
	defer q.mu.Unlock()

	q.items = append(q.items, item)
	fmt.Printf("Enqueued: %s (queue size: %d)\n", item, len(q.items))
	q.cond.Signal() // Wake up one waiting consumer
}

// Dequeue removes and returns an item from the queue
// Blocks if the queue is empty until an item is available
func (q *Queue) Dequeue() string {
	q.mu.Lock()
	defer q.mu.Unlock()

	// Wait while queue is empty
	for len(q.items) == 0 {
		fmt.Println("Queue empty, waiting for items...")
		q.cond.Wait()
	}

	// Get the first item
	item := q.items[0]
	q.items = q.items[1:]
	fmt.Printf("Dequeued: %s (queue size: %d)\n", item, len(q.items))
	return item
}

// section: queue

// section: main
func main() {
	q := NewQueue()
	var wg sync.WaitGroup

	// Start consumers first (they will wait)
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 3; j++ {
				item := q.Dequeue()
				fmt.Printf("Consumer %d processed: %s\n", id, item)
				time.Sleep(50 * time.Millisecond)
			}
		}(i)
	}

	// Give consumers time to start waiting
	time.Sleep(100 * time.Millisecond)

	// Start producer
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 1; i <= 9; i++ {
			item := fmt.Sprintf("item-%d", i)
			q.Enqueue(item)
			time.Sleep(100 * time.Millisecond)
		}
	}()

	wg.Wait()
	fmt.Println("\nAll producers and consumers completed")
}

// section: main

// section: output
// Output (order may vary):
// Queue empty, waiting for items...
// Queue empty, waiting for items...
// Queue empty, waiting for items...
// Enqueued: item-1 (queue size: 1)
// Dequeued: item-1 (queue size: 0)
// Consumer 1 processed: item-1
// Enqueued: item-2 (queue size: 1)
// Dequeued: item-2 (queue size: 0)
// Consumer 2 processed: item-2
// Enqueued: item-3 (queue size: 1)
// Dequeued: item-3 (queue size: 0)
// Consumer 3 processed: item-3
// ...
// section: output
