1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2021 by Sergey V. Zhdanovskih.
4  *
5  *  This file is part of "GEDKeeper".
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 using System;
22 using GDModel.Providers.GEDCOM;
23 using GKCore.Types;
24 
25 namespace GDModel
26 {
27     // Standard: [M]Male / [F]Female / [U]Unknown (gedcom-5.5.1, p.61)
28     // Tamura Jones: +[X]Intersex (GEDCOM 5.5.1 Specification Annotated Edition)
29     public enum GDMSex
30     {
31         svUnknown,
32         svMale,
33         svFemale,
34         svIntersex,
35 
36         svLast = svFemale
37     }
38 
39 
40     public sealed class GDMIndividualRecord : GDMRecordWithEvents, IGDMIndividualRecord
41     {
42         private GDMList<GDMAssociation> fAssociations;
43         private GDMList<GDMChildToFamilyLink> fChildToFamilyLinks;
44         private GDMList<GDMPointer> fGroups;
45         private GDMList<GDMPersonalName> fPersonalNames;
46         private GDMList<GDMSpouseToFamilyLink> fSpouseToFamilyLinks;
47         private GDMSex fSex;
48 
49 
50         public bool HasAssociations
51         {
52             get { return fAssociations != null && fAssociations.Count != 0; }
53         }
54 
55         public GDMList<GDMAssociation> Associations
56         {
57             get {
58                 if (fAssociations == null) {
59                     fAssociations = new GDMList<GDMAssociation>();
60                 }
61 
62                 return fAssociations;
63             }
64         }
65 
66         public bool Bookmark
67         {
68             get {
69                 return FindTag(GEDCOMTagName._BOOKMARK, 0) != null;
70             }
71             set {
72                 if (value) {
73                     if (FindTag(GEDCOMTagName._BOOKMARK, 0) == null) {
74                         AddTag(new GDMValueTag((int)GEDCOMTagType._BOOKMARK, ""));
75                     }
76                 } else {
77                     DeleteTag(GEDCOMTagName._BOOKMARK);
78                 }
79             }
80         }
81 
82         public GDMList<GDMChildToFamilyLink> ChildToFamilyLinks
83         {
84             get { return fChildToFamilyLinks; }
85         }
86 
87         public bool HasGroups
88         {
89             get { return fGroups != null && fGroups.Count != 0; }
90         }
91 
92         public GDMList<GDMPointer> Groups
93         {
94             get {
95                 if (fGroups == null) {
96                     fGroups = new GDMList<GDMPointer>();
97                 }
98 
99                 return fGroups;
100             }
101         }
102 
103         public bool Patriarch
104         {
105             get {
106                 return FindTag(GEDCOMTagName._PATRIARCH, 0) != null;
107             }
108             set {
109                 if (value) {
110                     if (FindTag(GEDCOMTagName._PATRIARCH, 0) == null) {
111                         AddTag(new GDMValueTag((int)GEDCOMTagType._PATRIARCH, ""));
112                     }
113                 } else {
114                     DeleteTag(GEDCOMTagName._PATRIARCH);
115                 }
116             }
117         }
118 
119         public GDMList<GDMPersonalName> PersonalNames
120         {
121             get { return fPersonalNames; }
122         }
123 
124         public GDMSex Sex
125         {
126             get { return fSex; }
127             set { fSex = value; }
128         }
129 
130         public GDMList<GDMSpouseToFamilyLink> SpouseToFamilyLinks
131         {
132             get { return fSpouseToFamilyLinks; }
133         }
134 
135 
GDMIndividualRecord(GDMTree tree)136         public GDMIndividualRecord(GDMTree tree) : base(tree)
137         {
138             SetName(GEDCOMTagType.INDI);
139 
140             fChildToFamilyLinks = new GDMList<GDMChildToFamilyLink>();
141             fPersonalNames = new GDMList<GDMPersonalName>();
142             fSpouseToFamilyLinks = new GDMList<GDMSpouseToFamilyLink>();
143         }
144 
Dispose(bool disposing)145         protected override void Dispose(bool disposing)
146         {
147             if (disposing) {
148                 if (fAssociations != null) fAssociations.Dispose();
149                 fChildToFamilyLinks.Dispose();
150                 if (fGroups != null) fGroups.Dispose();
151                 fPersonalNames.Dispose();
152                 fSpouseToFamilyLinks.Dispose();
153             }
154             base.Dispose(disposing);
155         }
156 
TrimExcess()157         internal override void TrimExcess()
158         {
159             base.TrimExcess();
160 
161             if (fAssociations != null) fAssociations.TrimExcess();
162             fChildToFamilyLinks.TrimExcess();
163             if (fGroups != null) fGroups.TrimExcess();
164             fPersonalNames.TrimExcess();
165             fSpouseToFamilyLinks.TrimExcess();
166         }
167 
AddEvent(GDMCustomEvent evt)168         public override GDMCustomEvent AddEvent(GDMCustomEvent evt)
169         {
170             if (evt != null) {
171                 if (evt is GDMIndividualEvent || evt is GDMIndividualAttribute) {
172                     Events.Add(evt);
173                 } else {
174                     throw new ArgumentException(@"Event has the invalid type", "evt");
175                 }
176             }
177 
178             return evt;
179         }
180 
AddPersonalName(GDMPersonalName value)181         public GDMPersonalName AddPersonalName(GDMPersonalName value)
182         {
183             if (value != null) {
184                 fPersonalNames.Add(value);
185             }
186             return value;
187         }
188 
Clear()189         public override void Clear()
190         {
191             base.Clear();
192 
193             fSex = GDMSex.svUnknown;
194 
195             for (int i = fChildToFamilyLinks.Count - 1; i >= 0; i--) {
196                 var family = fTree.GetPtrValue<GDMFamilyRecord>(fChildToFamilyLinks[i]);
197                 family.DeleteChild(this);
198             }
199             fChildToFamilyLinks.Clear();
200 
201             for (int i = fSpouseToFamilyLinks.Count - 1; i >= 0; i--) {
202                 var family = fTree.GetPtrValue<GDMFamilyRecord>(fSpouseToFamilyLinks[i]);
203                 family.RemoveSpouse(this);
204             }
205             fSpouseToFamilyLinks.Clear();
206 
207             if (fGroups != null) {
208                 for (int i = fGroups.Count - 1; i >= 0; i--) {
209                     var group = fTree.GetPtrValue<GDMGroupRecord>(fGroups[i]);
210                     group.RemoveMember(this);
211                 }
212                 fGroups.Clear();
213             }
214 
215             if (fAssociations != null) fAssociations.Clear();
216 
217             fPersonalNames.Clear();
218         }
219 
IsEmpty()220         public override bool IsEmpty()
221         {
222             return base.IsEmpty() && (fSex == GDMSex.svUnknown) && (fPersonalNames.Count == 0)
223                 && (fChildToFamilyLinks.Count == 0) && (fSpouseToFamilyLinks.Count == 0)
224                 && (fAssociations == null || fAssociations.Count == 0)
225                 && (fGroups == null || fGroups.Count == 0);
226         }
227 
IndexOfGroup(GDMGroupRecord groupRec)228         public int IndexOfGroup(GDMGroupRecord groupRec)
229         {
230             if (groupRec != null && fGroups != null) {
231                 int num = fGroups.Count;
232                 for (int i = 0; i < num; i++) {
233                     if (fGroups[i].XRef == groupRec.XRef) {
234                         return i;
235                     }
236                 }
237             }
238 
239             return -1;
240         }
241 
IndexOfSpouse(GDMFamilyRecord familyRec)242         public int IndexOfSpouse(GDMFamilyRecord familyRec)
243         {
244             if (familyRec != null) {
245                 int num = fSpouseToFamilyLinks.Count;
246                 for (int i = 0; i < num; i++) {
247                     if (fSpouseToFamilyLinks[i].XRef == familyRec.XRef) {
248                         return i;
249                     }
250                 }
251             }
252 
253             return -1;
254         }
255 
DeleteSpouseToFamilyLink(GDMFamilyRecord familyRec)256         public void DeleteSpouseToFamilyLink(GDMFamilyRecord familyRec)
257         {
258             if (familyRec == null) return;
259 
260             int num = fSpouseToFamilyLinks.Count;
261             for (int i = 0; i < num; i++) {
262                 if (fSpouseToFamilyLinks[i].XRef == familyRec.XRef) {
263                     fSpouseToFamilyLinks.DeleteAt(i);
264                     break;
265                 }
266             }
267         }
268 
DeleteChildToFamilyLink(GDMFamilyRecord familyRec)269         public void DeleteChildToFamilyLink(GDMFamilyRecord familyRec)
270         {
271             if (familyRec == null) return;
272 
273             int num = fChildToFamilyLinks.Count;
274             for (int i = 0; i < num; i++) {
275                 if (fChildToFamilyLinks[i].XRef == familyRec.XRef) {
276                     fChildToFamilyLinks.DeleteAt(i);
277                     break;
278                 }
279             }
280         }
281 
FindChildToFamilyLink(GDMFamilyRecord familyRec)282         public GDMChildToFamilyLink FindChildToFamilyLink(GDMFamilyRecord familyRec)
283         {
284             if (familyRec == null) return null;
285 
286             for (int i = 0, num = fChildToFamilyLinks.Count; i < num; i++) {
287                 var childLink = fChildToFamilyLinks[i];
288                 if (childLink.XRef == familyRec.XRef) {
289                     return childLink;
290                 }
291             }
292 
293             return null;
294         }
295 
ExchangeSpouses(int index1, int index2)296         public void ExchangeSpouses(int index1, int index2)
297         {
298             fSpouseToFamilyLinks.Exchange(index1, index2);
299         }
300 
IsLive()301         public bool IsLive()
302         {
303             return FindEvent(GEDCOMTagType.DEAT) == null;
304         }
305 
Assign(GDMTag source)306         public override void Assign(GDMTag source)
307         {
308             GDMIndividualRecord sourceRec = source as GDMIndividualRecord;
309             if (sourceRec == null)
310                 throw new ArgumentException(@"Argument is null or wrong type", "source");
311 
312             base.Assign(source);
313 
314             fSex = sourceRec.fSex;
315 
316             foreach (GDMPersonalName srcName in sourceRec.fPersonalNames) {
317                 GDMPersonalName copyName = new GDMPersonalName();
318                 copyName.Assign(srcName);
319                 AddPersonalName(copyName);
320             }
321         }
322 
MoveTo(GDMRecord targetRecord)323         public override void MoveTo(GDMRecord targetRecord)
324         {
325             GDMIndividualRecord targetIndi = targetRecord as GDMIndividualRecord;
326             if (targetIndi == null) {
327                 throw new ArgumentException(@"Argument is null or wrong type", "targetRecord");
328             }
329 
330             base.MoveTo(targetRecord);
331 
332             targetIndi.Sex = fSex;
333 
334             while (fPersonalNames.Count > 0) {
335                 GDMPersonalName obj = fPersonalNames.Extract(0);
336                 targetIndi.AddPersonalName(obj);
337             }
338 
339             string currentXRef = this.XRef;
340             string targetXRef = targetRecord.XRef;
341 
342             while (fChildToFamilyLinks.Count > 0) {
343                 GDMChildToFamilyLink ctfLink = fChildToFamilyLinks.Extract(0);
344                 var family = fTree.GetPtrValue<GDMFamilyRecord>(ctfLink);
345 
346                 int num = family.Children.Count;
347                 for (int i = 0; i < num; i++) {
348                     GDMIndividualLink childPtr = family.Children[i];
349 
350                     if (childPtr.XRef == currentXRef) {
351                         childPtr.XRef = targetXRef;
352                     }
353                 }
354 
355                 targetIndi.ChildToFamilyLinks.Add(ctfLink);
356             }
357 
358             while (fSpouseToFamilyLinks.Count > 0) {
359                 GDMSpouseToFamilyLink stfLink = fSpouseToFamilyLinks.Extract(0);
360                 var family = fTree.GetPtrValue<GDMFamilyRecord>(stfLink);
361 
362                 if (family.Husband.XRef == currentXRef) {
363                     family.Husband.XRef = targetXRef;
364                 } else if (family.Wife.XRef == currentXRef) {
365                     family.Wife.XRef = targetXRef;
366                 }
367 
368                 targetIndi.SpouseToFamilyLinks.Add(stfLink);
369             }
370 
371             while (fAssociations != null && fAssociations.Count > 0) {
372                 GDMAssociation obj = fAssociations.Extract(0);
373                 targetIndi.Associations.Add(obj);
374             }
375 
376             while (fGroups != null && fGroups.Count > 0) {
377                 GDMPointer obj = fGroups.Extract(0);
378                 targetIndi.Groups.Add(obj);
379             }
380         }
381 
ReplaceXRefs(GDMXRefReplacer map)382         public override void ReplaceXRefs(GDMXRefReplacer map)
383         {
384             base.ReplaceXRefs(map);
385 
386             if (fAssociations != null) fAssociations.ReplaceXRefs(map);
387             fChildToFamilyLinks.ReplaceXRefs(map);
388             if (fGroups != null) fGroups.ReplaceXRefs(map);
389             fPersonalNames.ReplaceXRefs(map);
390             fSpouseToFamilyLinks.ReplaceXRefs(map);
391         }
392 
393         public sealed class LifeDatesRet
394         {
395             public readonly GDMCustomEvent BirthEvent;
396             public readonly GDMCustomEvent DeathEvent;
397 
LifeDatesRet(GDMCustomEvent birthEvent, GDMCustomEvent deathEvent)398             public LifeDatesRet(GDMCustomEvent birthEvent, GDMCustomEvent deathEvent)
399             {
400                 BirthEvent = birthEvent;
401                 DeathEvent = deathEvent;
402             }
403         }
404 
GetLifeDates()405         public LifeDatesRet GetLifeDates()
406         {
407             GDMCustomEvent birthEvent = null;
408             GDMCustomEvent deathEvent = null;
409 
410             int num = Events.Count;
411             for (int i = 0; i < num; i++) {
412                 GDMCustomEvent evt = Events[i];
413                 var evtType = evt.GetTagType();
414 
415                 if (evtType == GEDCOMTagType.BIRT && birthEvent == null) {
416                     birthEvent = evt;
417                 } else if (evtType == GEDCOMTagType.DEAT && deathEvent == null) {
418                     deathEvent = evt;
419                 }
420             }
421 
422             return new LifeDatesRet(birthEvent, deathEvent);
423         }
424 
GetPrimaryFullName()425         public string GetPrimaryFullName()
426         {
427             string result = (fPersonalNames.Count <= 0) ? string.Empty : fPersonalNames[0].FullName;
428             return result;
429         }
430 
GetComparableName(bool onlyFirstPart)431         private string GetComparableName(bool onlyFirstPart)
432         {
433             string resName;
434 
435             if (fPersonalNames.Count > 0) {
436                 GDMPersonalName np = fPersonalNames[0];
437 
438                 if (onlyFirstPart) {
439                     resName = np.FirstPart;
440                 } else {
441                     resName = np.StringValue;
442                 }
443             } else {
444                 resName = "";
445             }
446 
447             return resName;
448         }
449 
IsMatch(GDMTag tag, MatchParams matchParams)450         public override float IsMatch(GDMTag tag, MatchParams matchParams)
451         {
452             GDMIndividualRecord indi = tag as GDMIndividualRecord;
453             if (indi == null) return 0.0f;
454 
455             if (Sex != indi.Sex) return 0.0f;
456 
457             bool womanMode = (Sex == GDMSex.svFemale);
458 
459             float matchesCount = 0.0f;
460             float nameMatch = 0.0f;
461             float birthMatch = 0.0f;
462             float deathMatch = 0.0f;
463 
464             // check name
465             /*for (int i = 0; i < indi.PersonalNames.Count; i++)
466 			{
467 				for (int k = 0; k < fPersonalNames.Count; k++)
468 				{
469 					float currentNameMatch = fPersonalNames[k].IsMatch(indi.PersonalNames[i]);
470 					nameMatch = Math.Max(nameMatch, currentNameMatch);
471 				}
472 			}*/
473 
474             string iName = GetComparableName(womanMode);
475             string kName = indi.GetComparableName(womanMode);
476 
477             if (!string.IsNullOrEmpty(iName) && !string.IsNullOrEmpty(kName)) {
478                 nameMatch = GetStrMatch(iName, kName, matchParams);
479                 matchesCount++;
480             }
481 
482             // 0% name match would be pointless checking other details
483             if (nameMatch != 0.0f && matchParams.DatesCheck)
484             {
485                 var dates = GetLifeDates();
486                 var indiDates = indi.GetLifeDates();
487 
488                 if (dates.BirthEvent != null && indiDates.BirthEvent != null) {
489                     birthMatch = dates.BirthEvent.IsMatch(indiDates.BirthEvent, matchParams);
490                     matchesCount++;
491                 } else if (dates.BirthEvent == null && indiDates.BirthEvent == null) {
492                     birthMatch = 100.0f;
493                     matchesCount++;
494                 } else {
495                     matchesCount++;
496                 }
497 
498                 /*if (death != null && indiDeath != null) {
499 					deathMatch = death.IsMatch(indiDeath, matchParams);
500 					matches++;
501 				} else if (death == null && indiDeath == null) {
502 					deathMatch = 100.0f;
503 					matches++;
504 				} else {
505 					matches++;
506 				}*/
507             }
508 
509             float match = (nameMatch + birthMatch + deathMatch) / matchesCount;
510             return match;
511         }
512 
AddAssociation(string relation, GDMIndividualRecord relPerson)513         public GDMAssociation AddAssociation(string relation, GDMIndividualRecord relPerson)
514         {
515             GDMAssociation result = new GDMAssociation();
516             result.Relation = relation;
517             result.XRef = (relPerson == null) ? string.Empty : relPerson.XRef;
518             Associations.Add(result);
519             return result;
520         }
521 
SetPrimaryMultimediaLink(GDMMultimediaRecord mediaRec)522         public GDMMultimediaLink SetPrimaryMultimediaLink(GDMMultimediaRecord mediaRec)
523         {
524             if (mediaRec == null) return null;
525             GDMMultimediaLink mmLink = null;
526 
527             if (HasMultimediaLinks) {
528                 int num = MultimediaLinks.Count;
529                 for (int i = 0; i < num; i++) {
530                     GDMMultimediaLink lnk = MultimediaLinks[i];
531 
532                     if (lnk.XRef == mediaRec.XRef) {
533                         mmLink = lnk;
534                         break;
535                     }
536                 }
537             }
538 
539             if (mmLink == null) {
540                 mmLink = this.AddMultimedia(mediaRec);
541             }
542 
543             mmLink.IsPrimary = true;
544             return mmLink;
545         }
546 
GetPrimaryMultimediaLink()547         public GDMMultimediaLink GetPrimaryMultimediaLink()
548         {
549             GDMMultimediaLink result = null;
550             if (!HasMultimediaLinks) return result;
551 
552             int num = MultimediaLinks.Count;
553             for (int i = 0; i < num; i++) {
554                 GDMMultimediaLink mmLink = MultimediaLinks[i];
555                 if (mmLink.IsPrimary) {
556                     result = mmLink;
557                     break;
558                 }
559             }
560 
561             return result;
562         }
563     }
564 }
565