Vereinsmeisterschaften  22aa7800eae54b428d40e835886cefe1fdefdfdf
This is a software that can be used to manage the internal competition of the swimming club Illertissen called "Vereinsmeisterschaften".
Loading...
Searching...
No Matches
CompetitionDistanceRuleService.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using System.Collections.ObjectModel;
3using System.ComponentModel;
4using System.Data;
5using System.Runtime.Intrinsics.X86;
6using System.Text;
10
12{
17 {
22
26 public event EventHandler OnFileFinished;
27
28 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
29
33 public string PersistentPath { get; set; }
34
35 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
36
40 private ObservableCollection<CompetitionDistanceRule> _competitionDistanceRules { get; set; }
41
45 private List<CompetitionDistanceRule> _competitionDistanceRulesOnLoad { get; set; }
46
47 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
48
49 private IFileService _fileService;
50 private ICompetitionService _competitionService;
51
56 {
57 _competitionDistanceRules = new ObservableCollection<CompetitionDistanceRule>();
58 _competitionDistanceRules.CollectionChanged += _competitionDistanceRules_CollectionChanged;
59 _fileService = fileService;
60 }
61
63 public void SetCompetitionServiceObj(ICompetitionService competitionService)
64 {
65 _competitionService = competitionService;
66 }
67
68 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
69
78 public async Task<bool> Load(string path, CancellationToken cancellationToken)
79 {
80 bool importingResult = false;
81 Exception exception = null;
82 await Task.Run(() =>
83 {
84 try
85 {
86 if (!File.Exists(path))
87 {
88 OnFileProgress?.Invoke(this, 0);
89 ClearAll();
90 OnFileProgress?.Invoke(this, 100);
91 }
92 else
93 {
94 List<CompetitionDistanceRule> list = _fileService.LoadFromCsv<CompetitionDistanceRule>(path, cancellationToken, CompetitionDistanceRule.SetPropertyFromString, OnFileProgress, (header) =>
95 {
96 return PropertyNameLocalizedStringHelper.FindProperty(typeof(CompetitionDistanceRule), header);
97 });
98 _competitionDistanceRules = new ObservableCollection<CompetitionDistanceRule>();
99 foreach (CompetitionDistanceRule rule in list)
100 {
101 AddDistanceRule(rule);
102 }
103 _competitionDistanceRules.CollectionChanged += _competitionDistanceRules_CollectionChanged;
104 }
105
107
108 PersistentPath = path;
109 importingResult = true;
110 }
111 catch (OperationCanceledException)
112 {
113 importingResult = false;
114 }
115 catch (Exception ex)
116 {
117 exception = ex;
118 }
119 });
120 OnFileFinished?.Invoke(this, null);
121 if (exception != null) { throw exception; }
122
123 return importingResult;
124 }
125
126 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
127
134 public async Task<bool> Save(CancellationToken cancellationToken, string path = "")
135 {
136 if (string.IsNullOrEmpty(path)) { path = PersistentPath; }
137
138 bool saveResult = false;
139 Exception exception = null;
140 await Task.Run(() =>
141 {
142 try
143 {
144 _fileService.SaveToCsv(path, _competitionDistanceRules.ToList(), cancellationToken, OnFileProgress, (data, parentObject, currentProperty) =>
145 {
146 if (data is Enum dataEnum)
147 {
148 return EnumCoreLocalizedStringHelper.Convert(dataEnum);
149 }
150 else
151 {
152 return data.ToString();
153 }
154 },
155 (header, type) =>
156 {
157 return PropertyNameLocalizedStringHelper.Convert(typeof(CompetitionDistanceRule), header);
158 });
159
161 saveResult = true;
162 }
163 catch (OperationCanceledException)
164 {
165 saveResult = false;
166 }
167 catch (Exception ex)
168 {
169 exception = ex;
170 }
171 });
172 OnFileFinished?.Invoke(this, null);
173 if (exception != null) { throw exception; }
174 return saveResult;
175 }
176
177 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
178
184
185 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
186
188 public ObservableCollection<CompetitionDistanceRule> GetCompetitionDistanceRules() => _competitionDistanceRules;
189
191 public void ClearAll()
192 {
193 if (_competitionDistanceRules == null)
194 {
195 _competitionDistanceRules = new ObservableCollection<CompetitionDistanceRule>();
196 _competitionDistanceRules.CollectionChanged += _competitionDistanceRules_CollectionChanged;
197 }
199 {
200 rule.PropertyChanged -= DistanceRule_PropertyChanged;
201 }
202 if (_competitionDistanceRules.Count > 0) { _competitionDistanceRules.Clear(); }
203
204 OnPropertyChanged(nameof(HasUnsavedChanges));
205 }
206
208 public void ResetToLoadedState()
209 {
210 if (_competitionDistanceRulesOnLoad == null) { return; }
211 _competitionDistanceRules.CollectionChanged -= _competitionDistanceRules_CollectionChanged;
212 ClearAll();
214 {
216 }
217 _competitionDistanceRules.CollectionChanged += _competitionDistanceRules_CollectionChanged;
218 }
219
221 public void AddDistanceRule(CompetitionDistanceRule distanceRule)
222 {
223 if (_competitionDistanceRules == null)
224 {
225 _competitionDistanceRules = new ObservableCollection<CompetitionDistanceRule>();
226 _competitionDistanceRules.CollectionChanged += _competitionDistanceRules_CollectionChanged;
227 }
228 _competitionDistanceRules.Add(distanceRule);
229 distanceRule.PropertyChanged += DistanceRule_PropertyChanged;
230
231 OnPropertyChanged(nameof(HasUnsavedChanges));
232 }
233
236 {
237 distanceRule.PropertyChanged -= DistanceRule_PropertyChanged;
238 _competitionDistanceRules?.Remove(distanceRule);
239
240 OnPropertyChanged(nameof(HasUnsavedChanges));
241 }
242
243 private void DistanceRule_PropertyChanged(object sender, PropertyChangedEventArgs e)
244 {
246 OnPropertyChanged(nameof(HasUnsavedChanges));
247 }
248
249 private void _competitionDistanceRules_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
250 {
252 OnPropertyChanged(nameof(HasUnsavedChanges));
253 }
254
256 public ushort GetCompetitionDistanceFromRules(byte age, SwimmingStyles swimmingStyle)
257 {
258 CompetitionDistanceRule rule = _competitionDistanceRules.FirstOrDefault(r => age >= r.MinAge &&
259 age <= r.MaxAge &&
260 r.SwimmingStyle == swimmingStyle);
261 if (rule == null)
262 {
263 return 0; // No distance rule found
264 }
265 return rule.Distance;
266 }
267
268 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
269
270 #region Validation
271
273 public List<CompetitionDistanceRuleValidationIssue> ValidateRules()
274 {
275 List<CompetitionDistanceRuleValidationIssue> result = new List<CompetitionDistanceRuleValidationIssue>();
276
277 List<SwimmingStyles> ruleStyles = _competitionDistanceRules.Select(r => r.SwimmingStyle).Distinct().ToList();
278 foreach (SwimmingStyles style in ruleStyles)
279 {
280 result.AddRange(validateAgeGaps(style));
281 result.AddRange(validateOverlaps(style));
282 result.AddRange(validateUnreachableRules(style));
283 }
284
285 // Each unreachable rule is also overlapped completely by another rule.
286 // It is enough to only report the unreachable state.
287 HashSet<CompetitionDistanceRule> unreachableRules = result.Where(i => i.IssueType == CompetitionDistanceRuleValidationIssue.CompetitionDistanceRuleValidationIssueType.UnreachableRule)
288 .Select(i => i.Rule1)
289 .ToHashSet();
290 result.RemoveAll(i => i.IssueType == CompetitionDistanceRuleValidationIssue.CompetitionDistanceRuleValidationIssueType.Overlap &&
291 (unreachableRules.Contains(i.Rule1) || unreachableRules.Contains(i.Rule2)));
292
293 return result;
294 }
295
302 private List<CompetitionDistanceRuleValidationIssue> validateAgeGaps(SwimmingStyles style)
303 {
304 List<CompetitionDistanceRuleValidationIssue> validationResult = new List<CompetitionDistanceRuleValidationIssue>();
305
306 List<CompetitionDistanceRule> orderedAndFilteredRules = _competitionDistanceRules.Where(r => r.SwimmingStyle == style)?.OrderBy(r => r.MinAge).ToList();
307 for (int i = 0; i < orderedAndFilteredRules.Count - 1; i++)
308 {
309 CompetitionDistanceRule currentRule = orderedAndFilteredRules[i];
310 CompetitionDistanceRule nextRule = orderedAndFilteredRules[i + 1];
311
312 if (currentRule.MaxAge + 1 < nextRule.MinAge)
313 {
315 {
317 SwimmingStyle = style,
318 MinAge = (byte)(currentRule.MaxAge + 1),
319 MaxAge = (byte)(nextRule.MinAge - 1),
320 Rule1 = currentRule,
321 Rule2 = nextRule
322 };
323
324 validationResult.Add(issue);
325 }
326 }
327 return validationResult;
328 }
329
336 private List<CompetitionDistanceRuleValidationIssue> validateOverlaps(SwimmingStyles style)
337 {
338 List<CompetitionDistanceRuleValidationIssue> validationResult = new List<CompetitionDistanceRuleValidationIssue>();
339
340 List<CompetitionDistanceRule> orderedAndFilteredRules = _competitionDistanceRules.Where(r => r.SwimmingStyle == style)?.OrderBy(r => r.MinAge).ToList();
341 for (int i = 0; i < orderedAndFilteredRules.Count - 1; i++)
342 {
343 CompetitionDistanceRule currentRule = orderedAndFilteredRules[i];
344 CompetitionDistanceRule nextRule = orderedAndFilteredRules[i + 1];
345
346 if (nextRule.MinAge <= currentRule.MaxAge)
347 {
349 {
351 SwimmingStyle = style,
352 MinAge = nextRule.MinAge,
353 MaxAge = (byte)Math.Min(currentRule.MaxAge, nextRule.MaxAge),
354 Rule1 = currentRule,
355 Rule2 = nextRule
356 };
357
358 validationResult.Add(issue);
359 }
360 }
361 return validationResult;
362 }
363
370 private List<CompetitionDistanceRuleValidationIssue> validateUnreachableRules(SwimmingStyles style)
371 {
372 List<CompetitionDistanceRuleValidationIssue> validationResult = new List<CompetitionDistanceRuleValidationIssue>();
373
374 List<CompetitionDistanceRule> filteredRules = _competitionDistanceRules.Where(r => r.SwimmingStyle == style).ToList();
375
376 HashSet<byte> coveredAges = new HashSet<byte>();
377 foreach (CompetitionDistanceRule rule in filteredRules)
378 {
379 bool anyReachable = false;
380
381 for (byte age = rule.MinAge; age <= rule.MaxAge; age++)
382 {
383 if (!coveredAges.Contains(age))
384 {
385 anyReachable = true;
386 break;
387 }
388 }
389
390 if (!anyReachable)
391 {
393 {
395 SwimmingStyle = style,
396 Rule1 = rule
397 };
398 validationResult.Add(issue);
399 }
400
401 for (byte age = rule.MinAge; age <= rule.MaxAge; age++)
402 {
403 coveredAges.Add(age);
404 }
405 }
406 return validationResult;
407 }
408
409 #endregion
410 }
411}
static void SetPropertyFromString(CompetitionDistanceRule dataObj, string propertyName, string value)
Set the requested property in the CompetitionDistanceRule object by parsing the given string value.
Class describing one issue during the validation of the CompetitionDistanceRule
async Task< bool > Save(CancellationToken cancellationToken, string path="")
Save the list of Competitions to a file.
ObservableCollection< CompetitionDistanceRule > GetCompetitionDistanceRules()
Return all available objects.List of CompetitionDistanceRule objects
void AddDistanceRule(CompetitionDistanceRule distanceRule)
Add a new CompetitionDistanceRule to the CompetitionDistanceRules list.
void SetCompetitionServiceObj(ICompetitionService competitionService)
Save the reference to the ICompetitionService object.Dependency Injection in the constructor can't be...
List< CompetitionDistanceRuleValidationIssue > validateUnreachableRules(SwimmingStyles style)
Check if there are CompetitionDistanceRule objects for the requested SwimmingStyles that are never re...
List< CompetitionDistanceRule > _competitionDistanceRulesOnLoad
List with all objects at the time the method was called.
void RemoveDistanceRule(CompetitionDistanceRule distanceRule)
Remove the given CompetitionDistanceRule from the Competition list.
List< CompetitionDistanceRuleValidationIssue > validateOverlaps(SwimmingStyles style)
Check if there are overlaps between the CompetitionDistanceRule objects for the requested SwimmingSty...
ObservableCollection< CompetitionDistanceRule > _competitionDistanceRules
List with all objects.
bool HasUnsavedChanges
Check if the list of CompetitionDistanceRule has not saved changed.
List< CompetitionDistanceRuleValidationIssue > validateAgeGaps(SwimmingStyles style)
Check if there are gaps between the CompetitionDistanceRule objects for the requested SwimmingStyles ...
void ResetToLoadedState()
Reset the list of to the state when the method was called.This will clear all and add the that were...
EventHandler OnFileFinished
Event that is raised when the file operation is finished.
List< CompetitionDistanceRuleValidationIssue > ValidateRules()
Check if all rules are valid and return a list of found issues.CompetitionDistanceRuleValidationIssue...
ushort GetCompetitionDistanceFromRules(byte age, SwimmingStyles swimmingStyle)
Try to find a matching rule for the requested age and swimming style.Found distance or 0 if not found
ProgressDelegate OnFileProgress
Event that is raised when the file operation progress changes.
async Task< bool > Load(string path, CancellationToken cancellationToken)
Load a list of to the .
Interface for a service used to get and store a list of CompetitionDistanceRule objects.
Interface for a service used to get and store a list of objects.
void UpdateAllCompetitionDistancesFromDistanceRules(bool keepRudolphTableFlags=false)
Update the Competition.Distance from the matching CompetitionDistanceRule for all Competition objects...
Interface for a service that handles file operations.
delegate void ProgressDelegate(object sender, float progress, string currentStep="")
Delegate void for progress changes.
SwimmingStyles
Available swimming styles.