package collections import ( "fmt" "eq2emu/internal/common" "eq2emu/internal/database" ) // MasterList manages a collection of collections using the generic MasterList base type MasterList struct { *common.MasterList[int32, *Collection] } // NewMasterList creates a new collection master list func NewMasterList() *MasterList { return &MasterList{ MasterList: common.NewMasterList[int32, *Collection](), } } // AddCollection adds a collection to the master list func (ml *MasterList) AddCollection(collection *Collection) bool { return ml.Add(collection) } // GetCollection retrieves a collection by ID func (ml *MasterList) GetCollection(id int32) *Collection { return ml.Get(id) } // GetCollectionSafe retrieves a collection by ID with existence check func (ml *MasterList) GetCollectionSafe(id int32) (*Collection, bool) { return ml.GetSafe(id) } // HasCollection checks if a collection exists by ID func (ml *MasterList) HasCollection(id int32) bool { return ml.Exists(id) } // RemoveCollection removes a collection by ID func (ml *MasterList) RemoveCollection(id int32) bool { return ml.Remove(id) } // GetAllCollections returns all collections as a map func (ml *MasterList) GetAllCollections() map[int32]*Collection { return ml.GetAll() } // GetAllCollectionsList returns all collections as a slice func (ml *MasterList) GetAllCollectionsList() []*Collection { return ml.GetAllSlice() } // GetCollectionCount returns the number of collections func (ml *MasterList) GetCollectionCount() int { return ml.Size() } // ClearCollections removes all collections from the list func (ml *MasterList) ClearCollections() { ml.Clear() } // NeedsItem checks if any collection needs the specified item func (ml *MasterList) NeedsItem(itemID int32) bool { for _, collection := range ml.GetAll() { if collection.NeedsItem(itemID) { return true } } return false } // FindCollectionsByCategory finds collections in a specific category func (ml *MasterList) FindCollectionsByCategory(category string) []*Collection { return ml.Filter(func(collection *Collection) bool { return collection.GetCategory() == category }) } // FindCollectionsByLevel finds collections for a specific level range func (ml *MasterList) FindCollectionsByLevel(minLevel, maxLevel int8) []*Collection { return ml.Filter(func(collection *Collection) bool { level := collection.GetLevel() return level >= minLevel && level <= maxLevel }) } // GetCollectionsNeedingItem returns all collections that need a specific item func (ml *MasterList) GetCollectionsNeedingItem(itemID int32) []*Collection { return ml.Filter(func(collection *Collection) bool { return collection.NeedsItem(itemID) }) } // GetCategories returns all unique collection categories func (ml *MasterList) GetCategories() []string { categoryMap := make(map[string]bool) ml.ForEach(func(id int32, collection *Collection) { categoryMap[collection.GetCategory()] = true }) categories := make([]string, 0, len(categoryMap)) for category := range categoryMap { categories = append(categories, category) } return categories } // ValidateCollections checks all collections for consistency func (ml *MasterList) ValidateCollections() []string { var issues []string ml.ForEach(func(id int32, collection *Collection) { if collection == nil { issues = append(issues, fmt.Sprintf("Collection ID %d is nil", id)) return } if collection.GetID() != id { issues = append(issues, fmt.Sprintf("Collection ID mismatch: map key %d != collection ID %d", id, collection.GetID())) } if len(collection.GetName()) == 0 { issues = append(issues, fmt.Sprintf("Collection ID %d has empty name", id)) } if len(collection.GetCategory()) == 0 { issues = append(issues, fmt.Sprintf("Collection ID %d has empty category", id)) } if collection.GetLevel() < 0 { issues = append(issues, fmt.Sprintf("Collection ID %d has negative level: %d", id, collection.GetLevel())) } if len(collection.CollectionItems) == 0 { issues = append(issues, fmt.Sprintf("Collection ID %d has no collection items", id)) } // Check for duplicate item indices indexMap := make(map[int8]bool) for _, item := range collection.CollectionItems { if indexMap[item.Index] { issues = append(issues, fmt.Sprintf("Collection ID %d has duplicate item index: %d", id, item.Index)) } indexMap[item.Index] = true } }) return issues } // IsValid returns true if all collections are valid func (ml *MasterList) IsValid() bool { issues := ml.ValidateCollections() return len(issues) == 0 } // GetStatistics returns statistics about the collection collection func (ml *MasterList) GetStatistics() map[string]any { stats := make(map[string]any) stats["total_collections"] = ml.Size() if ml.IsEmpty() { return stats } // Count by category categoryCounts := make(map[string]int) var totalItems, totalRewards int var minLevel, maxLevel int8 = 127, 0 var minID, maxID int32 first := true ml.ForEach(func(id int32, collection *Collection) { categoryCounts[collection.GetCategory()]++ totalItems += len(collection.CollectionItems) totalRewards += len(collection.RewardItems) + len(collection.SelectableRewardItems) level := collection.GetLevel() if level < minLevel { minLevel = level } if level > maxLevel { maxLevel = level } if first { minID = id maxID = id first = false } else { if id < minID { minID = id } if id > maxID { maxID = id } } }) stats["collections_by_category"] = categoryCounts stats["total_collection_items"] = totalItems stats["total_rewards"] = totalRewards stats["min_level"] = minLevel stats["max_level"] = maxLevel stats["min_id"] = minID stats["max_id"] = maxID stats["id_range"] = maxID - minID stats["average_items_per_collection"] = float64(totalItems) / float64(ml.Size()) return stats } // LoadAllCollections loads all collections from the database into the master list func (ml *MasterList) LoadAllCollections(db *database.Database) error { if db == nil { return fmt.Errorf("database connection is nil") } // Clear existing collections ml.Clear() query := `SELECT id, collection_name, collection_category, level FROM collections ORDER BY id` rows, err := db.Query(query) if err != nil { return fmt.Errorf("failed to query collections: %w", err) } defer rows.Close() count := 0 for rows.Next() { collection := &Collection{ db: db, isNew: false, CollectionItems: make([]CollectionItem, 0), RewardItems: make([]CollectionRewardItem, 0), SelectableRewardItems: make([]CollectionRewardItem, 0), } err := rows.Scan(&collection.ID, &collection.Name, &collection.Category, &collection.Level) if err != nil { return fmt.Errorf("failed to scan collection: %w", err) } // Load collection items for this collection itemQuery := `SELECT item_id, item_index, found FROM collection_items WHERE collection_id = ? ORDER BY item_index` itemRows, err := db.Query(itemQuery, collection.ID) if err != nil { return fmt.Errorf("failed to load collection items for collection %d: %w", collection.ID, err) } for itemRows.Next() { var item CollectionItem if err := itemRows.Scan(&item.ItemID, &item.Index, &item.Found); err != nil { itemRows.Close() return fmt.Errorf("failed to scan collection item: %w", err) } collection.CollectionItems = append(collection.CollectionItems, item) } itemRows.Close() // Load rewards for this collection rewardQuery := `SELECT reward_type, reward_value, reward_quantity FROM collection_rewards WHERE collection_id = ?` rewardRows, err := db.Query(rewardQuery, collection.ID) if err != nil { return fmt.Errorf("failed to load rewards for collection %d: %w", collection.ID, err) } for rewardRows.Next() { var rewardType, rewardValue string var quantity int8 if err := rewardRows.Scan(&rewardType, &rewardValue, &quantity); err != nil { rewardRows.Close() return fmt.Errorf("failed to scan collection reward: %w", err) } switch rewardType { case "coin": fmt.Sscanf(rewardValue, "%d", &collection.RewardCoin) case "xp": fmt.Sscanf(rewardValue, "%d", &collection.RewardXP) case "item": var itemID int32 fmt.Sscanf(rewardValue, "%d", &itemID) collection.RewardItems = append(collection.RewardItems, CollectionRewardItem{ ItemID: itemID, Quantity: quantity, }) case "selectable_item": var itemID int32 fmt.Sscanf(rewardValue, "%d", &itemID) collection.SelectableRewardItems = append(collection.SelectableRewardItems, CollectionRewardItem{ ItemID: itemID, Quantity: quantity, }) } } rewardRows.Close() if !ml.AddCollection(collection) { return fmt.Errorf("failed to add collection %d to master list", collection.ID) } count++ } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating collection rows: %w", err) } return nil } // LoadAllCollectionsFromDatabase is a convenience function that creates a master list and loads all collections func LoadAllCollectionsFromDatabase(db *database.Database) (*MasterList, error) { masterList := NewMasterList() err := masterList.LoadAllCollections(db) if err != nil { return nil, err } return masterList, nil }