package collections import ( "fmt" "sync" "time" "eq2emu/internal/database" "eq2emu/internal/packets" ) // Manager provides centralized management for the collections system // following the SIMPLIFICATION.md methodology while maintaining C++ API compatibility type Manager struct { // Core components masterList *MasterList db *database.Database // Thread safety mutex sync.RWMutex // Performance monitoring stats struct { // Collection operations CollectionsLoaded int64 CollectionsSaved int64 CollectionItemsUpdated int64 // Player operations PlayersLoaded int64 PlayersUpdated int64 ItemChecks int64 // Packet operations PacketsSent int64 PacketErrors int64 PacketUpdates int64 // Cache performance CacheHits int64 CacheMisses int64 } // Configuration config struct { EnableCaching bool EnableStatistics bool EnableValidation bool MaxPlayersToTrack int AutoSaveInterval time.Duration PacketBuilderTimeout time.Duration } } // NewManager creates a new collections manager with default configuration func NewManager(db *database.Database) *Manager { m := &Manager{ masterList: NewMasterList(), db: db, } // Set default configuration m.config.EnableCaching = true m.config.EnableStatistics = true m.config.EnableValidation = true m.config.MaxPlayersToTrack = 1000 m.config.AutoSaveInterval = 5 * time.Minute m.config.PacketBuilderTimeout = 30 * time.Second return m } // Initialize loads all collections from database and prepares the manager func (m *Manager) Initialize() error { if m.db == nil { return fmt.Errorf("database connection is required") } err := m.masterList.LoadAllCollections(m.db) if err != nil { return fmt.Errorf("failed to load collections: %v", err) } if m.config.EnableStatistics { m.stats.CollectionsLoaded = int64(m.masterList.Size()) } return nil } // === C++ API Compatibility Methods === // AddCollection adds a collection to the master list (C++ API compatibility) func (m *Manager) AddCollection(collection *Collection) bool { if collection == nil { return false } m.mutex.Lock() defer m.mutex.Unlock() if m.config.EnableStatistics { m.stats.CollectionsLoaded++ } return m.masterList.AddCollection(collection) } // GetCollection retrieves a collection by ID (C++ API compatibility) func (m *Manager) GetCollection(id int32) *Collection { m.mutex.RLock() defer m.mutex.RUnlock() if m.config.EnableStatistics { if collection := m.masterList.GetCollection(id); collection != nil { m.stats.CacheHits++ return collection } m.stats.CacheMisses++ return nil } return m.masterList.GetCollection(id) } // ClearCollections removes all collections (C++ API compatibility) func (m *Manager) ClearCollections() { m.mutex.Lock() defer m.mutex.Unlock() m.masterList.ClearCollections() } // Size returns the number of collections (C++ API compatibility) func (m *Manager) Size() int32 { m.mutex.RLock() defer m.mutex.RUnlock() return int32(m.masterList.Size()) } // NeedsItem checks if any collection needs the specified item (C++ API compatibility) func (m *Manager) NeedsItem(itemID int32) bool { m.mutex.RLock() defer m.mutex.RUnlock() if m.config.EnableStatistics { m.stats.ItemChecks++ } return m.masterList.NeedsItem(itemID) } // GetMasterList returns the master collection list (C++ API compatibility) func (m *Manager) GetMasterList() *MasterList { return m.masterList } // === Enhanced Go Methods === // GetCollectionsSafe retrieves all collections with thread safety func (m *Manager) GetCollectionsSafe() map[int32]*Collection { m.mutex.RLock() defer m.mutex.RUnlock() return m.masterList.GetAllCollections() } // UpdateCollection updates an existing collection with validation func (m *Manager) UpdateCollection(collection *Collection) error { if collection == nil { return fmt.Errorf("collection cannot be nil") } m.mutex.Lock() defer m.mutex.Unlock() if m.config.EnableValidation { if collection.GetID() <= 0 { return fmt.Errorf("invalid collection ID: %d", collection.GetID()) } if len(collection.GetName()) == 0 { return fmt.Errorf("collection name cannot be empty") } } err := m.masterList.UpdateCollection(collection) if err != nil { return err } if m.config.EnableStatistics { m.stats.CollectionsLoaded++ } return nil } // === Packet Building Methods === // SendCollectionUpdate builds and returns collection update packet data (C++ API compatibility) func (m *Manager) SendCollectionUpdate(characterID int32, clientVersion uint32, playerCollections map[int32]*Collection) ([]byte, error) { packet, exists := packets.GetPacket("WS_CollectionUpdate") if !exists { if m.config.EnableStatistics { m.stats.PacketErrors++ } return nil, fmt.Errorf("failed to get WS_CollectionUpdate packet structure: packet not found") } // Build collection array collectionArray := make([]map[string]interface{}, 0, len(playerCollections)) for _, collection := range playerCollections { // Build collection items array itemsArray := make([]map[string]interface{}, 0, len(collection.CollectionItems)) for _, item := range collection.CollectionItems { itemsArray = append(itemsArray, map[string]interface{}{ "item_flag": item.Found, }) } collectionData := map[string]interface{}{ "collection_name": collection.GetName(), "collection_category": collection.GetCategory(), "completed": collection.Completed, "collection_id": collection.GetID(), "level": collection.GetLevel(), "ready_to_turn_in": collection.GetIsReadyToTurnIn(), "num_items": len(collection.CollectionItems), "items_array": itemsArray, } collectionArray = append(collectionArray, collectionData) } data := map[string]interface{}{ "num_collections": len(playerCollections), "collections_array": collectionArray, "new_collection_flag": 1, } builder := packets.NewPacketBuilder(packet, clientVersion, 0) packetData, err := builder.Build(data) if err != nil { if m.config.EnableStatistics { m.stats.PacketErrors++ } return nil, fmt.Errorf("failed to build collection update packet: %v", err) } if m.config.EnableStatistics { m.stats.PacketsSent++ m.stats.PacketUpdates++ } return packetData, nil } // SendCollectionFilter builds and returns collection filter packet data (C++ API compatibility) func (m *Manager) SendCollectionFilter(characterID int32, clientVersion uint32, itemID int32, collectionsForItem []*Collection) ([]byte, error) { packet, exists := packets.GetPacket("WS_CollectionFilter") if !exists { if m.config.EnableStatistics { m.stats.PacketErrors++ } return nil, fmt.Errorf("failed to get WS_CollectionFilter packet structure: packet not found") } // Build filter array filterArray := make([]map[string]interface{}, 0, len(collectionsForItem)) for _, collection := range collectionsForItem { collectionItem := collection.GetCollectionItemByItemID(itemID) if collectionItem != nil { filterData := map[string]interface{}{ "collection_id": collection.GetID(), "collection_item_num": collectionItem.Index, } filterArray = append(filterArray, filterData) } } data := map[string]interface{}{ "num_filters": len(filterArray), "filters_array": filterArray, "unknown3": m.Size(), // Player collection count } builder := packets.NewPacketBuilder(packet, clientVersion, 0) packetData, err := builder.Build(data) if err != nil { if m.config.EnableStatistics { m.stats.PacketErrors++ } return nil, fmt.Errorf("failed to build collection filter packet: %v", err) } if m.config.EnableStatistics { m.stats.PacketsSent++ } return packetData, nil } // SendCollectionItem builds and returns collection item packet data (C++ API compatibility) func (m *Manager) SendCollectionItem(characterID int32, clientVersion uint32, collectionID int32, itemID int32) ([]byte, error) { packet, exists := packets.GetPacket("WS_CollectionItem") if !exists { if m.config.EnableStatistics { m.stats.PacketErrors++ } return nil, fmt.Errorf("failed to get WS_CollectionItem packet structure: packet not found") } collection := m.GetCollection(collectionID) if collection == nil { return nil, fmt.Errorf("collection %d not found", collectionID) } collectionItem := collection.GetCollectionItemByItemID(itemID) if collectionItem == nil { return nil, fmt.Errorf("collection item %d not found in collection %d", itemID, collectionID) } data := map[string]interface{}{ "collection_id": collectionID, "collection_item_num": collectionItem.Index, "name": collection.GetName(), "description": collection.GetCategory(), "level": collection.GetLevel(), "max_coin": collection.RewardCoin, "min_coin": collection.RewardCoin, } builder := packets.NewPacketBuilder(packet, clientVersion, 0) packetData, err := builder.Build(data) if err != nil { if m.config.EnableStatistics { m.stats.PacketErrors++ } return nil, fmt.Errorf("failed to build collection item packet: %v", err) } if m.config.EnableStatistics { m.stats.PacketsSent++ } return packetData, nil } // === Player Collection Management === // CreatePlayerList creates a new player collection list func (m *Manager) CreatePlayerList(characterID int32) *PlayerList { return NewPlayerList(characterID, m.db) } // LoadPlayerCollections loads all collections for a player func (m *Manager) LoadPlayerCollections(characterID int32) (*PlayerList, error) { playerList := NewPlayerList(characterID, m.db) err := playerList.LoadPlayerCollections(m.masterList) if err != nil { return nil, fmt.Errorf("failed to load player collections: %v", err) } if m.config.EnableStatistics { m.stats.PlayersLoaded++ } return playerList, nil } // SavePlayerCollections saves all player collection progress func (m *Manager) SavePlayerCollections(playerList *PlayerList) error { if playerList == nil { return fmt.Errorf("player list cannot be nil") } err := playerList.SaveAllCollections() if err != nil { return fmt.Errorf("failed to save player collections: %v", err) } if m.config.EnableStatistics { m.stats.CollectionsSaved++ } return nil } // === Statistics and Monitoring === // GetStatistics returns comprehensive manager statistics func (m *Manager) GetStatistics() map[string]interface{} { m.mutex.RLock() defer m.mutex.RUnlock() stats := make(map[string]interface{}) // Manager stats stats["collections_loaded"] = m.stats.CollectionsLoaded stats["collections_saved"] = m.stats.CollectionsSaved stats["collection_items_updated"] = m.stats.CollectionItemsUpdated // Player stats stats["players_loaded"] = m.stats.PlayersLoaded stats["players_updated"] = m.stats.PlayersUpdated stats["item_checks"] = m.stats.ItemChecks // Packet stats stats["packets_sent"] = m.stats.PacketsSent stats["packet_errors"] = m.stats.PacketErrors stats["packet_updates"] = m.stats.PacketUpdates // Cache stats stats["cache_hits"] = m.stats.CacheHits stats["cache_misses"] = m.stats.CacheMisses if m.stats.CacheHits+m.stats.CacheMisses > 0 { hitRate := float64(m.stats.CacheHits) / float64(m.stats.CacheHits+m.stats.CacheMisses) * 100 stats["cache_hit_rate"] = fmt.Sprintf("%.2f%%", hitRate) } // Master list stats masterStats := m.masterList.GetStatistics() for key, value := range masterStats { stats["master_"+key] = value } // Configuration stats["config_caching_enabled"] = m.config.EnableCaching stats["config_statistics_enabled"] = m.config.EnableStatistics stats["config_validation_enabled"] = m.config.EnableValidation return stats } // ResetStatistics clears all performance counters func (m *Manager) ResetStatistics() { m.mutex.Lock() defer m.mutex.Unlock() m.stats = struct { CollectionsLoaded int64 CollectionsSaved int64 CollectionItemsUpdated int64 PlayersLoaded int64 PlayersUpdated int64 ItemChecks int64 PacketsSent int64 PacketErrors int64 PacketUpdates int64 CacheHits int64 CacheMisses int64 }{} } // === Configuration Management === // SetConfiguration updates manager configuration func (m *Manager) SetConfiguration(config map[string]interface{}) { m.mutex.Lock() defer m.mutex.Unlock() if val, ok := config["enable_caching"].(bool); ok { m.config.EnableCaching = val } if val, ok := config["enable_statistics"].(bool); ok { m.config.EnableStatistics = val } if val, ok := config["enable_validation"].(bool); ok { m.config.EnableValidation = val } if val, ok := config["max_players_to_track"].(int); ok { m.config.MaxPlayersToTrack = val } if val, ok := config["auto_save_interval"].(time.Duration); ok { m.config.AutoSaveInterval = val } } // GetConfiguration returns current manager configuration func (m *Manager) GetConfiguration() map[string]interface{} { m.mutex.RLock() defer m.mutex.RUnlock() return map[string]interface{}{ "enable_caching": m.config.EnableCaching, "enable_statistics": m.config.EnableStatistics, "enable_validation": m.config.EnableValidation, "max_players_to_track": m.config.MaxPlayersToTrack, "auto_save_interval": m.config.AutoSaveInterval, "packet_builder_timeout": m.config.PacketBuilderTimeout, } } // === Validation and Health Checks === // ValidateCollections performs comprehensive validation func (m *Manager) ValidateCollections() []string { m.mutex.RLock() defer m.mutex.RUnlock() return m.masterList.ValidateCollections() } // IsHealthy returns true if the manager is in a healthy state func (m *Manager) IsHealthy() bool { m.mutex.RLock() defer m.mutex.RUnlock() if m.db == nil { return false } if m.masterList == nil { return false } if m.config.EnableValidation { issues := m.masterList.ValidateCollections() return len(issues) == 0 } return true }