package appearances import ( "fmt" "eq2emu/internal/common" "eq2emu/internal/database" ) // MasterList manages a collection of appearances using the generic MasterList base type MasterList struct { *common.MasterList[int32, *Appearance] } // NewMasterList creates a new appearance master list func NewMasterList() *MasterList { return &MasterList{ MasterList: common.NewMasterList[int32, *Appearance](), } } // AddAppearance adds an appearance to the master list func (ml *MasterList) AddAppearance(appearance *Appearance) bool { return ml.Add(appearance) } // GetAppearance retrieves an appearance by ID func (ml *MasterList) GetAppearance(id int32) *Appearance { return ml.Get(id) } // GetAppearanceSafe retrieves an appearance by ID with existence check func (ml *MasterList) GetAppearanceSafe(id int32) (*Appearance, bool) { return ml.GetSafe(id) } // HasAppearance checks if an appearance exists by ID func (ml *MasterList) HasAppearance(id int32) bool { return ml.Exists(id) } // RemoveAppearance removes an appearance by ID func (ml *MasterList) RemoveAppearance(id int32) bool { return ml.Remove(id) } // GetAllAppearances returns all appearances as a map func (ml *MasterList) GetAllAppearances() map[int32]*Appearance { return ml.GetAll() } // GetAllAppearancesList returns all appearances as a slice func (ml *MasterList) GetAllAppearancesList() []*Appearance { return ml.GetAllSlice() } // GetAppearanceCount returns the number of appearances func (ml *MasterList) GetAppearanceCount() int { return ml.Size() } // ClearAppearances removes all appearances from the list func (ml *MasterList) ClearAppearances() { ml.Clear() } // FindAppearancesByName finds appearances containing the given name substring func (ml *MasterList) FindAppearancesByName(nameSubstring string) []*Appearance { return ml.Filter(func(appearance *Appearance) bool { return contains(appearance.GetName(), nameSubstring) }) } // FindAppearancesByMinClient finds appearances with specific minimum client version func (ml *MasterList) FindAppearancesByMinClient(minClient int16) []*Appearance { return ml.Filter(func(appearance *Appearance) bool { return appearance.GetMinClientVersion() == minClient }) } // GetCompatibleAppearances returns appearances compatible with the given client version func (ml *MasterList) GetCompatibleAppearances(clientVersion int16) []*Appearance { return ml.Filter(func(appearance *Appearance) bool { return appearance.IsCompatibleWithClient(clientVersion) }) } // GetAppearancesByIDRange returns appearances within the given ID range (inclusive) func (ml *MasterList) GetAppearancesByIDRange(minID, maxID int32) []*Appearance { return ml.Filter(func(appearance *Appearance) bool { id := appearance.GetID() return id >= minID && id <= maxID }) } // ValidateAppearances checks all appearances for consistency func (ml *MasterList) ValidateAppearances() []string { var issues []string ml.ForEach(func(id int32, appearance *Appearance) { if appearance == nil { issues = append(issues, fmt.Sprintf("Appearance ID %d is nil", id)) return } if appearance.GetID() != id { issues = append(issues, fmt.Sprintf("Appearance ID mismatch: map key %d != appearance ID %d", id, appearance.GetID())) } if len(appearance.GetName()) == 0 { issues = append(issues, fmt.Sprintf("Appearance ID %d has empty name", id)) } if appearance.GetMinClientVersion() < 0 { issues = append(issues, fmt.Sprintf("Appearance ID %d has negative min client version: %d", id, appearance.GetMinClientVersion())) } }) return issues } // IsValid returns true if all appearances are valid func (ml *MasterList) IsValid() bool { issues := ml.ValidateAppearances() return len(issues) == 0 } // GetStatistics returns statistics about the appearance collection func (ml *MasterList) GetStatistics() map[string]any { stats := make(map[string]any) stats["total_appearances"] = ml.Size() if ml.IsEmpty() { return stats } // Count by minimum client version versionCounts := make(map[int16]int) var minID, maxID int32 first := true ml.ForEach(func(id int32, appearance *Appearance) { versionCounts[appearance.GetMinClientVersion()]++ if first { minID = id maxID = id first = false } else { if id < minID { minID = id } if id > maxID { maxID = id } } }) stats["appearances_by_min_client"] = versionCounts stats["min_id"] = minID stats["max_id"] = maxID stats["id_range"] = maxID - minID return stats } // LoadAllAppearances loads all appearances from the database into the master list func (ml *MasterList) LoadAllAppearances(db *database.Database) error { if db == nil { return fmt.Errorf("database connection is nil") } // Clear existing appearances ml.Clear() query := `SELECT appearance_id, name, min_client_version FROM appearances ORDER BY appearance_id` rows, err := db.Query(query) if err != nil { return fmt.Errorf("failed to query appearances: %w", err) } defer rows.Close() count := 0 for rows.Next() { appearance := &Appearance{ db: db, isNew: false, } err := rows.Scan(&appearance.ID, &appearance.Name, &appearance.MinClient) if err != nil { return fmt.Errorf("failed to scan appearance: %w", err) } if !ml.AddAppearance(appearance) { return fmt.Errorf("failed to add appearance %d to master list", appearance.ID) } count++ } if err := rows.Err(); err != nil { return fmt.Errorf("error iterating appearance rows: %w", err) } return nil } // LoadAllAppearancesFromDatabase is a convenience function that creates a master list and loads all appearances func LoadAllAppearancesFromDatabase(db *database.Database) (*MasterList, error) { masterList := NewMasterList() err := masterList.LoadAllAppearances(db) if err != nil { return nil, err } return masterList, nil } // contains checks if a string contains a substring (case-sensitive) func contains(str, substr string) bool { if len(substr) == 0 { return true } if len(str) < len(substr) { return false } for i := 0; i <= len(str)-len(substr); i++ { if str[i:i+len(substr)] == substr { return true } } return false }