package nl.thehyve.ocdu.validators.clinicalDataChecks;

import nl.thehyve.ocdu.models.OCEntities.ClinicalData;
import nl.thehyve.ocdu.models.OcDefinitions.CRFDefinition;
import nl.thehyve.ocdu.models.OcDefinitions.ItemDefinition;
import nl.thehyve.ocdu.models.OcDefinitions.MetaData;
import nl.thehyve.ocdu.models.errors.MandatoryItemInCrfMissing;
import nl.thehyve.ocdu.models.errors.ValidationErrorMessage;
import org.openclinica.ws.beans.StudySubjectWithEventsType;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by piotrzakrzewski on 11/05/16.
 */
public class MandatoryInCrfCrossCheck implements ClinicalDataCrossCheck {
    @Override
    public ValidationErrorMessage getCorrespondingError(List<ClinicalData> data, MetaData metaData, Map<ClinicalData, ItemDefinition> itemDefMap, List<StudySubjectWithEventsType> studySubjectWithEventsTypeList, Map<ClinicalData, Boolean> shownMap, Map<String, Set<CRFDefinition>> eventMap) {
        HashMap<String, Set<String>> mandatoryMap = getMandatoryMap(data, eventMap);
        HashMap<String, Set<String>> presentMap = getPresentMap(data);
        MandatoryItemInCrfMissing error = new MandatoryItemInCrfMissing();
        reportMissingColumns(mandatoryMap, presentMap, error);
        reportMissingValues(mandatoryMap, data, error, shownMap);
        if (error.getOffendingValues().size() > 0)
            return error;
        else return null;
    }

    private void reportMissingValues(HashMap<String, Set<String>> mandatoryMap, List<ClinicalData> data, MandatoryItemInCrfMissing error, Map<ClinicalData, Boolean> shownMap) {
        data.stream().forEach(clinicalData -> {
            String item = clinicalData.getItem();
            String crfId = clinicalData.getCrfName() + clinicalData.getCrfVersion();
            Set<String> mandatory = mandatoryMap.get(crfId);
            String value = clinicalData.getValue();
            if (mandatory != null && mandatory.contains(item) && value.equals("") && shownMap.get(clinicalData)) { // is mandatory and value is empty, item not hidden for given subject
                error.addOffendingValue("Item: " + item + " cannot be empty as it is mandatory in CRF: " + crfId + " but was empty for subject: " + clinicalData.getSsid());
            }
        });
        Map<String, Set<String>> userItems = new HashMap<>();
        Map<String, Set<String>> mandatoryForSubject = new HashMap<>();

        data.stream().forEach(clinicalData -> {
            String crfId = clinicalData.getCrfName() + clinicalData.getCrfVersion();
            Set<String> mandatoryItems = mandatoryMap.get(crfId);
            if (!userItems.containsKey(clinicalData.getSsid())) {
                userItems.put(clinicalData.getSsid(), new HashSet<>());
                mandatoryForSubject.put(clinicalData.getSsid(), mandatoryItems);
            }
            Set<String> items = userItems.get(clinicalData.getSsid());
            items.add(clinicalData.getItem());
        });
        userItems.keySet().forEach(subject -> {
            Set<String> itemsUploadedForUser = userItems.get(subject);
            Set<String> mandatory = mandatoryForSubject.get(subject);
            if (mandatory != null) {
                for (String mandatoryItem : mandatory) {
                    if (!itemsUploadedForUser.contains(mandatoryItem)) {
                        error.addOffendingValue("Subject: " + subject + " is missing mandatory item: " + mandatoryItem);
                    }
                }
            }
        });
    }


    private void reportMissingColumns(HashMap<String, Set<String>> mandatoryMap, HashMap<String, Set<String>> presentMap, ValidationErrorMessage error) {
        for (String crfId : mandatoryMap.keySet()) {
            Set<String> expected = mandatoryMap.get(crfId);
            Set<String> found = presentMap.get(crfId);
            expected.stream().filter(expectedItem -> !found.contains(expectedItem)).forEach(missing -> {
                error.addOffendingValue("CRF: " + crfId + " requires item: " + missing);
            });
        }
    }

    private HashMap<String, Set<String>> getPresentMap(List<ClinicalData> data) {
        HashMap<String, Set<String>> presentMap = new HashMap<>();
        data.stream().forEach(clinicalData -> {
            String crfId = clinicalData.getCrfName() + clinicalData.getCrfVersion();
            if (!presentMap.containsKey(crfId)) {
                Set<String> presentItems = new HashSet<>();
                presentItems.add(clinicalData.getItem());
                presentMap.put(crfId, presentItems);
            } else {
                Set<String> presentItems = presentMap.get(crfId);
                presentItems.add(clinicalData.getItem());
            }
        });
        return presentMap;
    }

    private HashMap<String, Set<String>> getMandatoryMap(List<ClinicalData> data, Map<String, Set<CRFDefinition>> eventMap) {
        HashMap<String, Set<String>> mandatoryMap = new HashMap<>();
        data.stream().forEach(clinicalData -> {
            String eventName = clinicalData.getEventName();
            String crfName = clinicalData.getCrfName();
            String crfVersion = clinicalData.getCrfVersion();
            CRFDefinition matching = getMatchingCrf(eventName, crfName, crfVersion, eventMap);
            if (matching != null) { // Missing CRF or Event are  separate errors
                Set<String> expected = matching.getMandatoryItemNames();
                mandatoryMap.put(crfName + crfVersion, expected);
            }
        });
        return mandatoryMap;
    }

    private CRFDefinition getMatchingCrf(String eventName, String CRFName, String CRfVersion, Map<String, Set<CRFDefinition>> eventMap) {
        Set<CRFDefinition> crfInEvents = eventMap.get(eventName);
        if (crfInEvents == null) {
            return null;
        }
        List<CRFDefinition> matching = crfInEvents.stream()
                .filter(crfDefinition -> crfDefinition.getName().equals(CRFName) && crfDefinition.getVersion().equals(CRfVersion)).collect(Collectors.toList());
        assert matching.size() < 2;
        if (matching.size() == 0) {
            return null;
        } else {
            return matching.get(0);
        }
    }


}

@@ -1181,32 +1181,82 @@
 ssing();%0A       
+ HashMap%3CString, Set%3CString%3E%3E remainingMandatory =
  reportMissingCo
@@ -1323,25 +1323,34 @@
 gValues(
-m
+remainingM
 andatory
 Map, dat
@@ -1341,19 +1341,17 @@
 andatory
-Map
+ 
 , data, 
@@ -3587,36 +3587,60 @@
 %7D%0A%0A%0A    private 
-void
+HashMap%3CString, Set%3CString%3E%3E
  reportMissingCo
@@ -3757,24 +3757,99 @@
 ge error) %7B%0A
+        HashMap%3CString, Set%3CString%3E%3E remainingMandatory = new HashMap%3C%3E();%0A
         for 
@@ -3996,32 +3996,93 @@
 Map.get(crfId);%0A
+            Set%3CString%3E remaining = new HashSet%3C%3E(expected);%0A
             expe
@@ -4275,21 +4275,153 @@
     
-%7D);%0A        %7D
+    remaining.remove(missing);%0A            %7D);%0A            remainingMandatory.put(crfId, remaining);%0A        %7D%0A        return remainingMandatory;
 %0A   

Remove redundant missing mandatory item messages