393 lines
10 KiB
Go

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]any {
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()
}