package appearances import ( "fmt" "sync" ) // Manager provides high-level management of the appearance system type Manager struct { appearances *Appearances database Database logger Logger mutex sync.RWMutex // Statistics totalLookups int64 successfulLookups int64 failedLookups int64 cacheHits int64 cacheMisses int64 } // NewManager creates a new appearance manager func NewManager(database Database, logger Logger) *Manager { return &Manager{ appearances: NewAppearances(), database: database, logger: logger, } } // Initialize loads appearances from database func (m *Manager) Initialize() error { if m.logger != nil { m.logger.LogInfo("Initializing appearance manager...") } if m.database == nil { if m.logger != nil { m.logger.LogWarning("No database provided, starting with empty appearance list") } return nil } appearances, err := m.database.LoadAllAppearances() if err != nil { return fmt.Errorf("failed to load appearances from database: %w", err) } for _, appearance := range appearances { if err := m.appearances.InsertAppearance(appearance); err != nil { if m.logger != nil { m.logger.LogError("Failed to insert appearance %d: %v", appearance.GetID(), err) } } } if m.logger != nil { m.logger.LogInfo("Loaded %d appearances from database", len(appearances)) } return nil } // GetAppearances returns the appearances collection func (m *Manager) GetAppearances() *Appearances { return m.appearances } // FindAppearanceByID finds an appearance by ID with statistics tracking func (m *Manager) FindAppearanceByID(id int32) *Appearance { m.mutex.Lock() m.totalLookups++ m.mutex.Unlock() appearance := m.appearances.FindAppearanceByID(id) m.mutex.Lock() if appearance != nil { m.successfulLookups++ m.cacheHits++ } else { m.failedLookups++ m.cacheMisses++ } m.mutex.Unlock() if m.logger != nil && appearance == nil { m.logger.LogDebug("Appearance lookup failed for ID: %d", id) } return appearance } // AddAppearance adds a new appearance func (m *Manager) AddAppearance(appearance *Appearance) error { if appearance == nil { return fmt.Errorf("appearance cannot be nil") } // Validate the appearance if len(appearance.GetName()) == 0 { return fmt.Errorf("appearance name cannot be empty") } if appearance.GetID() <= 0 { return fmt.Errorf("appearance ID must be positive") } // Check if appearance already exists if m.appearances.HasAppearance(appearance.GetID()) { return fmt.Errorf("appearance with ID %d already exists", appearance.GetID()) } // Add to collection if err := m.appearances.InsertAppearance(appearance); err != nil { return fmt.Errorf("failed to insert appearance: %w", err) } // Save to database if available if m.database != nil { if err := m.database.SaveAppearance(appearance); err != nil { // Remove from collection if database save failed m.appearances.RemoveAppearance(appearance.GetID()) return fmt.Errorf("failed to save appearance to database: %w", err) } } if m.logger != nil { m.logger.LogInfo("Added appearance %d: %s (min client: %d)", appearance.GetID(), appearance.GetName(), appearance.GetMinClientVersion()) } return nil } // UpdateAppearance updates an existing appearance func (m *Manager) UpdateAppearance(appearance *Appearance) error { if appearance == nil { return fmt.Errorf("appearance cannot be nil") } // Check if appearance exists if !m.appearances.HasAppearance(appearance.GetID()) { return fmt.Errorf("appearance with ID %d does not exist", appearance.GetID()) } // Update in collection if err := m.appearances.UpdateAppearance(appearance); err != nil { return fmt.Errorf("failed to update appearance: %w", err) } // Save to database if available if m.database != nil { if err := m.database.SaveAppearance(appearance); err != nil { return fmt.Errorf("failed to save appearance to database: %w", err) } } if m.logger != nil { m.logger.LogInfo("Updated appearance %d: %s", appearance.GetID(), appearance.GetName()) } return nil } // RemoveAppearance removes an appearance func (m *Manager) RemoveAppearance(id int32) error { // Check if appearance exists if !m.appearances.HasAppearance(id) { return fmt.Errorf("appearance with ID %d does not exist", id) } // Remove from database first if available if m.database != nil { if err := m.database.DeleteAppearance(id); err != nil { return fmt.Errorf("failed to delete appearance from database: %w", err) } } // Remove from collection if !m.appearances.RemoveAppearance(id) { return fmt.Errorf("failed to remove appearance from collection") } if m.logger != nil { m.logger.LogInfo("Removed appearance %d", id) } return nil } // GetCompatibleAppearances returns appearances compatible with client version func (m *Manager) GetCompatibleAppearances(clientVersion int16) []*Appearance { return m.appearances.GetCompatibleAppearances(clientVersion) } // SearchAppearancesByName searches for appearances by name substring func (m *Manager) SearchAppearancesByName(nameSubstring string) []*Appearance { return m.appearances.FindAppearancesByName(nameSubstring) } // GetStatistics returns appearance system statistics func (m *Manager) GetStatistics() map[string]interface{} { m.mutex.RLock() defer m.mutex.RUnlock() // Get basic appearance statistics stats := m.appearances.GetStatistics() // Add manager statistics stats["total_lookups"] = m.totalLookups stats["successful_lookups"] = m.successfulLookups stats["failed_lookups"] = m.failedLookups stats["cache_hits"] = m.cacheHits stats["cache_misses"] = m.cacheMisses if m.totalLookups > 0 { stats["success_rate"] = float64(m.successfulLookups) / float64(m.totalLookups) * 100 stats["cache_hit_rate"] = float64(m.cacheHits) / float64(m.totalLookups) * 100 } return stats } // ResetStatistics resets all statistics func (m *Manager) ResetStatistics() { m.mutex.Lock() defer m.mutex.Unlock() m.totalLookups = 0 m.successfulLookups = 0 m.failedLookups = 0 m.cacheHits = 0 m.cacheMisses = 0 } // ValidateAllAppearances validates all appearances in the system func (m *Manager) ValidateAllAppearances() []string { return m.appearances.ValidateAppearances() } // ReloadFromDatabase reloads all appearances from database func (m *Manager) ReloadFromDatabase() error { if m.database == nil { return fmt.Errorf("no database available") } // Clear current appearances m.appearances.ClearAppearances() // Reload from database return m.Initialize() } // GetAppearanceCount returns the total number of appearances func (m *Manager) GetAppearanceCount() int { return m.appearances.GetAppearanceCount() } // ProcessCommand handles appearance-related commands func (m *Manager) ProcessCommand(command string, args []string) (string, error) { switch command { case "stats": return m.handleStatsCommand(args) case "validate": return m.handleValidateCommand(args) case "search": return m.handleSearchCommand(args) case "info": return m.handleInfoCommand(args) case "reload": return m.handleReloadCommand(args) default: return "", fmt.Errorf("unknown appearance command: %s", command) } } // handleStatsCommand shows appearance system statistics func (m *Manager) handleStatsCommand(args []string) (string, error) { stats := m.GetStatistics() result := "Appearance System Statistics:\n" result += fmt.Sprintf("Total Appearances: %d\n", stats["total_appearances"]) result += fmt.Sprintf("Total Lookups: %d\n", stats["total_lookups"]) result += fmt.Sprintf("Successful Lookups: %d\n", stats["successful_lookups"]) result += fmt.Sprintf("Failed Lookups: %d\n", stats["failed_lookups"]) if successRate, exists := stats["success_rate"]; exists { result += fmt.Sprintf("Success Rate: %.1f%%\n", successRate) } if cacheHitRate, exists := stats["cache_hit_rate"]; exists { result += fmt.Sprintf("Cache Hit Rate: %.1f%%\n", cacheHitRate) } if minID, exists := stats["min_id"]; exists { result += fmt.Sprintf("ID Range: %d - %d\n", minID, stats["max_id"]) } return result, nil } // handleValidateCommand validates all appearances func (m *Manager) handleValidateCommand(_ []string) (string, error) { issues := m.ValidateAllAppearances() if len(issues) == 0 { return "All appearances are valid.", nil } result := fmt.Sprintf("Found %d issues with appearances:\n", len(issues)) for i, issue := range issues { if i >= 10 { // Limit output result += "... (and more)\n" break } result += fmt.Sprintf("%d. %s\n", i+1, issue) } return result, nil } // handleSearchCommand searches for appearances by name func (m *Manager) handleSearchCommand(args []string) (string, error) { if len(args) == 0 { return "", fmt.Errorf("search term required") } searchTerm := args[0] results := m.SearchAppearancesByName(searchTerm) if len(results) == 0 { return fmt.Sprintf("No appearances found matching '%s'.", searchTerm), nil } result := fmt.Sprintf("Found %d appearances matching '%s':\n", len(results), searchTerm) for i, appearance := range results { if i >= 20 { // Limit output result += "... (and more)\n" break } result += fmt.Sprintf(" %d: %s (min client: %d)\n", appearance.GetID(), appearance.GetName(), appearance.GetMinClientVersion()) } return result, nil } // handleInfoCommand shows information about a specific appearance func (m *Manager) handleInfoCommand(args []string) (string, error) { if len(args) == 0 { return "", fmt.Errorf("appearance ID required") } var appearanceID int32 if _, err := fmt.Sscanf(args[0], "%d", &appearanceID); err != nil { return "", fmt.Errorf("invalid appearance ID: %s", args[0]) } appearance := m.FindAppearanceByID(appearanceID) if appearance == nil { return fmt.Sprintf("Appearance %d not found.", appearanceID), nil } result := "Appearance Information:\n" result += fmt.Sprintf("ID: %d\n", appearance.GetID()) result += fmt.Sprintf("Name: %s\n", appearance.GetName()) result += fmt.Sprintf("Min Client Version: %d\n", appearance.GetMinClientVersion()) return result, nil } // handleReloadCommand reloads appearances from database func (m *Manager) handleReloadCommand(_ []string) (string, error) { if err := m.ReloadFromDatabase(); err != nil { return "", fmt.Errorf("failed to reload appearances: %w", err) } count := m.GetAppearanceCount() return fmt.Sprintf("Successfully reloaded %d appearances from database.", count), nil } // Shutdown gracefully shuts down the manager func (m *Manager) Shutdown() { if m.logger != nil { m.logger.LogInfo("Shutting down appearance manager...") } // Clear appearances m.appearances.ClearAppearances() }