package sign import ( "fmt" "sync" ) // Manager provides high-level management of the sign system type Manager struct { signs map[int32]*Sign // Signs by ID signsByZone map[int32][]*Sign // Signs by zone ID signsByWidget map[int32]*Sign // Signs by widget ID database Database logger Logger mutex sync.RWMutex // Statistics totalSigns int64 signsByType map[int8]int64 // Sign type -> count signInteractions int64 zoneTransports int64 transporterUses int64 } // NewManager creates a new sign manager func NewManager(database Database, logger Logger) *Manager { return &Manager{ signs: make(map[int32]*Sign), signsByZone: make(map[int32][]*Sign), signsByWidget: make(map[int32]*Sign), database: database, logger: logger, signsByType: make(map[int8]int64), } } // Initialize loads signs from database func (m *Manager) Initialize() error { if m.logger != nil { m.logger.LogInfo("Initializing sign manager...") } // TODO: Load all signs from database when database system is integrated // This would typically iterate through all zones and load their signs return nil } // LoadZoneSigns loads signs for a specific zone func (m *Manager) LoadZoneSigns(zoneID int32) error { if m.database == nil { return fmt.Errorf("database is nil") } signs, err := m.database.LoadSigns(zoneID) if err != nil { return fmt.Errorf("failed to load signs for zone %d: %w", zoneID, err) } m.mutex.Lock() defer m.mutex.Unlock() for _, sign := range signs { m.addSignUnsafe(sign) } if m.logger != nil { m.logger.LogInfo("Loaded %d signs for zone %d", len(signs), zoneID) } return nil } // AddSign adds a sign to the manager func (m *Manager) AddSign(sign *Sign) error { if sign == nil { return fmt.Errorf("sign is nil") } // Validate the sign if issues := sign.Validate(); len(issues) > 0 { return fmt.Errorf("sign validation failed: %v", issues) } m.mutex.Lock() defer m.mutex.Unlock() m.addSignUnsafe(sign) if m.logger != nil { m.logger.LogInfo("Added sign %d (widget %d) of type %d", sign.Spawn.GetDatabaseID(), sign.GetWidgetID(), sign.GetSignType()) } return nil } // addSignUnsafe adds a sign without locking (internal use) func (m *Manager) addSignUnsafe(sign *Sign) { signID := sign.Spawn.GetDatabaseID() widgetID := sign.GetWidgetID() // Add to main collection m.signs[signID] = sign // Add to widget collection if widgetID > 0 { m.signsByWidget[widgetID] = sign } // Add to zone collection if sign.Spawn != nil { zoneID := sign.Spawn.GetZone() m.signsByZone[zoneID] = append(m.signsByZone[zoneID], sign) } // Update statistics m.totalSigns++ m.signsByType[sign.GetSignType()]++ } // RemoveSign removes a sign from the manager func (m *Manager) RemoveSign(signID int32) bool { m.mutex.Lock() defer m.mutex.Unlock() sign, exists := m.signs[signID] if !exists { return false } // Remove from main collection delete(m.signs, signID) // Remove from widget collection if sign.GetWidgetID() > 0 { delete(m.signsByWidget, sign.GetWidgetID()) } // Remove from zone collection if sign.Spawn != nil { zoneID := sign.Spawn.GetZone() if zoneSigns, exists := m.signsByZone[zoneID]; exists { for i, zoneSign := range zoneSigns { if zoneSign == sign { m.signsByZone[zoneID] = append(zoneSigns[:i], zoneSigns[i+1:]...) break } } } } // Update statistics m.totalSigns-- m.signsByType[sign.GetSignType()]-- if m.logger != nil { m.logger.LogInfo("Removed sign %d (widget %d)", signID, sign.GetWidgetID()) } return true } // GetSign returns a sign by ID func (m *Manager) GetSign(signID int32) *Sign { m.mutex.RLock() defer m.mutex.RUnlock() return m.signs[signID] } // GetSignByWidget returns a sign by widget ID func (m *Manager) GetSignByWidget(widgetID int32) *Sign { m.mutex.RLock() defer m.mutex.RUnlock() return m.signsByWidget[widgetID] } // GetZoneSigns returns all signs in a zone func (m *Manager) GetZoneSigns(zoneID int32) []*Sign { m.mutex.RLock() defer m.mutex.RUnlock() signs := m.signsByZone[zoneID] // Return a copy to prevent external modification result := make([]*Sign, len(signs)) copy(result, signs) return result } // GetSignsByType returns all signs of a specific type func (m *Manager) GetSignsByType(signType int8) []*Sign { m.mutex.RLock() defer m.mutex.RUnlock() var result []*Sign for _, sign := range m.signs { if sign.GetSignType() == signType { result = append(result, sign) } } return result } // SaveSign saves a sign to database func (m *Manager) SaveSign(sign *Sign) error { if m.database == nil { return fmt.Errorf("database is nil") } if sign == nil { return fmt.Errorf("sign is nil") } err := m.database.SaveSign(sign) if err != nil { return fmt.Errorf("failed to save sign: %w", err) } if m.logger != nil { m.logger.LogDebug("Saved sign %d to database", sign.Spawn.GetDatabaseID()) } return nil } // HandleSignUse processes sign usage and records statistics func (m *Manager) HandleSignUse(sign *Sign, client Client, command string) error { if sign == nil { return fmt.Errorf("sign is nil") } m.mutex.Lock() m.signInteractions++ m.mutex.Unlock() err := sign.HandleUse(client, command) // Record specific interaction types for statistics if err == nil { m.mutex.Lock() if sign.IsZoneSign() { m.zoneTransports++ } if sign.Spawn != nil && sign.Spawn.GetTransporterID() > 0 { m.transporterUses++ } m.mutex.Unlock() } if m.logger != nil { if err != nil { m.logger.LogError("Sign %d use failed: %v", sign.Spawn.GetDatabaseID(), err) } else { m.logger.LogDebug("Sign %d used successfully by character %d", sign.Spawn.GetDatabaseID(), client.GetCharacterID()) } } return err } // GetStatistics returns sign system statistics func (m *Manager) GetStatistics() map[string]any { m.mutex.RLock() defer m.mutex.RUnlock() stats := make(map[string]any) stats["total_signs"] = m.totalSigns stats["sign_interactions"] = m.signInteractions stats["zone_transports"] = m.zoneTransports stats["transporter_uses"] = m.transporterUses // Copy sign type statistics typeStats := make(map[int8]int64) for signType, count := range m.signsByType { typeStats[signType] = count } stats["signs_by_type"] = typeStats // Zone statistics zoneStats := make(map[int32]int) for zoneID, signs := range m.signsByZone { zoneStats[zoneID] = len(signs) } stats["signs_by_zone"] = zoneStats return stats } // ResetStatistics resets all statistics func (m *Manager) ResetStatistics() { m.mutex.Lock() defer m.mutex.Unlock() m.signInteractions = 0 m.zoneTransports = 0 m.transporterUses = 0 } // ValidateAllSigns validates all signs in the system func (m *Manager) ValidateAllSigns() map[int32][]string { m.mutex.RLock() defer m.mutex.RUnlock() issues := make(map[int32][]string) for signID, sign := range m.signs { if signIssues := sign.Validate(); len(signIssues) > 0 { issues[signID] = signIssues } } return issues } // GetSignCount returns the total number of signs func (m *Manager) GetSignCount() int64 { m.mutex.RLock() defer m.mutex.RUnlock() return m.totalSigns } // GetSignTypeCount returns the number of signs of a specific type func (m *Manager) GetSignTypeCount(signType int8) int64 { m.mutex.RLock() defer m.mutex.RUnlock() return m.signsByType[signType] } // ProcessCommand handles sign-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 "list": return m.handleListCommand(args) case "info": return m.handleInfoCommand(args) default: return "", fmt.Errorf("unknown sign command: %s", command) } } // handleStatsCommand shows sign system statistics func (m *Manager) handleStatsCommand(args []string) (string, error) { stats := m.GetStatistics() result := "Sign System Statistics:\n" result += fmt.Sprintf("Total Signs: %d\n", stats["total_signs"]) result += fmt.Sprintf("Sign Interactions: %d\n", stats["sign_interactions"]) result += fmt.Sprintf("Zone Transports: %d\n", stats["zone_transports"]) result += fmt.Sprintf("Transporter Uses: %d\n", stats["transporter_uses"]) typeStats := stats["signs_by_type"].(map[int8]int64) result += fmt.Sprintf("Generic Signs: %d\n", typeStats[SignTypeGeneric]) result += fmt.Sprintf("Zone Signs: %d\n", typeStats[SignTypeZone]) return result, nil } // handleValidateCommand validates all signs func (m *Manager) handleValidateCommand(args []string) (string, error) { issues := m.ValidateAllSigns() if len(issues) == 0 { return "All signs are valid.", nil } result := fmt.Sprintf("Found issues with %d signs:\n", len(issues)) count := 0 for signID, signIssues := range issues { if count >= 10 { // Limit output result += "... (and more)\n" break } result += fmt.Sprintf("Sign %d:\n", signID) for _, issue := range signIssues { result += fmt.Sprintf(" - %s\n", issue) } count++ } return result, nil } // handleListCommand lists signs func (m *Manager) handleListCommand(args []string) (string, error) { m.mutex.RLock() defer m.mutex.RUnlock() if len(m.signs) == 0 { return "No signs loaded.", nil } result := fmt.Sprintf("Signs (%d):\n", len(m.signs)) count := 0 for signID, sign := range m.signs { if count >= 20 { // Limit output result += "... (and more)\n" break } typeName := "Generic" if sign.GetSignType() == SignTypeZone { typeName = "Zone" } result += fmt.Sprintf(" %d: %s (%s, Widget: %d)\n", signID, sign.GetSignTitle(), typeName, sign.GetWidgetID()) count++ } return result, nil } // handleInfoCommand shows information about a specific sign func (m *Manager) handleInfoCommand(args []string) (string, error) { if len(args) == 0 { return "", fmt.Errorf("sign ID required") } // Parse sign ID var signID int32 if _, err := fmt.Sscanf(args[0], "%d", &signID); err != nil { return "", fmt.Errorf("invalid sign ID: %s", args[0]) } sign := m.GetSign(signID) if sign == nil { return fmt.Sprintf("Sign %d not found.", signID), nil } result := fmt.Sprintf("Sign Information:\n") result += fmt.Sprintf("ID: %d\n", signID) result += fmt.Sprintf("Widget ID: %d\n", sign.GetWidgetID()) result += fmt.Sprintf("Type: %d\n", sign.GetSignType()) result += fmt.Sprintf("Title: %s\n", sign.GetSignTitle()) result += fmt.Sprintf("Description: %s\n", sign.GetSignDescription()) result += fmt.Sprintf("Language: %d\n", sign.GetLanguage()) if sign.IsZoneSign() { result += fmt.Sprintf("Zone ID: %d\n", sign.GetSignZoneID()) result += fmt.Sprintf("Zone Coords: (%.2f, %.2f, %.2f)\n", sign.GetSignZoneX(), sign.GetSignZoneY(), sign.GetSignZoneZ()) result += fmt.Sprintf("Zone Heading: %.2f\n", sign.GetSignZoneHeading()) result += fmt.Sprintf("Distance: %.2f\n", sign.GetSignDistance()) } result += fmt.Sprintf("Include Location: %t\n", sign.GetIncludeLocation()) result += fmt.Sprintf("Include Heading: %t\n", sign.GetIncludeHeading()) return result, nil } // Shutdown gracefully shuts down the manager func (m *Manager) Shutdown() { if m.logger != nil { m.logger.LogInfo("Shutting down sign manager...") } // Nothing to clean up currently, but placeholder for future cleanup }