

package gov.va.med.cds.xml.schema;


import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import gov.va.med.cds.junit.runners.Suite;
import gov.va.med.cds.junit.runners.SuiteAwareRunner;
import gov.va.med.cds.util.StreamUtil;

import net.sf.saxon.TransformerFactoryImpl;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.sql.rowset.serial.SerialException;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Validator;


@RunWith( SuiteAwareRunner.class )
public class XMLDocumentReorderingTest
{
    private static Resource[] jarResources = null;
    private static PathMatchingResourcePatternResolver pathResolver = new PathMatchingResourcePatternResolver();
    public static final String TEMPLATE_JAR_PATH_PATTERN = "file:templatecache/**/VitalsignsRead40010.jar";
    //public static final String TEMPLATE_JAR_PATH_PATTERN = "file:templatecache/**/RDIIntoleranceConditionPharmacyRead40010.jar";
    //public static final String TEMPLATE_JAR_PATH_PATTERN = "file:templatecache/**/LabRead40010.jar";
    //public static final String TEMPLATE_JAR_PATH_PATTERN = "file:templatecache/**/PharmacyRead40010.jar";
    public static byte[] byteArrayJar = null;

    private static String schemaName = "VitalsignsRead40010";
    //private static String schemaName = "RDIIntoleranceConditionPharmacyRead40010";
    // static String schemaName = "LabRead40010";
    //private static String schemaName = "PharmacyRead40010";

    private static SchemaHelperInterface schemaHelper;
    private static Validator validator;


    @BeforeClass
    @Suite( groups = { "checkintest" } )
    public static void setUp( )
        throws IOException,
            SerialException,
            SQLException
    {
        jarResources = pathResolver.getResources( TEMPLATE_JAR_PATH_PATTERN );
        Resource jarResource = jarResources[0];
        byteArrayJar = getBytesFromFile( jarResource.getFile() );
        assertNotNull( byteArrayJar );
        Blob blobJar = new javax.sql.rowset.serial.SerialBlob( byteArrayJar );
        schemaHelper = new SchemaHelper( blobJar, schemaName );
        validator = schemaHelper.getValidator();
    }


