525 lines
13 KiB
Go
525 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
|
|
} |