001package org.hl7.fhir.r5.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.r5.model.Base;
013import org.hl7.fhir.r5.model.CodeSystem;
014import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
015import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
016import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
017import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
018import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
019import org.hl7.fhir.utilities.Utilities;
020import org.hl7.fhir.utilities.i18n.RenderingI18nContext;
021import org.hl7.fhir.utilities.validation.ValidationMessage;
022import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
023import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
024import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
025import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
026import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
027import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
028import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
029import org.hl7.fhir.utilities.xhtml.XhtmlNode;
030
031public class CodeSystemComparer extends CanonicalResourceComparer {
032
033
034  public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> {
035
036    private StructuralMatch<PropertyComponent> properties = new StructuralMatch<PropertyComponent>(); 
037    private StructuralMatch<CodeSystemFilterComponent> filters = new StructuralMatch<CodeSystemFilterComponent>();                                             
038    private StructuralMatch<ConceptDefinitionComponent> combined;                                             
039    private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
040    public CodeSystemComparison(CodeSystem left, CodeSystem right) {
041      super(left, right);
042      combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base
043    }
044
045    public Map<String, String> getPropMap() {
046      return propMap;
047    }
048
049    public StructuralMatch<ConceptDefinitionComponent> getCombined() {
050      return combined;
051    }
052
053    public StructuralMatch<PropertyComponent> getProperties() {
054      return properties;
055    }
056
057    public StructuralMatch<CodeSystemFilterComponent> getFilters() {
058      return filters;
059    }
060
061    @Override
062    protected String abbreviation() {
063      return "cs";
064    }
065
066    @Override
067    protected String summary() {
068      String res = "CodeSystem: "+left.present()+" vs "+right.present();
069      String ch = changeSummary();
070      if (ch != null) {
071        res = res + ". "+ch;
072      }
073      return res;
074    }
075
076
077    @Override
078    protected String fhirType() {
079      return "CodeSystem";
080    }
081
082    @Override
083    protected void countMessages(MessageCounts cnts) {
084      super.countMessages(cnts);
085      combined.countMessages(cnts);
086    }
087
088  }
089
090  private CodeSystem right;
091
092  public CodeSystemComparer(ComparisonSession session) {
093    super(session);
094  }
095
096  public CodeSystemComparison compare(CodeSystem left, CodeSystem right) {    
097    if (left == null)
098      throw new DefinitionException("No CodeSystem provided (left)");
099    if (right == null)
100      throw new DefinitionException("No CodeSystem provided (right)");
101    
102    CodeSystemComparison res = new CodeSystemComparison(left, right);
103    session.identify(res);
104    CodeSystem cs = new CodeSystem();
105    res.setUnion(cs);
106    session.identify(cs);
107    cs.setName("Union"+left.getName()+"And"+right.getName());
108    cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
109    cs.setStatus(left.getStatus());
110    cs.setDate(new Date());
111    for (PropertyComponent pL : left.getProperty()) {
112      cs.addProperty(pL.copy());
113    }
114    for (PropertyComponent pR : left.getProperty()) {
115      PropertyComponent pL = findProperty(left, pR);
116      if (pL == null) {
117        String code = getUniqued(pR.getCode(), cs.getProperty());
118        cs.addProperty(pR.copy().setCode(code));
119      } else {
120        res.getPropMap().put(pR.getCode(), pL.getCode());
121      }
122    }
123
124    CodeSystem cs1 = new CodeSystem();
125    res.setIntersection(cs1);
126    session.identify(cs1);
127    cs1.setName("Intersection"+left.getName()+"And"+right.getName());
128    cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
129    cs1.setStatus(left.getStatus());
130    cs1.setDate(new Date());
131    cs1.getProperty().addAll(cs.getProperty());
132
133
134    List<String> chMetadata = new ArrayList<>();
135    boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
136    if (comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res)) {
137      ch = true;
138      chMetadata.add("versionNeeded");
139    }
140    if (comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
141      ch = true;
142      chMetadata.add("compositional");
143    }
144    res.updatedMetadataState(ch, chMetadata);
145    ch = false;
146    ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
147    ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
148    ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right);
149    
150    ch = compareProperties(left.getProperty(), right.getProperty(), res.getProperties(), res.getUnion().getProperty(), res.getIntersection().getProperty(), res.getUnion(), res.getIntersection(), res, "CodeSystem.property", right) || ch;
151    ch = compareFilters(left.getFilter(), right.getFilter(), res.getFilters(), res.getUnion().getFilter(), res.getIntersection().getFilter(), res.getUnion(), res.getIntersection(), res, "CodeSystem.filter", right) || ch;
152    ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept", right) || ch;
153    res.updateDefinitionsState(ch);
154
155    session.annotate(right, res);
156    return res;
157  }
158
159  private String getUniqued(String code, List<PropertyComponent> list) {
160    int i = 0;
161    while (true) {
162      boolean ok = true;
163      String res = code+(i == 0 ? "" : i);
164      for (PropertyComponent t : list) {
165        if (res.equals(t.getCode())) {
166          ok = false;
167        }
168      }
169      if (ok) {
170        return res;
171      }
172    }
173  }
174
175  private PropertyComponent findProperty(CodeSystem left, PropertyComponent p) {
176    for (PropertyComponent t : left.getProperty()) {
177      if (p.hasUri() && t.hasUri() && p.getUri().equals(t.getUri())) {
178        return t;
179      } else if (!p.hasUri() && !t.hasUri() && p.getCode().equals(t.getCode())) {
180        return t;
181      }
182    }
183    return null;
184  }
185
186  private boolean compareProperties(List<PropertyComponent> left, List<PropertyComponent> right, StructuralMatch<PropertyComponent> combined,
187    List<PropertyComponent> union, List<PropertyComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
188    boolean def = false;
189    List<PropertyComponent> matchR = new ArrayList<>();
190    for (PropertyComponent l : left) {
191      PropertyComponent r = findInList(right, l);
192      if (r == null) {
193        union.add(l);
194        res.updateContentState(true);
195        combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
196        session.markDeleted(parent, "concept", l);
197      } else {
198        matchR.add(r);
199        PropertyComponent cdM = merge(l, r, res);
200        PropertyComponent cdI = intersect(l, r, res);
201        union.add(cdM);
202        intersection.add(cdI);
203        StructuralMatch<PropertyComponent> sm = new StructuralMatch<CodeSystem.PropertyComponent>(l, r);
204        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
205          def = true;
206        }
207        combined.getChildren().add(sm);
208      }
209    }
210    for (PropertyComponent r : right) {
211      if (!matchR.contains(r)) {
212        union.add(r);
213        res.updateContentState(true);
214        combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
215        session.markAdded(r);     
216      }
217    }
218    return def;
219  }
220
221
222  private boolean compareFilters(List<CodeSystemFilterComponent> left, List<CodeSystemFilterComponent> right, StructuralMatch<CodeSystemFilterComponent> combined,
223    List<CodeSystemFilterComponent> union, List<CodeSystemFilterComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
224    boolean def = false;
225    List<CodeSystemFilterComponent> matchR = new ArrayList<>();
226    for (CodeSystemFilterComponent l : left) {
227      CodeSystemFilterComponent r = findInList(right, l);
228      if (r == null) {
229        union.add(l);
230        res.updateContentState(true);
231        combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
232        session.markDeleted(parent, "concept", l);
233      } else {
234        matchR.add(r);
235        CodeSystemFilterComponent cdM = merge(l, r, res);
236        CodeSystemFilterComponent cdI = intersect(l, r, res);
237        union.add(cdM);
238        intersection.add(cdI);
239        StructuralMatch<CodeSystemFilterComponent> sm = new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, r);
240        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
241          def = true;
242        }
243        combined.getChildren().add(sm);
244      }
245    }
246    for (CodeSystemFilterComponent r : right) {
247      if (!matchR.contains(r)) {
248        union.add(r);
249        res.updateContentState(true);
250        combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
251        session.markAdded(r);     
252      }
253    }
254    return def;
255  }
256
257  
258  private boolean compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined,
259    List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) {
260    boolean def = false;
261    List<ConceptDefinitionComponent> matchR = new ArrayList<>();
262    for (ConceptDefinitionComponent l : left) {
263      ConceptDefinitionComponent r = findInList(right, l);
264      if (r == null) {
265        union.add(l);
266        res.updateContentState(true);
267        combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
268        session.markDeleted(parent, "concept", l);
269      } else {
270        matchR.add(r);
271        ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res);
272        ConceptDefinitionComponent cdI = intersect(l, r, res);
273        union.add(cdM);
274        intersection.add(cdI);
275        StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r);
276        if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) {
277          def = true;
278        }
279        combined.getChildren().add(sm);
280        if (compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept", r)) {
281          def = true;
282        }
283      }
284    }
285    for (ConceptDefinitionComponent r : right) {
286      if (!matchR.contains(r)) {
287        union.add(r);
288        res.updateContentState(true);
289        combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));    
290        session.markAdded(r);     
291      }
292    }
293    return def;
294  }
295  
296  private CodeSystemFilterComponent findInList(List<CodeSystemFilterComponent> list, CodeSystemFilterComponent item) {
297    for (CodeSystemFilterComponent t : list) {
298      if (t.getCode().equals(item.getCode())) {
299        return t;
300      }
301    }
302    return null;
303  }
304
305
306  private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) {
307    for (ConceptDefinitionComponent t : list) {
308      if (t.getCode().equals(item.getCode())) {
309        return t;
310      }
311    }
312    return null;
313  }
314
315
316  private PropertyComponent findInList(List<PropertyComponent> list, PropertyComponent item) {
317    for (PropertyComponent t : list) {
318      if (t.getCode().equals(item.getCode())) {
319        return t;
320      }
321    }
322    return null;
323  }
324
325  private boolean compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res, Base parent) {
326    boolean result = false;
327    result = compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res, parent, l.getDisplayElement(), r.getDisplayElement()) || result;
328    result = compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res, parent, l.getDefinitionElement(), r.getDefinitionElement()) || result;
329    // todo: designations, properties
330    return result;
331  }
332
333  private boolean compare(List<ValidationMessage> msgs, PropertyComponent l, PropertyComponent r, String path, CodeSystemComparison res, Base parent) {
334    boolean result = false;
335    result = compareStrings(path, msgs, l.getUri(), r.getUri(), "uri", IssueSeverity.WARNING, res, parent, l.getUriElement(), r.getUriElement()) || result;
336    result = compareStrings(path, msgs, l.hasType() ? l.getType().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result;
337    result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result;
338    return result;
339  }
340
341  private boolean compare(List<ValidationMessage> msgs, CodeSystemFilterComponent l, CodeSystemFilterComponent r, String path, CodeSystemComparison res, Base parent) {
342    boolean result = false;
343    result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result;
344// todo: repeating
345//    result = compareStrings(path, msgs, l.hasOperator() ? l.getOperator().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result;
346    result = compareStrings(path, msgs, l.getValue(), r.getValue(), "value", IssueSeverity.WARNING, res, parent, l.getValueElement(), r.getValueElement()) || result;
347    return result;
348  }
349
350  private boolean compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res, Base parent, Base l, Base r) {
351    if (!Utilities.noString(right)) {
352      if (Utilities.noString(left)) {
353        msgs.add(vmI(level, "Value for "+name+" added", path));   
354        session.markAdded(r);     
355        return true;
356      } else if (!left.equals(right)) {
357        if (level != IssueSeverity.NULL) {
358          res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
359        }
360        msgs.add(vmI(level, name+" changed from left to right", path));
361        session.markChanged(r, l);   
362        return true;
363      }
364    } else if (!Utilities.noString(left)) {
365      msgs.add(vmI(level, "Value for "+name+" removed", path));
366      session.markDeleted(parent, "concept", l);
367      return true;
368    }
369    return false;
370  }
371
372  private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
373    ConceptDefinitionComponent cd = l.copy();
374    if (!l.hasDisplay() && r.hasDisplay()) {
375      cd.setDisplay(r.getDisplay());
376    }
377    if (!l.hasDefinition() && r.hasDefinition()) {
378      cd.setDefinition(r.getDefinition());
379    }
380    mergeProps(cd, l, r, destProps, res);
381    mergeDesignations(cd, l, r);
382    return cd;
383  }
384
385  private PropertyComponent merge(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) {
386    PropertyComponent cd = l.copy();
387    if (!l.hasDescription() && r.hasDescription()) {
388      cd.setDescription(r.getDescription());
389    }
390    return cd;
391  }
392
393
394  private CodeSystemFilterComponent merge(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) {
395    CodeSystemFilterComponent cd = l.copy();
396    if (!l.hasDescription() && r.hasDescription()) {
397      cd.setDescription(r.getDescription());
398    }
399    return cd;
400  }
401
402  private ConceptDefinitionComponent intersect(ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
403    ConceptDefinitionComponent cd = l.copy();
404    if (l.hasDisplay() && !r.hasDisplay()) {
405      cd.setDisplay(null);
406    }
407    if (l.hasDefinition() && !r.hasDefinition()) {
408      cd.setDefinition(null);
409    }
410    intersectProps(cd, l, r, res);
411    //    mergeDesignations(cd, l, r);
412    return cd;
413  }
414
415  private PropertyComponent intersect(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) {
416    PropertyComponent cd = l.copy();
417    if (l.hasDescription() && !r.hasDescription()) {
418      cd.setDescription(null);
419    }
420    return cd;
421  }
422
423  private CodeSystemFilterComponent intersect(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) {
424    CodeSystemFilterComponent cd = l.copy();
425    if (l.hasDescription() && !r.hasDescription()) {
426      cd.setDescription(null);
427    }
428    return cd;
429  }
430
431  private void mergeDesignations(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r) {
432    for (ConceptDefinitionDesignationComponent td : l.getDesignation()) {
433      if (hasDesignation(td, r.getDesignation())) {
434        cd.getDesignation().add(td);
435      }
436    }
437    for (ConceptDefinitionDesignationComponent td : r.getDesignation()) {
438      if (hasDesignation(td, l.getDesignation())) {
439        cd.getDesignation().add(td);
440      }
441    }
442  }
443
444  private boolean hasDesignation(ConceptDefinitionDesignationComponent td, List<ConceptDefinitionDesignationComponent> designation) {
445    for (ConceptDefinitionDesignationComponent t : designation) {
446      if (designationsMatch(td, t)) {
447        return true;
448      }
449    }
450    return false;
451  }
452
453  private boolean designationsMatch(ConceptDefinitionDesignationComponent l, ConceptDefinitionDesignationComponent r) {
454    if (l.hasUse() != r.hasUse()) {
455      return false;
456    }
457    if (l.hasLanguage() != r.hasLanguage()) {
458      return false;
459    }
460    if (l.hasValue() != r.hasValue()) {
461      return false;
462    }
463    if (l.hasUse()) {
464      if (l.getUse().equalsDeep(r.getUse())) {
465        return false;
466      }
467    }
468    if (l.hasLanguage()) {
469      if (l.getLanguageElement().equalsDeep(r.getLanguageElement())) {
470        return false;
471      }
472    }
473    if (l.hasValue()) {
474      if (l.getValueElement().equalsDeep(r.getValueElement())) {
475        return false;
476      }
477    }
478    return true;
479  }
480
481  private void mergeProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
482    List<ConceptPropertyComponent> matchR = new ArrayList<>();
483    for (ConceptPropertyComponent lp : l.getProperty()) {
484      ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
485      if (rp == null) {
486        cd.getProperty().add(lp);
487      } else {
488        matchR.add(rp);
489        cd.getProperty().add(lp);
490        if (lp.getValue().equalsDeep(rp.getValue())) {
491          cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
492        }
493      }
494    }
495    for (ConceptPropertyComponent rp : r.getProperty()) {
496      if (!matchR.contains(rp)) {
497        cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));        
498      }
499    }
500  }
501
502  private void intersectProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
503    for (ConceptPropertyComponent lp : l.getProperty()) {
504      ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
505      if (rp != null) {
506        cd.getProperty().add(lp);
507      }
508    }
509  }
510
511  private ConceptPropertyComponent findRightProp(List<ConceptPropertyComponent> rightProperties, ConceptPropertyComponent lp, CodeSystemComparison res) {
512    for (ConceptPropertyComponent p : rightProperties) {
513      String rp = res.getPropMap().get(p.getCode());
514      if (rp != null && rp.equals(lp.getCode())) {
515        return p;
516      }
517    }
518    return null;
519  }
520
521  public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException {
522    // columns: code, display (left|right), properties (left|right)
523    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c");
524    TableModel model = gen.new TableModel(id, true);
525    model.setAlternating(true);
526    model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100));
527    model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
528    for (PropertyComponent p : comparison.getUnion().getProperty()) {
529      model.getTitles().add(gen.new Title(null, null, p.getCode(), p.getDescription(), null, 100, 2));
530    }
531    model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
532    for (StructuralMatch<ConceptDefinitionComponent> t : comparison.getCombined().getChildren()) {
533      addRow(gen, model.getRows(), t, comparison);
534    }
535    return gen.generate(model, prefix, 0, null);
536  }
537
538  private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ConceptDefinitionComponent> t, CodeSystemComparison comparison) {
539    Row r = gen.new Row();
540    rows.add(r);
541    r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
542    if (t.hasLeft() && t.hasRight()) {
543      if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
544        if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
545          r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));        
546        } else {
547          r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));        
548          r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
549        }
550      } else if (t.getLeft().hasDisplay()) {
551        r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));        
552        r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));        
553      } else if (t.getRight().hasDisplay()) {        
554        r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));        
555        r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));        
556      } else {
557        r.getCells().add(missingCell(gen).span(2));
558      }
559      for (PropertyComponent p : comparison.getUnion().getProperty()) {
560        ConceptPropertyComponent lp = getProp(t.getLeft(), p, false, comparison);
561        ConceptPropertyComponent rp = getProp(t.getRight(), p, true, comparison);
562
563        if (lp != null && rp != null) {
564          if (lp.getValue().equals(rp.getValue())) {
565            r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));        
566          } else {
567            r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));        
568            r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
569          }
570        } else if (lp != null) {
571          r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));        
572          r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));        
573        } else if (rp != null) {        
574          r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));        
575          r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));        
576        } else {
577          r.getCells().add(missingCell(gen).span(2));
578        }
579
580      }
581    } else if (t.hasLeft()) {
582      r.setColor(COLOR_NO_ROW_RIGHT);
583      r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
584      r.getCells().add(missingCell(gen));
585      for (PropertyComponent p : comparison.getUnion().getProperty()) {
586        r.getCells().add(propertyCell(gen, t.getLeft(), p, false, comparison));
587        r.getCells().add(missingCell(gen));
588      }
589    } else {
590      r.setColor(COLOR_NO_ROW_LEFT);
591      r.getCells().add(missingCell(gen));
592      r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
593      for (PropertyComponent p : comparison.getUnion().getProperty()) {
594        r.getCells().add(missingCell(gen));
595        r.getCells().add(propertyCell(gen, t.getLeft(), p, true, comparison));
596      }
597    }
598    r.getCells().add(cellForMessages(gen, t.getMessages()));
599  }
600
601  private Cell propertyCell(HierarchicalTableGenerator gen, ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
602    ConceptPropertyComponent cp = getProp(cd, p, right, comp);
603    if (cp == null) {
604      return missingCell(gen, right ? COLOR_NO_CELL_RIGHT : COLOR_NO_CELL_LEFT);
605    } else {
606      return gen.new Cell(null, null, cp.getValue().toString(), null, null);
607    }
608  }
609
610  public ConceptPropertyComponent getProp(ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
611    String c = p.getCode();
612    if (right) {
613      c = comp.getPropMap().get(c);
614    }
615    ConceptPropertyComponent cp = null;
616    if (cd != null) {
617      for (ConceptPropertyComponent t : cd.getProperty()) {
618        if (t.hasCode() && t.getCode().equals(c)) {
619          cp = t;
620        }
621      }
622    }
623    return cp;
624  }
625
626  @Override
627  protected String fhirType() {
628    return "CodeSystem";
629  }
630
631}