eq2go/internal/languages/manager.go

526 lines
13 KiB
Go

package languages
import (
"fmt"
"sync"
)
// Manager provides high-level management of the language system
type Manager struct {
masterLanguagesList *MasterLanguagesList
database Database
logger Logger
mutex sync.RWMutex
// Statistics
languageLookups int64
playersWithLanguages int64
languageUsageCount map[int32]int64 // Language ID -> usage count
}
// NewManager creates a new language manager
func NewManager(database Database, logger Logger) *Manager {
return &Manager{
masterLanguagesList: NewMasterLanguagesList(),
database: database,
logger: logger,
languageUsageCount: make(map[int32]int64),
}
}
// Initialize loads languages from database
func (m *Manager) Initialize() error {
if m.logger != nil {
m.logger.LogInfo("Initializing language manager...")
}
if m.database == nil {
if m.logger != nil {
m.logger.LogWarning("No database provided, starting with empty language list")
}
return nil
}
// Load languages from database
languages, err := m.database.LoadAllLanguages()
if err != nil {
return fmt.Errorf("failed to load languages from database: %w", err)
}
for _, language := range languages {
if err := m.masterLanguagesList.AddLanguage(language); err != nil {
if m.logger != nil {
m.logger.LogError("Failed to add language %d (%s): %v", language.GetID(), language.GetName(), err)
}
}
}
if m.logger != nil {
m.logger.LogInfo("Loaded %d languages from database", len(languages))
}
return nil
}
// GetMasterLanguagesList returns the master language list
func (m *Manager) GetMasterLanguagesList() *MasterLanguagesList {
return m.masterLanguagesList
}
// CreatePlayerLanguagesList creates a new player language list
func (m *Manager) CreatePlayerLanguagesList() *PlayerLanguagesList {
m.mutex.Lock()
m.playersWithLanguages++
m.mutex.Unlock()
return NewPlayerLanguagesList()
}
// GetLanguage returns a language by ID
func (m *Manager) GetLanguage(id int32) *Language {
m.mutex.Lock()
m.languageLookups++
m.mutex.Unlock()
return m.masterLanguagesList.GetLanguage(id)
}
// GetLanguageByName returns a language by name
func (m *Manager) GetLanguageByName(name string) *Language {
m.mutex.Lock()
m.languageLookups++
m.mutex.Unlock()
return m.masterLanguagesList.GetLanguageByName(name)
}
// AddLanguage adds a new language to the system
func (m *Manager) AddLanguage(language *Language) error {
if language == nil {
return fmt.Errorf("language cannot be nil")
}
// Add to master list
if err := m.masterLanguagesList.AddLanguage(language); err != nil {
return fmt.Errorf("failed to add language to master list: %w", err)
}
// Save to database if available
if m.database != nil {
if err := m.database.SaveLanguage(language); err != nil {
// Remove from master list if database save failed
m.masterLanguagesList.RemoveLanguage(language.GetID())
return fmt.Errorf("failed to save language to database: %w", err)
}
}
if m.logger != nil {
m.logger.LogInfo("Added language %d: %s", language.GetID(), language.GetName())
}
return nil
}
// UpdateLanguage updates an existing language
func (m *Manager) UpdateLanguage(language *Language) error {
if language == nil {
return fmt.Errorf("language cannot be nil")
}
// Update in master list
if err := m.masterLanguagesList.UpdateLanguage(language); err != nil {
return fmt.Errorf("failed to update language in master list: %w", err)
}
// Save to database if available
if m.database != nil {
if err := m.database.SaveLanguage(language); err != nil {
return fmt.Errorf("failed to save language to database: %w", err)
}
}
if m.logger != nil {
m.logger.LogInfo("Updated language %d: %s", language.GetID(), language.GetName())
}
return nil
}
// RemoveLanguage removes a language from the system
func (m *Manager) RemoveLanguage(id int32) error {
// Check if language exists
if !m.masterLanguagesList.HasLanguage(id) {
return fmt.Errorf("language with ID %d does not exist", id)
}
// Remove from database first if available
if m.database != nil {
if err := m.database.DeleteLanguage(id); err != nil {
return fmt.Errorf("failed to delete language from database: %w", err)
}
}
// Remove from master list
if !m.masterLanguagesList.RemoveLanguage(id) {
return fmt.Errorf("failed to remove language from master list")
}
if m.logger != nil {
m.logger.LogInfo("Removed language %d", id)
}
return nil
}
// RecordLanguageUsage records language usage for statistics
func (m *Manager) RecordLanguageUsage(languageID int32) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.languageUsageCount[languageID]++
}
// GetStatistics returns language system statistics
func (m *Manager) GetStatistics() *LanguageStatistics {
m.mutex.RLock()
defer m.mutex.RUnlock()
// Create language name mapping
languagesByName := make(map[string]int32)
allLanguages := m.masterLanguagesList.GetAllLanguages()
for _, lang := range allLanguages {
languagesByName[lang.GetName()] = lang.GetID()
}
// Copy usage count
usageCount := make(map[int32]int64)
for id, count := range m.languageUsageCount {
usageCount[id] = count
}
return &LanguageStatistics{
TotalLanguages: len(allLanguages),
PlayersWithLanguages: int(m.playersWithLanguages),
LanguageUsageCount: usageCount,
LanguageLookups: m.languageLookups,
LanguagesByName: languagesByName,
}
}
// ResetStatistics resets all statistics
func (m *Manager) ResetStatistics() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.languageLookups = 0
m.playersWithLanguages = 0
m.languageUsageCount = make(map[int32]int64)
}
// ValidateAllLanguages validates all languages in the system
func (m *Manager) ValidateAllLanguages() []string {
allLanguages := m.masterLanguagesList.GetAllLanguages()
var issues []string
for _, lang := range allLanguages {
if !lang.IsValid() {
issues = append(issues, fmt.Sprintf("Language %d (%s) is invalid", lang.GetID(), lang.GetName()))
}
}
return issues
}
// ReloadFromDatabase reloads all languages from database
func (m *Manager) ReloadFromDatabase() error {
if m.database == nil {
return fmt.Errorf("no database available")
}
// Clear current languages
m.masterLanguagesList.Clear()
// Reload from database
return m.Initialize()
}
// GetLanguageCount returns the total number of languages
func (m *Manager) GetLanguageCount() int32 {
return m.masterLanguagesList.Size()
}
// ProcessCommand handles language-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)
case "reload":
return m.handleReloadCommand(args)
case "add":
return m.handleAddCommand(args)
case "remove":
return m.handleRemoveCommand(args)
case "search":
return m.handleSearchCommand(args)
default:
return "", fmt.Errorf("unknown language command: %s", command)
}
}
// handleStatsCommand shows language system statistics
func (m *Manager) handleStatsCommand(args []string) (string, error) {
stats := m.GetStatistics()
result := "Language System Statistics:\n"
result += fmt.Sprintf("Total Languages: %d\n", stats.TotalLanguages)
result += fmt.Sprintf("Players with Languages: %d\n", stats.PlayersWithLanguages)
result += fmt.Sprintf("Language Lookups: %d\n", stats.LanguageLookups)
if len(stats.LanguageUsageCount) > 0 {
result += "\nMost Used Languages:\n"
// Show top 5 most used languages
count := 0
for langID, usage := range stats.LanguageUsageCount {
if count >= 5 {
break
}
lang := m.GetLanguage(langID)
name := "Unknown"
if lang != nil {
name = lang.GetName()
}
result += fmt.Sprintf(" %s (ID: %d): %d uses\n", name, langID, usage)
count++
}
}
return result, nil
}
// handleValidateCommand validates all languages
func (m *Manager) handleValidateCommand(args []string) (string, error) {
issues := m.ValidateAllLanguages()
if len(issues) == 0 {
return "All languages are valid.", nil
}
result := fmt.Sprintf("Found %d issues with languages:\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
}
// handleListCommand lists languages
func (m *Manager) handleListCommand(args []string) (string, error) {
languages := m.masterLanguagesList.GetAllLanguages()
if len(languages) == 0 {
return "No languages loaded.", nil
}
result := fmt.Sprintf("Languages (%d):\n", len(languages))
count := 0
for _, language := range languages {
if count >= 20 { // Limit output
result += "... (and more)\n"
break
}
result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName())
count++
}
return result, nil
}
// handleInfoCommand shows information about a specific language
func (m *Manager) handleInfoCommand(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("language ID or name required")
}
var language *Language
// Try to parse as ID first
var languageID int32
if _, err := fmt.Sscanf(args[0], "%d", &languageID); err == nil {
language = m.GetLanguage(languageID)
} else {
// Try as name
language = m.GetLanguageByName(args[0])
}
if language == nil {
return fmt.Sprintf("Language '%s' not found.", args[0]), nil
}
result := fmt.Sprintf("Language Information:\n")
result += fmt.Sprintf("ID: %d\n", language.GetID())
result += fmt.Sprintf("Name: %s\n", language.GetName())
result += fmt.Sprintf("Save Needed: %v\n", language.GetSaveNeeded())
// Show usage statistics if available
m.mutex.RLock()
if usage, exists := m.languageUsageCount[language.GetID()]; exists {
result += fmt.Sprintf("Usage Count: %d\n", usage)
} else {
result += "Usage Count: 0\n"
}
m.mutex.RUnlock()
return result, nil
}
// handleReloadCommand reloads languages from database
func (m *Manager) handleReloadCommand(args []string) (string, error) {
if err := m.ReloadFromDatabase(); err != nil {
return "", fmt.Errorf("failed to reload languages: %w", err)
}
count := m.GetLanguageCount()
return fmt.Sprintf("Successfully reloaded %d languages from database.", count), nil
}
// handleAddCommand adds a new language
func (m *Manager) handleAddCommand(args []string) (string, error) {
if len(args) < 2 {
return "", fmt.Errorf("usage: add <id> <name>")
}
var id int32
if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil {
return "", fmt.Errorf("invalid language ID: %s", args[0])
}
name := args[1]
language := NewLanguage()
language.SetID(id)
language.SetName(name)
language.SetSaveNeeded(true)
if err := m.AddLanguage(language); err != nil {
return "", fmt.Errorf("failed to add language: %w", err)
}
return fmt.Sprintf("Successfully added language %d: %s", id, name), nil
}
// handleRemoveCommand removes a language
func (m *Manager) handleRemoveCommand(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("language ID required")
}
var id int32
if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil {
return "", fmt.Errorf("invalid language ID: %s", args[0])
}
if err := m.RemoveLanguage(id); err != nil {
return "", fmt.Errorf("failed to remove language: %w", err)
}
return fmt.Sprintf("Successfully removed language %d", id), nil
}
// handleSearchCommand searches for languages by name
func (m *Manager) handleSearchCommand(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("search term required")
}
searchTerm := args[0]
languages := m.masterLanguagesList.GetAllLanguages()
var results []*Language
// Search by name (case-insensitive partial match)
for _, language := range languages {
if contains(language.GetName(), searchTerm) {
results = append(results, language)
}
}
if len(results) == 0 {
return fmt.Sprintf("No languages found matching '%s'.", searchTerm), nil
}
result := fmt.Sprintf("Found %d languages matching '%s':\n", len(results), searchTerm)
for i, language := range results {
if i >= 20 { // Limit output
result += "... (and more)\n"
break
}
result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName())
}
return result, nil
}
// Shutdown gracefully shuts down the manager
func (m *Manager) Shutdown() {
if m.logger != nil {
m.logger.LogInfo("Shutting down language manager...")
}
// Clear languages
m.masterLanguagesList.Clear()
}
// contains checks if a string contains a substring (case-insensitive)
func contains(str, substr string) bool {
if len(substr) == 0 {
return true
}
if len(str) < len(substr) {
return false
}
// Convert to lowercase for case-insensitive comparison
strLower := make([]byte, len(str))
substrLower := make([]byte, len(substr))
for i := 0; i < len(str); i++ {
if str[i] >= 'A' && str[i] <= 'Z' {
strLower[i] = str[i] + 32
} else {
strLower[i] = str[i]
}
}
for i := 0; i < len(substr); i++ {
if substr[i] >= 'A' && substr[i] <= 'Z' {
substrLower[i] = substr[i] + 32
} else {
substrLower[i] = substr[i]
}
}
for i := 0; i <= len(strLower)-len(substrLower); i++ {
match := true
for j := 0; j < len(substrLower); j++ {
if strLower[i+j] != substrLower[j] {
match = false
break
}
}
if match {
return true
}
}
return false
}