/*
 * NIST HL7 Web Service
 * Hl7GazelleMessageValidation.java Feb 13, 2007
 *
 * This code was produced by the National Institute of Standards and
 * Technology (NIST). See the "nist.disclaimer" file given in the distribution
 * for information on the use and redistribution of this software.
 */

package gov.nist.hl7.ws.messagevalidation;

import gov.nist.hl7.core.message.AbstractMessage;
import gov.nist.hl7.core.profile.Profile;
import gov.nist.hl7.core.validation.message.MessageValidationContext;
import gov.nist.hl7.core.validation.message.MessageValidationResult;
import gov.nist.hl7.core.validation.message.TableProfileDocument;
import gov.nist.hl7.core.validation.report.ValidationReportDocument;
import gov.nist.hl7.core.validation.report.ValidationReportType;
import gov.nist.hl7.core.validation.report.ValidationReportType.MessageValidation.Result;
import gov.nist.hl7.ws.JdbcRepositoryDao;

import java.io.IOException;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;

import net.ihe.hl7.ws.detailedresults.DetailedResultsDocument;
import net.ihe.hl7.ws.detailedresults.ReferencedStandardDocument;
import net.ihe.hl7.ws.detailedresults.ValidationCountersDocument;
import net.ihe.hl7.ws.detailedresults.ValidationResultsDocument;
import net.ihe.hl7.ws.detailedresults.ValidationResultsOverviewDocument;
import net.ihe.hl7.ws.detailedresults.ValidationServiceStatusDocument;
import net.ihe.hl7.ws.detailedresults.DetailedResultsDocument.DetailedResults;
import net.ihe.hl7.ws.detailedresults.ReferencedStandardDocument.ReferencedStandard;
import net.ihe.hl7.ws.detailedresults.ResultXMLDocument.ResultXML;
import net.ihe.hl7.ws.detailedresults.StatusDocument.Status;
import net.ihe.hl7.ws.detailedresults.StatusDocument.Status.Enum;
import net.ihe.hl7.ws.detailedresults.ValidationCountersDocument.ValidationCounters;
import net.ihe.hl7.ws.detailedresults.ValidationResultsDocument.ValidationResults;
import net.ihe.hl7.ws.detailedresults.ValidationResultsOverviewDocument.ValidationResultsOverview;
import net.ihe.hl7.ws.detailedresults.ValidationServiceStatusDocument.ValidationServiceStatus;
import net.ihe.hl7.ws.summaryresults.SummaryResultsDocument;
import net.ihe.hl7.ws.summaryresults.SummaryResultsDocument.SummaryResults;
import net.ihe.hl7.ws.xmlmessagedata.MessageMetaDataDocument;
import net.ihe.hl7.ws.xmlmessagedata.MessageMetaDataType;
import net.ihe.hl7.ws.xmlvalidationcontext.NISTHL7V2ValidationContextDefinitionDocument;
import net.ihe.hl7.ws.xmlvalidationcontext.NISTHL7V2ValidationContextDefinitionType;
import net.ihe.hl7.ws.xmlvalidationcontext.ValidationContextDocument;
import net.ihe.hl7.ws.xmlvalidationcontext.ValidationContextType;
import net.ihe.hl7.ws.xmlvalidationcontext.ValidationContextType.ValidationSpecificContext;

/**
 * @author Roch Bertucat (NIST)
 * See interface for more details
 */
