package rules import ( "fmt" "log" "strconv" "eq2emu/internal/database" ) // DatabaseService handles rule database operations // Converted from C++ WorldDatabase rule functions type DatabaseService struct { db *database.DB } // NewDatabaseService creates a new database service instance func NewDatabaseService(db *database.DB) *DatabaseService { return &DatabaseService{ db: db, } } // LoadGlobalRuleSet loads the global rule set from database // Converted from C++ WorldDatabase::LoadGlobalRuleSet() func (ds *DatabaseService) LoadGlobalRuleSet(ruleManager *RuleManager) error { if ds.db == nil { return fmt.Errorf("database not initialized") } ruleSetID := int32(0) // Get the default ruleset ID from variables table query := "SELECT variable_value FROM variables WHERE variable_name = ?" row, err := ds.db.QueryRow(query, DefaultRuleSetIDVar) if err != nil { return fmt.Errorf("error querying default ruleset ID: %v", err) } if row == nil { log.Printf("[Rules] Variables table is missing %s variable name, using code-default rules", DefaultRuleSetIDVar) return nil } defer row.Close() variableValue := row.Text(0) if id, err := strconv.ParseInt(variableValue, 10, 32); err == nil { ruleSetID = int32(id) log.Printf("[Rules] Loading Global Ruleset id %d", ruleSetID) } else { return fmt.Errorf("invalid ruleset ID format: %s", variableValue) } if ruleSetID > 0 { if !ruleManager.SetGlobalRuleSet(ruleSetID) { return fmt.Errorf("error loading global rule set - rule set with ID %d does not exist", ruleSetID) } } ruleManager.stats.IncrementDatabaseOperations() return nil } // LoadRuleSets loads all rule sets from database // Converted from C++ WorldDatabase::LoadRuleSets() func (ds *DatabaseService) LoadRuleSets(ruleManager *RuleManager, reload bool) error { if ds.db == nil { return fmt.Errorf("database not initialized") } if reload { ruleManager.Flush(true) } // First load the coded defaults into the global rule set ruleManager.LoadCodedDefaultsIntoRuleSet(ruleManager.GetGlobalRuleSet()) // Load active rule sets from database query := "SELECT ruleset_id, ruleset_name FROM rulesets WHERE ruleset_active > 0" loadedCount := 0 err := ds.db.Query(query, func(row *database.Row) error { ruleSetID := int32(row.Int64(0)) ruleSetName := row.Text(1) ruleSet := NewRuleSet() ruleSet.SetID(ruleSetID) ruleSet.SetName(ruleSetName) if ruleManager.AddRuleSet(ruleSet) { log.Printf("[Rules] Loading rule set '%s' (%d)", ruleSet.GetName(), ruleSet.GetID()) err := ds.LoadRuleSetDetails(ruleManager, ruleSet) if err != nil { log.Printf("[Rules] Error loading rule set details for '%s': %v", ruleSetName, err) return nil // Continue with other rule sets } loadedCount++ } else { log.Printf("[Rules] Unable to add rule set '%s' - ID %d already exists", ruleSetName, ruleSetID) } return nil }) if err != nil { return fmt.Errorf("error querying rule sets: %v", err) } log.Printf("[Rules] Loaded %d Rule Sets", loadedCount) // Load global rule set err = ds.LoadGlobalRuleSet(ruleManager) if err != nil { return fmt.Errorf("error loading global rule set: %v", err) } ruleManager.stats.IncrementDatabaseOperations() return nil } // LoadRuleSetDetails loads the detailed rules for a specific rule set // Converted from C++ WorldDatabase::LoadRuleSetDetails() func (ds *DatabaseService) LoadRuleSetDetails(ruleManager *RuleManager, ruleSet *RuleSet) error { if ds.db == nil { return fmt.Errorf("database not initialized") } if ruleSet == nil { return fmt.Errorf("rule set is nil") } // Copy rules from global rule set (coded defaults) first ruleSet.CopyRulesInto(ruleManager.GetGlobalRuleSet()) // Load rule overrides from database query := "SELECT rule_category, rule_type, rule_value FROM ruleset_details WHERE ruleset_id = ?" loadedRules := 0 err := ds.db.Query(query, func(row *database.Row) error { categoryName := row.Text(0) typeName := row.Text(1) ruleValue := row.Text(2) // Find the rule by name rule := ruleSet.GetRuleByName(categoryName, typeName) if rule == nil { log.Printf("[Rules] Unknown rule with category '%s' and type '%s'", categoryName, typeName) return nil // Continue with other rules } log.Printf("[Rules] Setting rule category '%s', type '%s' to value: %s", categoryName, typeName, ruleValue) rule.SetValue(ruleValue) loadedRules++ return nil }, ruleSet.GetID()) if err != nil { return fmt.Errorf("error querying rule set details: %v", err) } log.Printf("[Rules] Loaded %d rule overrides for rule set '%s'", loadedRules, ruleSet.GetName()) ruleManager.stats.IncrementDatabaseOperations() return nil } // SaveRuleSet saves a rule set to the database func (ds *DatabaseService) SaveRuleSet(ruleSet *RuleSet) error { if ds.db == nil { return fmt.Errorf("database not initialized") } if ruleSet == nil { return fmt.Errorf("rule set is nil") } // Use transaction for atomicity return ds.db.Transaction(func(tx *database.DB) error { // Insert or update rule set query := `INSERT INTO rulesets (ruleset_id, ruleset_name, ruleset_active) VALUES (?, ?, 1) ON CONFLICT(ruleset_id) DO UPDATE SET ruleset_name = excluded.ruleset_name, ruleset_active = excluded.ruleset_active` err := tx.Exec(query, ruleSet.GetID(), ruleSet.GetName()) if err != nil { return fmt.Errorf("error saving rule set: %v", err) } // Delete existing rule details err = tx.Exec("DELETE FROM ruleset_details WHERE ruleset_id = ?", ruleSet.GetID()) if err != nil { return fmt.Errorf("error deleting existing rule details: %v", err) } // Insert rule details rules := ruleSet.GetRules() for _, categoryMap := range rules { for _, rule := range categoryMap { if rule.IsValid() { combined := rule.GetCombined() parts := splitCombined(combined) if len(parts) == 2 { query := "INSERT INTO ruleset_details (ruleset_id, rule_category, rule_type, rule_value) VALUES (?, ?, ?, ?)" err = tx.Exec(query, ruleSet.GetID(), parts[0], parts[1], rule.GetValue()) if err != nil { return fmt.Errorf("error saving rule detail: %v", err) } } } } } return nil }) } // DeleteRuleSet deletes a rule set from the database func (ds *DatabaseService) DeleteRuleSet(ruleSetID int32) error { if ds.db == nil { return fmt.Errorf("database not initialized") } // Use transaction for atomicity return ds.db.Transaction(func(tx *database.DB) error { // Delete rule details first (foreign key constraint) err := tx.Exec("DELETE FROM ruleset_details WHERE ruleset_id = ?", ruleSetID) if err != nil { return fmt.Errorf("error deleting rule details: %v", err) } // Delete rule set err = tx.Exec("DELETE FROM rulesets WHERE ruleset_id = ?", ruleSetID) if err != nil { return fmt.Errorf("error deleting rule set: %v", err) } return nil }) } // SetDefaultRuleSet sets the default rule set ID in the variables table func (ds *DatabaseService) SetDefaultRuleSet(ruleSetID int32) error { if ds.db == nil { return fmt.Errorf("database not initialized") } query := `INSERT INTO variables (variable_name, variable_value, comment) VALUES (?, ?, 'Default ruleset ID') ON CONFLICT(variable_name) DO UPDATE SET variable_value = excluded.variable_value` err := ds.db.Exec(query, DefaultRuleSetIDVar, strconv.Itoa(int(ruleSetID))) if err != nil { return fmt.Errorf("error setting default rule set: %v", err) } return nil } // GetDefaultRuleSetID gets the default rule set ID from the variables table func (ds *DatabaseService) GetDefaultRuleSetID() (int32, error) { if ds.db == nil { return 0, fmt.Errorf("database not initialized") } query := "SELECT variable_value FROM variables WHERE variable_name = ?" row, err := ds.db.QueryRow(query, DefaultRuleSetIDVar) if err != nil { return 0, fmt.Errorf("error querying default ruleset ID: %v", err) } if row == nil { return 0, fmt.Errorf("default ruleset ID not found in variables table") } defer row.Close() variableValue := row.Text(0) if id, err := strconv.ParseInt(variableValue, 10, 32); err == nil { return int32(id), nil } return 0, fmt.Errorf("invalid ruleset ID format: %s", variableValue) } // GetRuleSetList returns a list of all rule sets func (ds *DatabaseService) GetRuleSetList() ([]RuleSetInfo, error) { if ds.db == nil { return nil, fmt.Errorf("database not initialized") } query := "SELECT ruleset_id, ruleset_name, ruleset_active FROM rulesets ORDER BY ruleset_id" var ruleSets []RuleSetInfo err := ds.db.Query(query, func(row *database.Row) error { info := RuleSetInfo{ ID: int32(row.Int64(0)), Name: row.Text(1), Active: row.Bool(2), } ruleSets = append(ruleSets, info) return nil }) if err != nil { return nil, fmt.Errorf("error querying rule sets: %v", err) } return ruleSets, nil } // ValidateDatabase ensures the required tables exist func (ds *DatabaseService) ValidateDatabase() error { if ds.db == nil { return fmt.Errorf("database not initialized") } // Check if rulesets table exists query := "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='rulesets'" row, err := ds.db.QueryRow(query) if err != nil { return fmt.Errorf("error checking rulesets table: %v", err) } if row == nil || row.Int(0) == 0 { return fmt.Errorf("rulesets table does not exist") } row.Close() // Check if ruleset_details table exists query = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='ruleset_details'" row, err = ds.db.QueryRow(query) if err != nil { return fmt.Errorf("error checking ruleset_details table: %v", err) } if row == nil || row.Int(0) == 0 { return fmt.Errorf("ruleset_details table does not exist") } row.Close() // Check if variables table exists query = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='variables'" row, err = ds.db.QueryRow(query) if err != nil { return fmt.Errorf("error checking variables table: %v", err) } if row == nil || row.Int(0) == 0 { return fmt.Errorf("variables table does not exist") } row.Close() return nil } // RuleSetInfo contains basic information about a rule set type RuleSetInfo struct { ID int32 `json:"id"` Name string `json:"name"` Active bool `json:"active"` } // Helper function to split combined rule string func splitCombined(combined string) []string { for i, char := range combined { if char == ':' { return []string{combined[:i], combined[i+1:]} } } return []string{combined} } // CreateRulesTables creates the necessary database tables for rules func (ds *DatabaseService) CreateRulesTables() error { if ds.db == nil { return fmt.Errorf("database not initialized") } // Create rulesets table createRuleSets := ` CREATE TABLE IF NOT EXISTS rulesets ( ruleset_id INTEGER PRIMARY KEY, ruleset_name TEXT NOT NULL UNIQUE, ruleset_active INTEGER NOT NULL DEFAULT 0 )` err := ds.db.Exec(createRuleSets) if err != nil { return fmt.Errorf("error creating rulesets table: %v", err) } // Create ruleset_details table createRuleSetDetails := ` CREATE TABLE IF NOT EXISTS ruleset_details ( id INTEGER PRIMARY KEY AUTOINCREMENT, ruleset_id INTEGER NOT NULL, rule_category TEXT NOT NULL, rule_type TEXT NOT NULL, rule_value TEXT NOT NULL, description TEXT, FOREIGN KEY (ruleset_id) REFERENCES rulesets(ruleset_id) ON DELETE CASCADE )` err = ds.db.Exec(createRuleSetDetails) if err != nil { return fmt.Errorf("error creating ruleset_details table: %v", err) } // Create variables table if it doesn't exist createVariables := ` CREATE TABLE IF NOT EXISTS variables ( variable_name TEXT PRIMARY KEY, variable_value TEXT NOT NULL, comment TEXT )` err = ds.db.Exec(createVariables) if err != nil { return fmt.Errorf("error creating variables table: %v", err) } // Create indexes for better performance indexes := []string{ "CREATE INDEX IF NOT EXISTS idx_ruleset_details_ruleset_id ON ruleset_details(ruleset_id)", "CREATE INDEX IF NOT EXISTS idx_ruleset_details_category_type ON ruleset_details(rule_category, rule_type)", "CREATE INDEX IF NOT EXISTS idx_rulesets_active ON rulesets(ruleset_active)", } for _, indexSQL := range indexes { err = ds.db.Exec(indexSQL) if err != nil { return fmt.Errorf("error creating index: %v", err) } } return nil }