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
PersonService.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using System.Collections;
3using System.Collections.ObjectModel;
4using System.ComponentModel;
8
10{
14 public class PersonService : ObservableObject, IPersonService
15 {
20
24 public event EventHandler OnFileFinished;
25
26 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
27
31 public string PersistentPath { get; set; }
32
33 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
34
38 private ObservableCollection<Person> _personList { get; set; }
39
43 private List<Person> _personListOnLoad { get; set; }
44
45 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
46
47 private IFileService _fileService;
48 private IScoreService _scoreService;
49 private IRaceService _raceService;
50 private ICompetitionService _competitionService;
51
56 public PersonService(IFileService fileService)
57 {
58 _personList = new ObservableCollection<Person>();
59 _personList.CollectionChanged += _personList_CollectionChanged;
60 _fileService = fileService;
61 }
62
68 public void SetScoreServiceObj(IScoreService scoreService)
69 {
70 _scoreService = scoreService;
71 }
72
78 public void SetCompetitionServiceObj(ICompetitionService competitionService)
79 {
80 _competitionService = competitionService;
81 }
82
88 public void SetRaceServiceObj(IRaceService raceService)
89 {
90 _raceService = raceService;
91 }
92
93 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
94
103 public async Task<bool> Load(string path, CancellationToken cancellationToken)
104 {
105 bool importingResult = false;
106 Exception exception = null;
107 await Task.Run(() =>
108 {
109 try
110 {
111 if (!File.Exists(path))
112 {
113 OnFileProgress?.Invoke(this, 0);
114 ClearAll();
115 OnFileProgress?.Invoke(this, 100);
116 }
117 else
118 {
119 List<Person> list = _fileService.LoadFromCsv<Person>(path, cancellationToken, Person.SetPropertyFromString, OnFileProgress, (header) =>
120 {
121 return PropertyNameLocalizedStringHelper.FindProperty(typeof(Person), header);
122 });
123 _personList = new ObservableCollection<Person>();
124 _personList.CollectionChanged += _personList_CollectionChanged;
125 foreach (Person person in list)
126 {
127 AddPerson(person);
128 }
130 }
131
132 _personListOnLoad = _personList.ToList().ConvertAll(p => new Person(p));
133
134 PersistentPath = path;
135 importingResult = true;
136 }
137 catch (OperationCanceledException)
138 {
139 importingResult = false;
140 }
141 catch (Exception ex)
142 {
143 exception = ex;
144 }
145 });
146 OnFileFinished?.Invoke(this, null);
147 if (exception != null) { throw exception; }
148 return importingResult;
149 }
150
151 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
152
159 public async Task<bool> Save(CancellationToken cancellationToken, string path = "")
160 {
161 if (string.IsNullOrEmpty(path)) { path = PersistentPath; }
162
163 bool saveResult = false;
164 Exception exception = null;
165 await Task.Run(() =>
166 {
167 try
168 {
169 _fileService.SaveToCsv(path, _personList.ToList(), cancellationToken, OnFileProgress, (data, parentObject, currentProperty) =>
170 {
171 if (data is bool dataBool)
172 {
173 // Get the corresponding IsActive flag for the swimming style and set the file content accordingly
174 bool isActive = true;
175 switch (currentProperty.Name)
176 {
177 case nameof(Person.Breaststroke): isActive = (parentObject as Person)?.Starts[SwimmingStyles.Breaststroke]?.IsActive ?? true; break;
178 case nameof(Person.Freestyle): isActive = (parentObject as Person)?.Starts[SwimmingStyles.Freestyle]?.IsActive ?? true; break;
179 case nameof(Person.Backstroke): isActive = (parentObject as Person)?.Starts[SwimmingStyles.Backstroke]?.IsActive ?? true; break;
180 case nameof(Person.Butterfly): isActive = (parentObject as Person)?.Starts[SwimmingStyles.Butterfly]?.IsActive ?? true; break;
181 case nameof(Person.Medley): isActive = (parentObject as Person)?.Starts[SwimmingStyles.Medley]?.IsActive ?? true; break;
182 case nameof(Person.WaterFlea): isActive = (parentObject as Person)?.Starts[SwimmingStyles.WaterFlea]?.IsActive ?? true; break;
183 }
184 return dataBool ? (isActive ? "X" : Person.START_INACTIVE_MARKER_STRING) : "";
185 }
186 else if (data is Enum dataEnum)
187 {
188 return EnumCoreLocalizedStringHelper.Convert(dataEnum);
189 }
190 else if (data is IList dataList)
191 {
192 return string.Join(",", dataList.Cast<object>().Select(d => d.ToString()));
193 }
194 else
195 {
196 return data.ToString();
197 }
198 },
199 (header, type) =>
200 {
201 return PropertyNameLocalizedStringHelper.Convert(typeof(Person), header);
202 });
203
204 _personListOnLoad = _personList.ToList().ConvertAll(p => new Person(p));
205 saveResult = true;
206 }
207 catch (OperationCanceledException)
208 {
209 saveResult = false;
210 }
211 catch (Exception ex)
212 {
213 exception = ex;
214 }
215 });
216 OnFileFinished?.Invoke(this, null);
217 if (exception != null) { throw exception; }
218 return saveResult;
219 }
220
221 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
222
227 public ObservableCollection<Person> GetPersons() => _personList;
228
232 public void ClearAll()
233 {
234 if (_personList == null)
235 {
236 _personList = new ObservableCollection<Person>();
237 _personList.CollectionChanged += _personList_CollectionChanged;
238 }
239
240 foreach (Person person in _personList)
241 {
242 person.PropertyChanged -= Person_PropertyChanged;
243 }
244 if (_personList.Count > 0) { _personList.Clear(); }
245
246 OnPropertyChanged(nameof(PersonCount));
247 OnPropertyChanged(nameof(PersonStarts));
248 OnPropertyChanged(nameof(HasUnsavedChanges));
249 }
250
255 public void ResetToLoadedState()
256 {
257 if (_personListOnLoad == null) { return; }
258 ClearAll();
259 foreach (Person person in _personListOnLoad)
260 {
261 AddPerson(new Person(person));
262 }
264 _competitionService.UpdateAllCompetitionsForPerson();
265 _raceService.ReassignAllPersonStarts();
266 }
267
272 public void AddPerson(Person person)
273 {
274 if (_personList == null)
275 {
276 _personList = new ObservableCollection<Person>();
277 _personList.CollectionChanged += _personList_CollectionChanged;
278 }
279 _personList.Add(person);
280
281 person.PropertyChanged += Person_PropertyChanged;
282
284
285 OnPropertyChanged(nameof(PersonCount));
286 OnPropertyChanged(nameof(PersonStarts));
287 OnPropertyChanged(nameof(HasUnsavedChanges));
288 }
289
294 public void RemovePerson(Person person)
295 {
296 person.PropertyChanged -= Person_PropertyChanged;
297 _personList?.Remove(person);
298
300
301 OnPropertyChanged(nameof(PersonCount));
302 OnPropertyChanged(nameof(PersonStarts));
303 OnPropertyChanged(nameof(HasUnsavedChanges));
304 }
305
306 private bool _isupdatingScores = false; // Flag to avoid recursive calls of the Person_PropertyChanged event. During UpdateScoresForPerson the Score of the PersonStart is changed which would raise a property changed event of the Starts property of the Person again.
315 private void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
316 {
317 if (e.PropertyName == nameof(Person.Starts) && sender is Person senderPerson && _scoreService != null && !_isupdatingScores)
318 {
319 _isupdatingScores = true;
320 _scoreService.UpdateScoresForPerson(senderPerson);
321 _isupdatingScores = false;
322 }
323
324 switch (e.PropertyName)
325 {
326 case nameof(Person.Name):
327 case nameof(Person.FirstName):
328 // Duplicates depends on the basic parameters of the person (First name, Name, Birth year, Gender)
330 break;
331 case nameof(Person.Gender):
332 case nameof(Person.BirthYear):
333 // Duplicates depends on the basic parameters of the person (First name, Name, Birth year, Gender)
335 // Update the competitions for the person. They depend on the gender and birth year.
336 _competitionService.UpdateAllCompetitionsForPerson(sender as Person);
337 break;
338 case nameof(Person.Starts):
339 OnPropertyChanged(nameof(PersonStarts));
340 break;
341 default: break;
342 }
343
344 OnPropertyChanged(nameof(HasUnsavedChanges));
345 }
346
347 private void _personList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
348 {
349 UpdateHasDuplicatesForPersons();
350 OnPropertyChanged(nameof(PersonCount));
351 OnPropertyChanged(nameof(PersonStarts));
352 OnPropertyChanged(nameof(HasUnsavedChanges));
353 }
354
358 public int PersonCount => _personList?.Count ?? 0;
359
363 public int PersonStarts => _personList.Sum(p => p.Starts.Where(s => s.Value != null).Count());
364
369 {
371 IEnumerable<IGrouping<Person, Person>> duplicateGroups = _personList.GroupBy(p => p, basicPersonComparer).Where(g => g.Count() > 1);
372
373 // Reset all HasDuplicates flags
374 foreach (Person p in _personList) { p.HasDuplicates = false; }
375
376 // Set HasDuplicates flag for all persons that are in a duplicate group
377 foreach (IGrouping<Person, Person> duplicateGroup in duplicateGroups)
378 {
379 foreach (Person person in duplicateGroup) { person.HasDuplicates = true; }
380 }
381 }
382
387 public bool HasUnsavedChanges => (_personList != null && _personListOnLoad != null) ? !_personList.ToList().SequenceEqual(_personListOnLoad) : false;
388
389 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
390
398 public List<PersonStart> GetAllPersonStarts(PersonStartFilters filter = PersonStartFilters.None, object filterParameter = null, bool onlyValidStarts = false)
399 {
400 List<PersonStart> allPersonStarts = new List<PersonStart>();
401 foreach (Person person in _personList)
402 {
403 allPersonStarts.AddRange(person.Starts.Values.Cast<PersonStart>().Where(s => s != null));
404 }
405
406 if(onlyValidStarts)
407 {
408 allPersonStarts = allPersonStarts.Where(s => s.IsActive && s.IsCompetitionObjAssigned).ToList();
409 }
410
411 switch (filter)
412 {
413 case PersonStartFilters.None:
414 return allPersonStarts;
415 case PersonStartFilters.Person:
417 return allPersonStarts.Where(s => personBasicComparer.Equals(s.PersonObj, (Person)filterParameter)).ToList();
418 case PersonStartFilters.SwimmingStyle:
419 return allPersonStarts.Where(s => s.Style == (SwimmingStyles)filterParameter).ToList();
420 case PersonStartFilters.CompetitionID:
421 return allPersonStarts.Where(s => s.CompetitionObj != null && s.CompetitionObj.Id == (int)filterParameter).ToList();
422 default: return allPersonStarts;
423 }
424 }
425
426 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
427
428 #region Friend Management
429
434 {
435 // For each person: Find all other persons with whom they share a common FriendGroupID
436 foreach (Person person in _personList)
437 {
438 // Make sure the friend group IDs are sorted ascending
439 person.FriendGroupIDs.Sort((a, b) => a.CompareTo(b));
440
441 person.Friends?.Clear();
442
443 // Skip persons without FriendGroupIDs
444 if (person.FriendGroupIDs == null || person.FriendGroupIDs.Count == 0) { continue; }
445
446 List<Person> friends = _personList.Where(p => p != person &&
447 p.FriendGroupIDs != null &&
448 p.FriendGroupIDs.Intersect(person.FriendGroupIDs).Any()).ToList();
449 person.Friends?.AddRange(friends);
450 }
451
452 foreach (RacesVariant racesVariant in _raceService.AllRacesVariants)
453 {
454 racesVariant.CalculateScore();
455 }
456 }
457
461 //public int NumberFriendGroups => _personList?.Sum(p => p.FriendGroupIDs.Count) ?? 0;
462 public int NumberFriendGroups => _personList?.SelectMany(p => p.FriendGroupIDs)?.Distinct().Count() ?? 0;
463
464 #endregion
465
466 }
467}
Comparer that only uses the most basic properties of a Person to determine equality:
bool Equals(Person x, Person y)
Check if two Person objects are equal based on basic properties.
Class describing a person.
Definition Person.cs:12
string FirstName
First name of the person.
Definition Person.cs:77
ObservableCollection< int > FriendGroupIDs
IDs of the friend groups the person belongs to.
Definition Person.cs:497
List< Person > Friends
Friends of the person.
Definition Person.cs:525
UInt16 BirthYear
Birth year of the person.
Definition Person.cs:99
string Name
Last name of the person.
Definition Person.cs:66
Genders Gender
Gender of the person.
Definition Person.cs:88
Dictionary< SwimmingStyles, PersonStart > Starts
Dictionary with all starts of the person.
Definition Person.cs:201
static void SetPropertyFromString(Person dataObj, string propertyName, string value)
Set the requested property in the Person object by parsing the given string value.
Definition Person.cs:547
Class describing a start of a person.
Definition PersonStart.cs:9
Class that represents a combination variant of all single races.
double CalculateScore()
Recalculate all scores.
bool HasUnsavedChanges
Check if the list of Person has not saved changed.
ObservableCollection< Person > GetPersons()
Return all available Persons.
List< PersonStart > GetAllPersonStarts(PersonStartFilters filter=PersonStartFilters.None, object filterParameter=null, bool onlyValidStarts=false)
Get all PersonStart objects for all Person objects that are not null.
void SetCompetitionServiceObj(ICompetitionService competitionService)
Save the reference to the ICompetitionService object.
ObservableCollection< Person > _personList
List with all people.
int PersonStarts
Return the total number of starts of all persons.
EventHandler OnFileFinished
Event that is raised when the file operation is finished.
void SetScoreServiceObj(IScoreService scoreService)
Save the reference to the IScoreService object.
async Task< bool > Save(CancellationToken cancellationToken, string path="")
Save the list of Persons to a file.
List< Person > _personListOnLoad
List with all people at the time the Load(string, CancellationToken) method was called.
void ResetToLoadedState()
Reset the list of Persons to the state when the Load(string, CancellationToken) method was called.
ProgressDelegate OnFileProgress
Event that is raised when the file operation progress changes.
int NumberFriendGroups
Return the number of friend groups.
void SetRaceServiceObj(IRaceService raceService)
Save the reference to the IRaceService object.
async Task< bool > Load(string path, CancellationToken cancellationToken)
Load a list of Persons to the _personList.
PersonService(IFileService fileService)
Constructor.
void UpdateAllFriendReferencesFromFriendGroupIDs()
Loop all Person objects and update their friend references (Person.Friends) from the friend group IDs...
string PersistentPath
Path to the person file.
void AddPerson(Person person)
Add a new Person to the list of Persons.
void RemovePerson(Person person)
Remove the given Person from the list of Persons.
void UpdateHasDuplicatesForPersons()
Find all duplicate Person objects and update the Person.HasDuplicates flags.
void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
Event that is raised when a property of a Person changes.
Interface for a service used to get and store a list of objects.
Interface for a service that handles file operations.
Interface for a service used to get and store a list of Person objects.
Interface for a service used to manage Race and RacesVariant objects.
Interface for a service used to calculate the scores for all persons.
delegate void ProgressDelegate(object sender, float progress, string currentStep="")
Delegate void for progress changes.
SwimmingStyles
Available swimming styles.
PersonStartFilters
Available filters for PersonStart objects.