package guilds import ( "sort" "strings" "sync" "eq2emu/internal/database" ) // MasterList provides a bespoke, performance-optimized guild collection with specialized indices type MasterList struct { // Core storage guilds map[int32]*Guild mutex sync.RWMutex // Specialized indices for O(1) lookups byName map[string]*Guild // name -> guild (case-insensitive) byLevel map[int8][]*Guild // level -> guilds recruiting []*Guild // guilds with recruiters // Cached metadata with invalidation levels []int8 recruitingMap map[int32]bool // guildID -> isRecruiting metaStale bool } // NewMasterList creates a new bespoke master list optimized for guild operations func NewMasterList() *MasterList { return &MasterList{ guilds: make(map[int32]*Guild), byName: make(map[string]*Guild), byLevel: make(map[int8][]*Guild), recruiting: make([]*Guild, 0), recruitingMap: make(map[int32]bool), } } // Add adds a guild to the master list with O(1) core operations func (m *MasterList) Add(guild *Guild) bool { m.mutex.Lock() defer m.mutex.Unlock() guildID := guild.GetID() // Check existence if _, exists := m.guilds[guildID]; exists { return false } // Add to core storage m.guilds[guildID] = guild // Update specialized indices name := strings.ToLower(guild.GetName()) m.byName[name] = guild level := guild.GetLevel() m.byLevel[level] = append(m.byLevel[level], guild) // Update recruiting index isRecruiting := guild.GetNumRecruiters() > 0 m.recruitingMap[guildID] = isRecruiting if isRecruiting { m.recruiting = append(m.recruiting, guild) } // Invalidate metadata cache m.metaStale = true return true } // Get retrieves a guild by ID with O(1) performance func (m *MasterList) Get(guildID int32) *Guild { m.mutex.RLock() defer m.mutex.RUnlock() return m.guilds[guildID] } // GetByName retrieves a guild by name with O(1) performance func (m *MasterList) GetByName(name string) *Guild { m.mutex.RLock() defer m.mutex.RUnlock() return m.byName[strings.ToLower(name)] } // GetByLevel returns all guilds of a specific level with O(1) performance func (m *MasterList) GetByLevel(level int8) []*Guild { m.mutex.RLock() defer m.mutex.RUnlock() guilds := m.byLevel[level] if guilds == nil { return nil } // Return a copy to prevent external modification result := make([]*Guild, len(guilds)) copy(result, guilds) return result } // GetRecruiting returns all guilds that are recruiting with O(1) performance func (m *MasterList) GetRecruiting() []*Guild { m.mutex.RLock() defer m.mutex.RUnlock() // Return a copy to prevent external modification result := make([]*Guild, len(m.recruiting)) copy(result, m.recruiting) return result } // GetByLevelRange returns guilds within a level range using optimized range queries func (m *MasterList) GetByLevelRange(minLevel, maxLevel int8) []*Guild { m.mutex.RLock() defer m.mutex.RUnlock() var result []*Guild for level := minLevel; level <= maxLevel; level++ { if guilds := m.byLevel[level]; guilds != nil { result = append(result, guilds...) } } return result } // GetRecruitingByLevel returns recruiting guilds of a specific level using set intersection func (m *MasterList) GetRecruitingByLevel(level int8) []*Guild { m.mutex.RLock() defer m.mutex.RUnlock() levelGuilds := m.byLevel[level] if levelGuilds == nil { return nil } var result []*Guild for _, guild := range levelGuilds { if m.recruitingMap[guild.GetID()] { result = append(result, guild) } } return result } // Remove removes a guild from the master list func (m *MasterList) Remove(guildID int32) bool { m.mutex.Lock() defer m.mutex.Unlock() guild := m.guilds[guildID] if guild == nil { return false } // Remove from core storage delete(m.guilds, guildID) // Remove from name index name := strings.ToLower(guild.GetName()) delete(m.byName, name) // Remove from level index level := guild.GetLevel() if levelGuilds := m.byLevel[level]; levelGuilds != nil { for i, g := range levelGuilds { if g.GetID() == guildID { m.byLevel[level] = append(levelGuilds[:i], levelGuilds[i+1:]...) break } } // Clean up empty level arrays if len(m.byLevel[level]) == 0 { delete(m.byLevel, level) } } // Remove from recruiting index delete(m.recruitingMap, guildID) for i, g := range m.recruiting { if g.GetID() == guildID { m.recruiting = append(m.recruiting[:i], m.recruiting[i+1:]...) break } } // Invalidate metadata cache m.metaStale = true return true } // Update updates a guild's indices when its properties change func (m *MasterList) Update(guild *Guild) { m.mutex.Lock() defer m.mutex.Unlock() guildID := guild.GetID() oldGuild := m.guilds[guildID] if oldGuild == nil { return } // Update core storage m.guilds[guildID] = guild // Update name index if changed oldName := strings.ToLower(oldGuild.GetName()) newName := strings.ToLower(guild.GetName()) if oldName != newName { delete(m.byName, oldName) m.byName[newName] = guild } // Update level index if changed oldLevel := oldGuild.GetLevel() newLevel := guild.GetLevel() if oldLevel != newLevel { // Remove from old level if levelGuilds := m.byLevel[oldLevel]; levelGuilds != nil { for i, g := range levelGuilds { if g.GetID() == guildID { m.byLevel[oldLevel] = append(levelGuilds[:i], levelGuilds[i+1:]...) break } } if len(m.byLevel[oldLevel]) == 0 { delete(m.byLevel, oldLevel) } } // Add to new level m.byLevel[newLevel] = append(m.byLevel[newLevel], guild) } // Update recruiting index if changed oldRecruiting := m.recruitingMap[guildID] newRecruiting := guild.GetNumRecruiters() > 0 if oldRecruiting != newRecruiting { m.recruitingMap[guildID] = newRecruiting if oldRecruiting && !newRecruiting { // Remove from recruiting list for i, g := range m.recruiting { if g.GetID() == guildID { m.recruiting = append(m.recruiting[:i], m.recruiting[i+1:]...) break } } } else if !oldRecruiting && newRecruiting { // Add to recruiting list m.recruiting = append(m.recruiting, guild) } } // Invalidate metadata cache m.metaStale = true } // GetAll returns all guilds func (m *MasterList) GetAll() []*Guild { m.mutex.RLock() defer m.mutex.RUnlock() result := make([]*Guild, 0, len(m.guilds)) for _, guild := range m.guilds { result = append(result, guild) } return result } // Count returns the total number of guilds func (m *MasterList) Count() int { m.mutex.RLock() defer m.mutex.RUnlock() return len(m.guilds) } // GetLevels returns all guild levels with lazy caching func (m *MasterList) GetLevels() []int8 { m.mutex.Lock() defer m.mutex.Unlock() if !m.metaStale && m.levels != nil { return m.levels } // Rebuild level cache levelSet := make(map[int8]bool) for level := range m.byLevel { levelSet[level] = true } m.levels = make([]int8, 0, len(levelSet)) for level := range levelSet { m.levels = append(m.levels, level) } sort.Slice(m.levels, func(i, j int) bool { return m.levels[i] < m.levels[j] }) m.metaStale = false return m.levels } // GetStatistics returns detailed statistics about the guild system func (m *MasterList) GetStatistics() GuildStatistics { m.mutex.RLock() defer m.mutex.RUnlock() stats := GuildStatistics{ TotalGuilds: len(m.guilds), } totalMembers := 0 activeGuilds := 0 totalRecruiters := len(m.recruiting) uniqueAccounts := make(map[int32]bool) highestLevel := int8(1) for _, guild := range m.guilds { members := guild.GetAllMembers() memberCount := len(members) totalMembers += memberCount if memberCount > 0 { activeGuilds++ } // Track unique accounts for _, member := range members { uniqueAccounts[member.AccountID] = true } // Find highest level if guild.GetLevel() > highestLevel { highestLevel = guild.GetLevel() } } stats.TotalMembers = totalMembers stats.ActiveGuilds = activeGuilds stats.TotalRecruiters = totalRecruiters stats.UniqueAccounts = len(uniqueAccounts) stats.HighestGuildLevel = highestLevel if len(m.guilds) > 0 { stats.AverageGuildSize = float64(totalMembers) / float64(len(m.guilds)) } return stats } // Search performs efficient guild searches using specialized indices func (m *MasterList) Search(criteria GuildSearchCriteria) []*Guild { m.mutex.RLock() defer m.mutex.RUnlock() // Start with all guilds or use specialized indices for optimization var candidates []*Guild // Use level range optimization if specified if criteria.MinLevel > 0 || criteria.MaxLevel > 0 { minLevel := criteria.MinLevel maxLevel := criteria.MaxLevel if minLevel == 0 { minLevel = 1 } if maxLevel == 0 { maxLevel = 100 // reasonable max } for level := minLevel; level <= maxLevel; level++ { if guilds := m.byLevel[level]; guilds != nil { candidates = append(candidates, guilds...) } } } else if criteria.RecruitingOnly { // Use recruiting index for optimization candidates = make([]*Guild, len(m.recruiting)) copy(candidates, m.recruiting) } else { // Use all guilds candidates = make([]*Guild, 0, len(m.guilds)) for _, guild := range m.guilds { candidates = append(candidates, guild) } } // Apply remaining filters var results []*Guild for _, guild := range candidates { if m.matchesCriteria(guild, criteria) { results = append(results, guild) } } // Sort by name sort.Slice(results, func(i, j int) bool { return results[i].GetName() < results[j].GetName() }) return results } // LoadFromDatabase loads all guilds from database into the master list func (m *MasterList) LoadFromDatabase(db *database.Database) error { // Clear existing data m.mutex.Lock() m.guilds = make(map[int32]*Guild) m.byName = make(map[string]*Guild) m.byLevel = make(map[int8][]*Guild) m.recruiting = make([]*Guild, 0) m.recruitingMap = make(map[int32]bool) m.metaStale = true m.mutex.Unlock() // Load guild IDs first using MySQL-compatible approach var guildIDs []int32 rows, err := db.Query(`SELECT id FROM guilds ORDER BY id`) if err != nil { return err } defer rows.Close() for rows.Next() { var id int32 if err := rows.Scan(&id); err != nil { return err } guildIDs = append(guildIDs, id) } if err := rows.Err(); err != nil { return err } // Load each guild and add to master list for _, guildID := range guildIDs { guild, err := Load(db, guildID) if err != nil { continue // Skip failed loads } m.Add(guild) } return nil } // Helper method for criteria matching func (m *MasterList) matchesCriteria(guild *Guild, criteria GuildSearchCriteria) bool { // Name pattern matching if criteria.NamePattern != "" { if !strings.Contains(strings.ToLower(guild.GetName()), strings.ToLower(criteria.NamePattern)) { return false } } // Member count range memberCount := len(guild.GetAllMembers()) if criteria.MinMembers > 0 && memberCount < criteria.MinMembers { return false } if criteria.MaxMembers > 0 && memberCount > criteria.MaxMembers { return false } // Play style if criteria.PlayStyle > 0 && guild.GetRecruitingPlayStyle() != criteria.PlayStyle { return false } // Required flags for _, flag := range criteria.RequiredFlags { if guild.GetRecruitingFlag(flag) == 0 { return false } } // Required description tags for _, tag := range criteria.RequiredDescTags { found := false for i := int8(0); i < 4; i++ { if guild.GetRecruitingDescTag(i) == tag { found = true break } } if !found { return false } } // Excluded description tags for _, tag := range criteria.ExcludedDescTags { for i := int8(0); i < 4; i++ { if guild.GetRecruitingDescTag(i) == tag { return false } } } return true }