public class Hl7GazelleMessageValidation implements
Hl7GazelleMessageValidationInterface {

    private static final List<String> VERSIONS = Arrays.asList("2.3.1", "2.4", "2.5", "2.5.1");
    private String detailedResults;
    private String summaryResults;
    private MessageValidationUtils mvUtils;
    private JdbcRepositoryDao rDao;

    public void clearResultsCache() {
        detailedResults = null;
        summaryResults = null;
    }

    public String getDetailedResults(String objectOID) {
        // for now, return just the last report
        return detailedResults;
    }

    public String getSummaryResults(String objectOID) {
        // for now, return just the last report
        return summaryResults;
    }

    public String getValidationServiceStatus() {
        // do something smart
        return "OK";
    }

    public String validateMessage(String objectOID,
            String xmlReferencedStandard, String xmlValidationContext,
            String xmlMessageMetaData, String xmlMessage) {
        // init
        detailedResults = null;
        summaryResults = null;

        String ret = "";
        ValidationResultsOverviewDocument validResOverviewDoc = null;
        try {
            //XmlResultsOverview
            validResOverviewDoc = ValidationResultsOverviewDocument.Factory.newInstance();
            ValidationResultsOverview validResOverview =
                validResOverviewDoc.addNewValidationResultsOverview();

            validResOverview.setValidationServiceName("NIST Validation Service");
            validResOverview.setValidationServiceVersion("1.0");

            // Service Status
            String serviceStatus = getValidationServiceStatus();
            ValidationServiceStatusDocument serviceStatusDoc =
                ValidationServiceStatusDocument.Factory.newInstance();
            ValidationServiceStatus serviceStatusType =
                serviceStatusDoc.addNewValidationServiceStatus();
            Enum status;
            if (serviceStatus.equals("OK")) {
                status = Status.OK;
            } else {
                status = Status.ERROR;
            }

            serviceStatusType.setStatus(status);
            validResOverview.setValidationServiceStatus(serviceStatusDoc.getValidationServiceStatus());
            //validResOverviewType.setObjectGuid(objectOID);

            if (!serviceStatus.equals("OK")) {
                throw new Exception("Invalid Service Status.");
            }

            // extract xmlReferenceStandard data
            ReferencedStandard refStandard = ReferencedStandardDocument.
            Factory.parse(xmlReferencedStandard).getReferencedStandard();
            //String standardExtension = refStandard.getStandardExtension();
            // add to XmlResultsOverview
            validResOverview.setReferencedStandard(refStandard);

            //  extract xmlValidationContext data
            ValidationContextType validContext = ValidationContextDocument.
            Factory.parse(xmlValidationContext).getValidationContext();
            ValidationSpecificContext evsSpecificContext =
                validContext.getValidationSpecificContext();

            // Extract NIST HL7 message context
            NISTHL7V2ValidationContextDefinitionType vct =
                evsSpecificContext.getNISTHL7V2ValidationContextDefinition();
            NISTHL7V2ValidationContextDefinitionDocument mvc =
                NISTHL7V2ValidationContextDefinitionDocument.Factory.newInstance();
            mvc.setNISTHL7V2ValidationContextDefinition(vct);
            String messageValidContext = mvc.toString();

            // extract xmlMessageMetaData data
            MessageMetaDataType msgMetaData = MessageMetaDataDocument.
            Factory.parse(xmlMessageMetaData).getMessageMetaData();
            String messageProfileID = msgMetaData.getMessageProfileID();

            // ----------------------------------------------
            // VALIDATION
            // ----------------------------------------------
            // check basic parameters
            String standardName = refStandard.getStandardName();
            String standardVersion = refStandard.getStandardVersion();
            String encodedChar = msgMetaData.getEncodedCharacter();
            preValidate(standardName, standardVersion, encodedChar);

            //String specificOID = validContext.getTestSpecification();
            // validation
            if (messageProfileID.equals("")) {
                throw new Exception("Invalid TestSpecification OID given in the validation context.");
            }
            String report = validateWithOID(messageProfileID, xmlMessage, messageValidContext);

            // add info to XmlResultsOverview
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            validResOverview.setValidationDate(sdf.format(new Date()));
            sdf = new SimpleDateFormat("HHmmss");
            validResOverview.setValidationTime(sdf.format(new Date()));
            validResOverview.setValidationTestId("Test xx");
            validResOverview.setValidationTestResult("PASSED");

            // -----------------------------------------------
            // BEGIN SummaryResults & DetailedResults mappings
            // -----------------------------------------------
            String genSummaryRes = validContext.getGenerateSummaryResults();
            String genDetailedRes = validContext.getGenerateDetailedResults();
            generateResults(report, validResOverviewDoc, genDetailedRes, genSummaryRes);
            // - END SummaryResults & DetailedResults mappings

        } catch (Exception e) {
            //e.printStackTrace();
            ret = e.getMessage();
            if (validResOverviewDoc != null) {
                validResOverviewDoc.getValidationResultsOverview().setValidationTestResult("FAILED");
                validResOverviewDoc.getValidationResultsOverview().
                getValidationServiceStatus().setAdditionalStatusInfo(ret);
            }
        }

        if (validResOverviewDoc != null) {
            ret = validResOverviewDoc.toString();
        }
        return ret;
    }

    private void generateResults(String report, ValidationResultsOverviewDocument validResOverviewDoc,
            String genDetailedRes, String genSummaryRes) throws XmlException, IOException {

        ValidationReportDocument validReportDoc = ValidationReportDocument.Factory.parse(report);
        ValidationReportType validReport = validReportDoc.getValidationReport();
        ValidationResultsOverview validResOverview = validResOverviewDoc.getValidationResultsOverview();

        // Counter Values
        Result result = validReportDoc.getValidationReport().getMessageValidation().getResult();
        //BigInteger nrValidCond = new BigInteger(String.valueOf(result.getIgnoreCount()));
        BigInteger nrValidWarning = new BigInteger(String.valueOf(result.getWarningCount()));
        BigInteger nrValidError = new BigInteger(String.valueOf(result.getErrorCount()));

        // ValidationCounters
        ValidationCountersDocument validCountDoc = ValidationCountersDocument.Factory.newInstance();
        ValidationCounters validCount = validCountDoc.addNewValidationCounters();
        validCount.setNrOfValidationWarnings(nrValidWarning);
        validCount.setNrOfValidationErrors(nrValidError);
        validCount.setValidationTest(validResOverview.getValidationTestResult());

        // ValidationResults
        ValidationResultsDocument validResDoc = ValidationResultsDocument.Factory.newInstance();
        ValidationResults[] validResultArray = new ValidationResults[1];
        ValidationResults validResult = validResDoc.addNewValidationResults();
        validResult.setResultText("result");
        ResultXML resultXml = validResult.addNewResultXML();

        // cast
        XmlObject xmlobject = validReport.changeType(net.ihe.hl7.ws.detailedresults.ValidationReportType.type);
        net.ihe.hl7.ws.detailedresults.ValidationReportType validReportTypeD =
            net.ihe.hl7.ws.detailedresults.ValidationReportType.Factory.parse(xmlobject.newInputStream());

        resultXml.setValidationReport(validReportTypeD);

        // DetailedResults
        if (genDetailedRes.equals("True")) {
            DetailedResultsDocument detailedResultsDoc = DetailedResultsDocument.Factory.newInstance();
            DetailedResults detResults = detailedResultsDoc.addNewDetailedResults();
            // ValidationResultsOverview
            detResults.setValidationResultsOverview(validResOverview);
            // Counter
            detResults.setValidationCounters(validCount);
            // ValidationResults
            validResultArray[0] = validResult;
            detResults.setValidationResultsArray(validResultArray);

            // save
            detailedResults = detailedResultsDoc.toString();
        }

        // SummaryResults: cast
        if (genSummaryRes.equals("True")) {

            SummaryResultsDocument summaryResultsDoc = SummaryResultsDocument.Factory.newInstance();
            SummaryResults sumResults = summaryResultsDoc.addNewSummaryResults();

            // ValidationResultsOverview: clone, changetype, cast, copy
            ValidationResultsOverviewDocument validResOverviewDocBkp = ValidationResultsOverviewDocument.Factory.parse(validResOverviewDoc.newInputStream());
            XmlObject xmlobj = validResOverviewDocBkp.changeType(net.ihe.hl7.ws.summaryresults.ValidationResultsOverviewDocument.type);
            net.ihe.hl7.ws.summaryresults.ValidationResultsOverviewDocument validResOverviewDocS =
                (net.ihe.hl7.ws.summaryresults.impl.ValidationResultsOverviewDocumentImpl) xmlobj;
            sumResults.setValidationResultsOverview(validResOverviewDocS.getValidationResultsOverview());

            // Counter
            ValidationCountersDocument validCountDocBkp = ValidationCountersDocument.Factory.parse(validCountDoc.newInputStream());
            xmlobj = validCountDocBkp.changeType(net.ihe.hl7.ws.summaryresults.ValidationCountersDocument.type);
            net.ihe.hl7.ws.summaryresults.ValidationCountersDocument validCountDocS =
                (net.ihe.hl7.ws.summaryresults.impl.ValidationCountersDocumentImpl) xmlobj;
            sumResults.setValidationCounters(validCountDocS.getValidationCounters());

            // ValidationResults
            ValidationResultsDocument validResDocBkp = ValidationResultsDocument.Factory.parse(validResDoc.newInputStream());
            xmlobj = validResDocBkp.changeType(net.ihe.hl7.ws.summaryresults.ValidationResultsDocument.type);
            net.ihe.hl7.ws.summaryresults.ValidationResultsDocument validResDocS =
                (net.ihe.hl7.ws.summaryresults.impl.ValidationResultsDocumentImpl) xmlobj;
            net.ihe.hl7.ws.summaryresults.ValidationResultsDocument.ValidationResults[] validResultArrayS
            = new net.ihe.hl7.ws.summaryresults.ValidationResultsDocument.ValidationResults[1];
            validResultArrayS[0] = validResDocS.getValidationResults();
            sumResults.setValidationResultsArray(validResultArrayS);

            // save
            summaryResults = summaryResultsDoc.toString();
        }
    }

    private String validateWithOID(String specificOID, String xmlMessage,
            String messageValidContext) throws Exception {
        Profile profile  = null;
        TableProfileDocument resource = null;
        try {
            // use the profileOID
            profile = rDao.getProfile(specificOID);
        } catch (Exception e) {
            ;
        }
        if (profile == null) {
            try {
                // use the handleOID
                if (rDao.getBindings(specificOID).length > 1) {
                    Map <String, Object> map = mvUtils.useHandle(specificOID, rDao);
                    resource = (TableProfileDocument) map.get("resource");
                    profile = (Profile) map.get("profile");
                }
            } catch (Exception e) {
                ;
            }
        }
        if (profile == null) {
            throw new Exception("Neither a profile nor a handle can"
                    + "be found with the given OID: " + specificOID);
        }

        // load the message
        AbstractMessage message = mvUtils.getMessage(xmlMessage);
        // load the validationContext
        MessageValidationContext mvc = mvUtils.getMVC(messageValidContext);
        // validate
        MessageValidationResult mvResult = mvUtils.validate(profile, message, mvc, resource);
        // get the report
        return mvResult.getReport().toString();
    }

    private void preValidate(String standardName, String standardVersion,
            String encodedChar) throws Exception {
        // check if standardName matches "HL7"
        if (!standardName.equals("HL7")) {
            throw new Exception("StandardName has to be \"HL7\"");
        }

        // check if standardVersion matches one of our supported version
        if (!VERSIONS.contains(standardVersion)) {
            throw new Exception("Version " + standardVersion + " not supported.");
        }

        // encodedChar can equals to "English" or "ASCII"
        if (encodedChar == null) {
            throw new Exception("EncodedCharacter cannot be null.");
        }
        if (!(encodedChar.equals("English") || encodedChar.equals("ASCII"))) {
            throw new Exception("EncodedCharacter shall be equals to \"ASCII\" or \"English\".");
        }
    }

    public void setJdbcRepositoryDao(JdbcRepositoryDao inRDao) {
        this.rDao = inRDao;
    }

    public void setMessageValidationUtils(MessageValidationUtils mvUtils) {
        this.mvUtils = mvUtils;
    }
}