    @Test
    @Suite( groups = { "checkintest" } )
    public void testCodeBasedReordering( )
        throws IOException
    {
        String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadInputSample.xml" ) );

        assertFalse( validateXmlAgainstSchema( sampleInputXml ) );

        try
        {
            // Create a dom4j document from the input xml
            Document inputDoc = DocumentHelper.parseText( sampleInputXml );

            long initialMemoryUsage = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
            long startReorderingTime = new Date().getTime();

            Element patientElement = inputDoc.getRootElement().element( "patient" );
            List resolvedIds = patientElement.elements( "requestedResolvedIdentifiers" );
            for ( Object obj : resolvedIds )
            {
                Element e = ( Element )obj;
                reorderIdFields( e.elements() );
            }

            Element vitalsEvent = ( Element )patientElement.elements( "vitalSignObservationEvents" ).get( 0 );
            Element vitalsPatient = vitalsEvent.element( "patient" );
            Element id = vitalsPatient.element( "identifier" );
            reorderIdFields( id.elements() );

            String reOrderedXml = inputDoc.asXML();
            assertTrue( validateXmlAgainstSchema( reOrderedXml ) );

            long elapsedTime = new Date().getTime() - startReorderingTime;
            long memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - initialMemoryUsage;

        }
        catch ( DocumentException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    private void reorderIdFields( List idFields )
    {
        Object identity = idFields.remove( 2 );
        Object assigningAuthority = idFields.remove( 1 );
        Object assigningFacility = idFields.remove( 0 );

        List l = new ArrayList();
        l.add( 0, identity );
        l.add( 1, assigningFacility );
        l.add( 2, assigningAuthority );

        idFields.addAll( l );
    }


    /*
     * Since we no longer using the XSLT path for CDS 3.1 code base,the test case will ignored
     * 
     * @Test
    @Suite( groups = { "checkintest" } )
    public void testXsltBasedReordering( )
        throws IOException,
            TransformerConfigurationException
    {
        String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadInputSample.xml" ) );

        assertFalse( validateXmlAgainstSchema( sampleInputXml ) );

        try
        {
            // Create a dom4j document from the input xml
            Document inputDoc = DocumentHelper.parseText( sampleInputXml );

            // Get the compiled xslt template
            Templates xsltTemplate = getXsltTemplate();

            long initialMemoryUsage = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
            long startReorderingTime = new Date().getTime();

            Transformer transformer = xsltTemplate.newTransformer();

            DocumentResult result = new DocumentResult();
            transformer.transform( new SAXSource( new InputSource( new StringReader( inputDoc.asXML() ) ) ), result );
            Document outputDoc = result.getDocument();

            String reOrderedXml = outputDoc.asXML();
            assertTrue( validateXmlAgainstSchema( reOrderedXml ) );

            long elapsedTime = new Date().getTime() - startReorderingTime;
            long memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - initialMemoryUsage;

        }
        catch ( DocumentException e )
        {
            e.printStackTrace();
        }
        catch ( TransformerException te )
        {
            te.printStackTrace();
        }
    }
*/

    @Test
    @Suite( groups = { "checkintest" } )
    public void testXsdBasedReordering( )
        throws IOException
    {
        String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadInputSample.xml" ) ); //93ms; 286744 bytes
        //String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource( "src/test/java/gov/va/med/cds/xml/schema/RDIIcRxReadSampleInput.xml" ) ); //46ms; 175280 bytes
        //String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource( "src/test/java/gov/va/med/cds/xml/schema/LabReadInputSample.xml" ) ); //141ms; 390160 bytes
        //String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource( "src/test/java/gov/va/med/cds/xml/schema/PharmacyReadInputSample.xml" ) ); //187ms; 524696 bytes

        assertFalse( validateXmlAgainstSchema( sampleInputXml ) );

        try
        {
            // Create a dom4j document from the input xml
            Document inputDoc = DocumentHelper.parseText( sampleInputXml );

            long initialMemoryUsage = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
            long startReorderingTime = new Date().getTime();

            Map<String, List<String>> elementMap = schemaHelper.getSymbolMap();
            /*
            for(String s : elementMap.keySet())
            {
            	System.out.println("ElementMap[" + s + "]: " + elementMap.get(s));
            }
            */
            Element rootElement = inputDoc.getRootElement();
            List<String> elementOrder = elementMap.get( "CLINICALDATA.CLINICALDATA" );

            try
            {
            reorderElementsInElement( rootElement, elementOrder, elementMap );
            }
            catch ( Exception e )
            {
                e.printStackTrace();
            }

            String reOrderedXml = inputDoc.asXML();
            //System.out.println("Reordered XML: " + reOrderedXml);
            assertTrue( validateXmlAgainstSchema( reOrderedXml ) );

            long elapsedTime = new Date().getTime() - startReorderingTime;
            long memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - initialMemoryUsage;
        }
        catch ( DocumentException e )
        {
            e.printStackTrace();
        }
    }


    private void reorderElementsInElement( Element element, List<String> elementOrder, Map<String, List<String>> elementMap )
    {
        //System.out.println(element.getName() + "[" + elementOrder.size() + "]: " + elementOrder);

        List orderedElementList = new ArrayList();

        for ( String xsdElementDetail : elementOrder )
        {
            int indexOfSlash = xsdElementDetail.indexOf( '/' );

            String elementName = xsdElementDetail.substring( 0, indexOfSlash );
            String elementType = xsdElementDetail.substring( indexOfSlash + 1 );

            List elementsOfType = null;

            try
            {
                elementsOfType = element.elements( elementName );
            }
            catch ( Exception e )
            {
                e.printStackTrace();
            }

            for ( Object o : elementsOfType )
            {
                Element e = ( Element )o;
                if ( elementType.contains( "." ) )
                {
                    reorderElementsInElement( e, elementMap.get( elementType ), elementMap );
                }
                orderedElementList.add( e.detach() );
            }
        }

        List<Element> elementsInElement = element.elements();
        elementsInElement.removeAll( orderedElementList );

        elementsInElement.addAll( orderedElementList );
    }


    @Test
    @Suite( groups = { "checkintest" } )
    public void testXmlTemplateBasedReordering( )
        throws IOException
    {
        String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadInputSample.xml" ) );
        String templateInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadBlank.xml" ) );

        assertFalse( validateXmlAgainstSchema( sampleInputXml ) );

        try
        {
            Document inputDoc = DocumentHelper.parseText( sampleInputXml );
            Document templateDoc = DocumentHelper.parseText( templateInputXml );
            Element patientElement = templateDoc.getRootElement().element( "patient" );
            Element sourcePatientElement = inputDoc.getRootElement().element( "patient" );

            long initialMemoryUsage = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
            long startReorderingTime = new Date().getTime();
            orderElement( patientElement, sourcePatientElement );
            long elapsedTime = new Date().getTime() - startReorderingTime;
            long memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - initialMemoryUsage;

            String reOrderedXml = templateDoc.asXML();
            assertTrue( validateXmlAgainstSchema( reOrderedXml ) );
        }
        catch ( DocumentException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    private void orderElement( Element templateElement, Element sourceElement )
    {
        if ( templateElement.hasContent() && templateElement.elements().size() > 0 )
        {
            List<Element> sourceList = sourceElement.selectNodes( templateElement.getPath() );
            if ( sourceList.size() > 0 )
            {
                for ( Element element : sourceList )
                {
                    List<Element> subSourceList = element.elements();
                    if ( subSourceList.size() > 0 )
                    {
                        for ( Element e1 : subSourceList )
                        {
                            orderElement( templateElement.element( e1.getName() ), e1 );
                        }
                    }
                    else
                    {
                        templateElement.element( element.getName() ).setText( element.getText() );
                    }
                }
            }
            else
            {
                templateElement.detach();
            }
        }
        else
        {
            templateElement.setText( sourceElement.getText() );
        }
    }


    @Test
    @Suite( groups = { "checkintest" } )
    public void testXmlUpdateInPlaceBasedReordering( )
        throws IOException
    {
        String sampleInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadInputSample.xml" ) );

        String templateInputXml = StreamUtil.resourceToString( new FileSystemResource(
                        "src/test/java/gov/va/med/cds/xml/schema/VitalsignsReadBlank.xml" ) );

        assertFalse( validateXmlAgainstSchema( sampleInputXml ) );

        try
        {
            Document inputDoc = DocumentHelper.parseText( sampleInputXml );
            Document templateDoc = DocumentHelper.parseText( templateInputXml );
            Element patientElement = templateDoc.getRootElement().element( "patient" );
            Element sourcePatientElement = inputDoc.getRootElement().element( "patient" );

            long initialMemoryUsage = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
            long startReorderingTime = new Date().getTime();
            orderElementInPlace( patientElement, sourcePatientElement );
            long elapsedTime = new Date().getTime() - startReorderingTime;
            long memoryUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - initialMemoryUsage;

            String reOrderedXml = inputDoc.asXML();
            assertTrue( validateXmlAgainstSchema( reOrderedXml ) );
        }
        catch ( DocumentException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    private void orderElementInPlace( Element templateElement, Element sourceElement )
    {
        if ( sourceElement.hasContent() && sourceElement.elements().size() > 0 )
        {
            List<Element> templateList = templateElement.elements();
            ArrayList<Node> sortedList = new ArrayList<Node>();
            for ( Element element : templateList )
            {
                List<Element> eList = sourceElement.elements( element.getName() );
                for ( Element e : eList )
                {
                    sortedList.add( e.detach() );
                    //sortedList.add( ( sourceElement.element( element.getName() ) ).detach() );
                }
            }

            sourceElement.clearContent();
            for ( Node n : sortedList )
            {
                sourceElement.add( n );
            }

            List<Element> sourceList = sourceElement.elements();
            for ( Element e1 : sourceList )
            {
                orderElementInPlace( templateElement.element( e1.getName() ), e1 );
            }
        }
    }


    // code need not to be revisted, CDS 3.1 will not process XSLT will not xslt any more 
    private Templates getXsltTemplate( )
        throws IOException,
            TransformerConfigurationException
    {
        Resource[] xsltResources = pathResolver.getResources( "file:./src/test/java/gov/va/med/cds/xml/schema/MapToVitalSignsRead40010.xslt" );
        TransformerFactory transformerFactory = new TransformerFactoryImpl();
        return transformerFactory.newTemplates( new StreamSource( xsltResources[0].getInputStream() ) );
    }


    private boolean validateXmlAgainstSchema( String inputXml )
    {
        try
        {
            Source source = new StreamSource( new StringReader( inputXml ) );
            validator.validate( source );
            return true;
        }
        catch ( IOException e )
        {
            //System.out.println("Invalid XML. Reason: " + e.getMessage());
            return false;
        }
        catch ( SAXException ex )
        {
            //System.out.println("Invalid XML. Reason: " + ex.getMessage());
            return false;
        }
    }


    /**
     * Returns the contents of the file in a byte array
     * @param file File this method should read
     * @return byte[] Returns a byte[] array of the contents of the file
     */
    private static byte[] getBytesFromFile( File file )
        throws IOException
    {

        InputStream is = new FileInputStream( file );

        // Get the size of the file
        long length = file.length();

        /*
         * You cannot create an array using a long type. It needs to be an int
         * type. Before converting to an int type, check to ensure that file is
         * not loarger than Integer.MAX_VALUE;
         */
        if ( length > Integer.MAX_VALUE )
        {
            System.out.println( "File is too large to process" );
            return null;
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[( int )length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while ( ( offset < bytes.length ) && ( ( numRead = is.read( bytes, offset, bytes.length - offset ) ) >= 0 ) )
        {

            offset += numRead;

        }

        // Ensure all the bytes have been read in
        if ( offset < bytes.length )
        {
            throw new IOException( "Could not completely read file " + file.getName() );
        }

        is.close();
        return bytes;

    }
}
