

package gov.va.med.cds.template;


import gov.va.med.cds.exceptionframework.ExceptionHandler;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Blob;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.InputSource;


public class SchemaResourceResolver
    implements
        LSResourceResolver
{
    static final int BUFF_SIZE = 100000;
    static final byte[] buffer = new byte[BUFF_SIZE];

    Hashtable<String, JarEntry> jarEntries = null;
    Hashtable<String, byte[]> jarEntryStreams = null;
    byte[] schema = null;


    public byte[] getSchema( )
    {
        return schema;
    }


    public SchemaResourceResolver( Blob aJar )
        throws Exception
    {
		JarInputStream jarInputStream = null;
		InputStream inputStream = null;
				
		try
		{
			inputStream = aJar.getBinaryStream();
			jarInputStream = new JarInputStream( inputStream );
			resolveSchemas( jarInputStream );
		}
		finally
		{
			if ( jarInputStream != null )
			{
			    try
			    {
			        jarInputStream.close();
			    }
			    finally
			    {
			        jarInputStream = null;
			    }
			}
            if ( inputStream != null )
            {
                try
                {
                    inputStream.close();
                }
                finally
                {
                    inputStream = null;
                }
            }
		}  
    }


    public SchemaResourceResolver( JarInputStream aJarInputStream )
    {
        resolveSchemas( aJarInputStream );
    }


    private void resolveSchemas( JarInputStream aJarInputStream )
    {
        jarEntries = new Hashtable<String, JarEntry>();
        jarEntryStreams = new Hashtable<String, byte[]>();
        JarEntry jarEntry = null;
        String entryName = null;
        byte[] jarEntryByteArray = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try
        {
            while ( ( jarEntry = aJarInputStream.getNextJarEntry() ) != null )
            {
                entryName = jarEntry.getName();
                jarEntries.put( entryName, jarEntry );
                if ( !jarEntry.isDirectory() )
                {
                    byteArrayOutputStream = new ByteArrayOutputStream();
                    while ( true )
                    {
                        synchronized ( buffer )
                        {
                            int amountRead = aJarInputStream.read( buffer );
                            if ( amountRead == -1 )
                            {
                                break;
                            }
                            byteArrayOutputStream.write( buffer, 0, amountRead );
                        }
                    }
                    jarEntryByteArray = byteArrayOutputStream.toByteArray();
                    if ( entryName.endsWith( ".xsd" ) )
                    {
                        entryName = entryName.substring( 0, entryName.indexOf( ".xsd" ) );
                        jarEntryStreams.put( entryName, jarEntryByteArray );
                        if ( entryName.startsWith( "template" ) )
                        {
                            schema = jarEntryByteArray;
                        }
                    }
                }
            }
        }
        catch ( IOException e )
        {
            ExceptionHandler.handleException( e, null, null, null );
        }
    }


    public Source getSchemaSource( )
    {
        Source schemaSource = null;
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( getSchema() );
        schemaSource = new StreamSource( byteArrayInputStream );

        return schemaSource;
    }


    public Source[] getSchemaSources( )
    {
        Source[] schemaSources = new Source[jarEntryStreams.size()];
        ByteArrayInputStream byteArrayInputStream = null;
        byte[] jarBytes = null;
        int index = 0;

        //create local copy
        Hashtable<String, byte[]> localJarEntryStreams = new Hashtable<String, byte[]>();
        Enumeration<String> jarKeys = jarEntryStreams.keys();
        while ( jarKeys.hasMoreElements() )
        {
            String key = jarKeys.nextElement();
            localJarEntryStreams.put( key, jarEntryStreams.get( key ) );
        }

        //Schema load order is required by the schemaCompiler                
        byte[] basedatatypes = localJarEntryStreams.remove( "Basedatatypes" );
        byte[] common = localJarEntryStreams.remove( "Common" );
        byte[] patient = localJarEntryStreams.remove( "Patient" );
        byte[] entrySchema = obtainEntrySchema( localJarEntryStreams );

        byteArrayInputStream = new ByteArrayInputStream( basedatatypes );
        schemaSources[index++ ] = new StreamSource( byteArrayInputStream );

        byteArrayInputStream = new ByteArrayInputStream( common );
        schemaSources[index++ ] = new StreamSource( byteArrayInputStream );

        Enumeration<byte[]> jarEnum = localJarEntryStreams.elements();
        while ( jarEnum.hasMoreElements() )
        {
            jarBytes = ( byte[] )jarEnum.nextElement();
            byteArrayInputStream = new ByteArrayInputStream( jarBytes );
            schemaSources[index++ ] = new StreamSource( byteArrayInputStream );
        }

        if ( patient != null )
        {
            byteArrayInputStream = new ByteArrayInputStream( patient );
            schemaSources[index++ ] = new StreamSource( byteArrayInputStream );
        }

        byteArrayInputStream = new ByteArrayInputStream( entrySchema );
        schemaSources[index++ ] = new StreamSource( byteArrayInputStream );

        return schemaSources;
    }


    public Map<String, InputSource> getSchemaMap( )
    {
        Map<String, InputSource> schemaMap = new HashMap<String, InputSource>();
        InputSource schemaInputSource = null;
        String key = null;

        Enumeration<String> jarKeys = jarEntryStreams.keys();
        while ( jarKeys.hasMoreElements() )
        {
            key = jarKeys.nextElement();
            schemaInputSource = obtainSchemaInputSource( jarEntryStreams.get( key ) );
            schemaMap.put( key, schemaInputSource );
        }

        return schemaMap;
    }


    //TODO Check for an easier way to convert to input source
    private InputSource obtainSchemaInputSource( byte[] aEntrySchema )
    {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( aEntrySchema );
        StreamSource schemaSource = new StreamSource( byteArrayInputStream );

        InputSource schemaInputSource = new InputSource( schemaSource.getSystemId() );
        schemaInputSource.setByteStream( schemaSource.getInputStream() );
        schemaInputSource.setCharacterStream( schemaSource.getReader() );
        schemaInputSource.setPublicId( schemaSource.getPublicId() );
        return schemaInputSource;
    }


    private byte[] obtainEntrySchema( Hashtable<String, byte[]> localJarEntryStreams )
    {
        Enumeration<String> jarKeys = localJarEntryStreams.keys();
        while ( jarKeys.hasMoreElements() )
        {
            String key = jarKeys.nextElement();
            if ( key.startsWith( "template/" ) )
            {
                return localJarEntryStreams.remove( key );
            }
        }
        
        return null;
    }


    @Override
    public LSInput resolveResource( String type, String namespaceURI, String publicId, String systemId, String baseURI )
    {
        MyLSInput newLSInput = null;
        ByteArrayInputStream byteArrayInputStream = null;
        byte[] schemaBytes = jarEntryStreams.get( namespaceURI );

        if ( schemaBytes != null )
        {
            byteArrayInputStream = new ByteArrayInputStream( schemaBytes );
            newLSInput = new MyLSInput( publicId, systemId, baseURI, byteArrayInputStream, null );
        }

        return newLSInput;
    }

    class MyLSInput
        implements
            LSInput
    {
        protected String publicId = null;
        protected String systemId = null;
        protected String baseSystemId = null;
        protected InputStream byteStream = null;
        protected Reader charStream = null;
        protected String data = null;
        protected String encoding = null;
        protected boolean certifiedText = false;


        public MyLSInput( String aPublicId, String aSystemId, String aBaseSystemId, InputStream aByteStream, String aEncoding )
        {
            publicId = aPublicId;
            systemId = aSystemId;
            baseSystemId = aBaseSystemId;
            byteStream = aByteStream;
            encoding = aEncoding;
        }


        @Override
        public String getBaseURI( )
        {
            return baseSystemId;
        }


        @Override
        public InputStream getByteStream( )
        {
            return byteStream;
        }


        @Override
        public boolean getCertifiedText( )
        {
            return certifiedText;
        }


        @Override
        public Reader getCharacterStream( )
        {
            return charStream;
        }


        @Override
        public String getEncoding( )
        {
            return encoding;
        }


        @Override
        public String getPublicId( )
        {
            return publicId;
        }


        @Override
        public String getStringData( )
        {
            return data;
        }


        @Override
        public String getSystemId( )
        {
            return systemId;
        }


        @Override
        public void setBaseURI( String aBaseURI )
        {
            baseSystemId = aBaseURI;
        }


        @Override
        public void setByteStream( InputStream aByteStream )
        {
            byteStream = aByteStream;
        }


        @Override
        public void setCertifiedText( boolean aCertifiedText )
        {
            certifiedText = aCertifiedText;
        }


        @Override
        public void setCharacterStream( Reader aCharacterStream )
        {
            charStream = aCharacterStream;
        }


        @Override
        public void setEncoding( String aEncoding )
        {
            encoding = aEncoding;
        }


        @Override
        public void setPublicId( String aPublicId )
        {
            publicId = aPublicId;
        }


        @Override
        public void setStringData( String aStringData )
        {
            data = aStringData;
        }


        @Override
        public void setSystemId( String aSystemId )
        {
            systemId = aSystemId;
        }
    }

}
