

package gov.va.med.cds.template;


import gov.va.med.cds.clinicaldata.Operation;
import gov.va.med.cds.clinicaldata.TemplateMetaData;
import gov.va.med.cds.clinicaldata.TemplateMetaDataInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.tfs.util.TemplateMetaDataHelper;
import gov.va.med.cds.util.SearchPathGeneratorInterface;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;


public class TemplateCacheFileManager
    implements
        TemplateCachePersistenceInterface
{
    private Log logger = LogFactory.getLog( TemplateCacheFileManager.class );
    private PathMatchingResourcePatternResolver pathResolver = new PathMatchingResourcePatternResolver();
    private SearchPathGeneratorInterface searchPathGenerator = null;
    private boolean persist = false;


    public void setPersist( boolean persist )
    {
        this.persist = persist;
    }


    // this setter is needed for spring injection
    public void setSearchPathGenerator( SearchPathGeneratorInterface searchPathGenerator )
    {
        this.searchPathGenerator = searchPathGenerator;
    }


    /** Rewritten to use byte[] from TemplateMetaData instead of InputStream.  Not thoroughly tested since this class should be unused. */
    public void saveTemplateMetaDataToPersistence( TemplateMetaDataInterface aTemplateMetaData )
    {
        // Short circuit the persistence since it is usually being persisted to
        // the same place the TemplateServiceSimulator is reading from.
        if ( !persist )
        {
            return;
        }

        String newJarName = searchPathGenerator.getSearchPath() + File.separatorChar + aTemplateMetaData.getWriteCdsVdm() + File.separatorChar
                        + makeFileName( aTemplateMetaData.getTemplateName() + ".jar" );

        byte[] templateJar = aTemplateMetaData.getTemplateJar();
        FileOutputStream fos = null;

        URL url = null;
        try
        {
            url = new URL( newJarName );
        }
        catch ( MalformedURLException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Malformed URL : " + newJarName, e.getMessage() );
        }
        String fileName = url.getFile();
        File newJarFile = new File( fileName );
        try
        {
            if ( !newJarFile.exists() )
            {
                newJarFile.createNewFile();
            }
            /*
             * There needs to be a separation of the read and write here because during testing, the
             * TemplateServicePojoTestProxy is reading from the same file as this is writing to.
             */
            // synchronize instead if needed
            if ( templateJar != null && templateJar.length > 0 )
            {
                fos = new FileOutputStream( newJarFile );
                fos.write( templateJar );
                fos.flush();
            }
            if ( logger.isDebugEnabled() )
            {
                logger.debug( "File path: " + newJarName );
            }
        }
        catch ( FileNotFoundException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Could not find the file: " + newJarName, e.getMessage() );
        }
        catch ( IOException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Could not find the file: " + newJarName, e.getMessage() );
        }
        finally
        {
            if ( null != fos )
            {
                try
                {
                    fos.close();
                }
                catch ( IOException e )
                {
                    throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Problem closing the stream for : " + newJarName, e.getMessage() );
                }
                finally
                {
                    fos = null;
                }
            }
        }
        
        saveMetaData( aTemplateMetaData, newJarFile );
    }


    private void saveMetaData( TemplateMetaDataInterface templateMetaData, File newJarFile )
    {
    	FileOutputStream fos = null;
        String metaFilePath = null;

        try
        {
            // Save the meta data
            Properties metaProps = getMetaDataProperties( templateMetaData, newJarFile.getPath() );
            metaFilePath = newJarFile.getParentFile().getParent() + File.separator + newJarFile.getName().split( "\\.jar" )[0] + ".meta";
            fos = new FileOutputStream( metaFilePath );
            metaProps.store( fos, null );
        }
        catch ( FileNotFoundException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Failed to save the metaFile: " + metaFilePath, e.getMessage() );
        }
        catch ( IOException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Failed to save the metaFile: " + metaFilePath, e.getMessage() );
        }
        finally
        {
            if ( null != fos )
            {
                try
                {
//                    fos.flush();
                    fos.close();
                }
                catch ( IOException e )
                {
                    throw new TemplateCacheException( ErrorCodeEnum.CANNOT_SAVE_TEMPLATE, e, "Problem closing the stream for meta file : "
                                    + metaFilePath, e.getMessage() );
                }
            }
        }
    }


    public final void loadAllTemplatesIntoCache( TemplateCacheInterface aTemplateCache )
        throws TemplateCacheException
    {
        loadTemplateCacheFromPersistence( aTemplateCache, null );
    }


    public final void loadTemplateIntoCache( TemplateCacheInterface aTemplateCache, String aTemplateId )
        throws TemplateCacheException
    {
        loadTemplateCacheFromPersistence( aTemplateCache, aTemplateId );
    }


    public void loadTemplateCacheFromPersistence( TemplateCacheInterface templateCache, String aTemplateId )
        throws TemplateCacheException
    {
        Resource[] metaResources = null;

        if ( aTemplateId == null ) // if called during initialization of the system, get all template jars
        {
            metaResources = findMetaResourcesFromDefaultSearchPath( metaResources );
        }
        else
        // if called during runtime when a new template jar has been loaded, only get that jar
        {
            metaResources = findMetaResourcesFromSpecificTemplateId( aTemplateId, metaResources );
        }

        try
        {
            loadEachResourceIntoTemplateCache( templateCache, metaResources );
        }
        catch ( IOException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.COULD_NOT_CREATE_A_URL, e,
                            "Failed to create a resource URL while trying to create an error message.", e.getMessage() );
        }
    }


    private void loadEachResourceIntoTemplateCache( TemplateCacheInterface templateCache, Resource[] metaResources )
        throws IOException
    {
        String absolutePath = null;
        String templateFilePath = null;
        Resource[] schemaResources = null;
        for ( Resource metaResource : metaResources )
        {
            TemplateMetaDataInterface metaData = new TemplateMetaData();
            absolutePath = resolveMetaData( metaResource.getURL().toString(), metaData );
            if ( null != absolutePath && 0 < absolutePath.length() )
            {
                templateFilePath = TemplateCachePersistenceInterface.TEMPLATE_RESOURCE_PATH_PREFIX + absolutePath
                                + TemplateCachePersistenceInterface.TEMPLATE_RESOURCE_PATH_SUFFIX;
                try
                {
                    schemaResources = pathResolver.getResources( templateFilePath );
                }
                catch ( IOException e )
                {
                    throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                                    "Couldn't resolve template paths using: " + templateFilePath, e.getMessage() );
                }
                if ( logger.isDebugEnabled() )
                {
                    logger.debug( "about to load schema resource: " + schemaResources[0].getURL() );
                }
                // there should only be one template schema per jar
                try
                {
                    templateCache.loadSchemaIntoCache( metaData );
                }
                catch ( IOException e )
                {
                    throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                                    "Failed to load template schema from path: " + schemaResources[0].getURL(), e.getMessage() );
                }
            }
        }
    }


    private Resource[] findMetaResourcesFromSpecificTemplateId( String aTemplateId, Resource[] metaResources )
    {
        String searchPath = searchPathGenerator.getSearchPath() + "/" + aTemplateId + ".meta";

        return findMetaResourcesFromSpecificSearchPath( searchPath, metaResources );
    }


    private Resource[] findMetaResourcesFromSpecificSearchPath( String searchPath, Resource[] metaResources )
    {
        try
        {
            metaResources = pathResolver.getResources( searchPath );
        }
        catch ( IOException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                            "Couldn't get template meta data resources using: " + searchPath, e.getMessage() );
        }
        return metaResources;
    }


    private Resource[] findMetaResourcesFromDefaultSearchPath( Resource[] metaResources )
    {
        try
        {
            metaResources = pathResolver.getResources( searchPathGenerator.getSearchPath()
                            + TemplateCachePersistenceInterface.TEMPLATE_SEARCH_PATH_SUFFIX );
        }
        catch ( IOException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                            "Couldn't get template meta data resources using: " + searchPathGenerator.getSearchPath()
                                            + TemplateCachePersistenceInterface.TEMPLATE_SEARCH_PATH_SUFFIX, e.getMessage() );
        }
        return metaResources;
    }


    private String makeFileName( String templateName )
    {
        return templateName.replaceAll( "[^-.a-zA-Z_0-9]", "_" );
    }


    private Properties getMetaDataProperties( TemplateMetaDataInterface metaData, String jarPath )
    {
        Properties metaProps = new Properties();

        metaProps.setProperty( "file", jarPath );
        metaProps.setProperty( "id", metaData.getTemplateId() );

        // Encode all vhim versions into a comma separated String
        String encodedVhimVersions = "";
        String[] vhimVersions = metaData.getVhimVersions();
        if ( vhimVersions.length > 0 )
        {
            encodedVhimVersions = vhimVersions[0];
            for ( int i = 1; i < vhimVersions.length; i++ )
            {
                encodedVhimVersions += ( ", " + vhimVersions[i] );
            }
        }
        metaProps.setProperty( "vhimVersions", encodedVhimVersions );

        Collection<String> entryPoints = TemplateMetaDataHelper.getEntryPoints( metaData.getDomainEntryPoints() );
        if ( null != entryPoints )
        {
            metaProps.setProperty( "entryPoint", entryPoints.toString() );
        }
        metaProps.setProperty( "name", metaData.getTemplateName() );
        metaProps.setProperty( "requestType", metaData.getOperation().name() );
        metaProps.setProperty( "description", metaData.getTemplateDescription() );

        return metaProps;
    }


    private String resolveMetaData( String templatePropFileName, TemplateMetaDataInterface aTemplateMetaData )
    {
        String resourceFile = null;
        InputStream propInputStream = null;
        File templatePropFile;
        try
        {
            templatePropFile = new File( new URL( templatePropFileName ).getFile() );
        }
        catch ( MalformedURLException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.COULD_NOT_CREATE_A_URL, e, "Couldn't create a URL from: " + templatePropFileName,
                            e.getMessage() );
        }

        Properties templateProps = new Properties();

        try
        {

            propInputStream = new FileInputStream( templatePropFile );
            templateProps.load( propInputStream );
        }
        catch ( FileNotFoundException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                            "Failure loading property file for meta data: " + templatePropFileName, e.getMessage() );
        }
        catch ( IOException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                            "Failure loading property file for meta data: " + templatePropFileName, e.getMessage() );
        }
        catch ( java.lang.IllegalArgumentException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                            "Problem with the meta data property file format for file: " + templatePropFile
                                            + ". Probably due to a uuencoding problem in its content.", e.getMessage() );
        }
        finally
        {
            if ( null != propInputStream )
            {
                try
                {
                    propInputStream.close();
                }
                catch ( IOException e )
                {
                    throw new TemplateCacheException( ErrorCodeEnum.CANNOT_LOAD_SCHEMA_INTO_TEMPLATE_CACHE, e,
                                    "Failure property file stream for meta data: " + templatePropFileName, e.getMessage() );
                }
            }
        }

        try
        {
            resourceFile = new URL( "file", null, templateProps.getProperty( "file" ) ).toExternalForm();
        }
        catch ( MalformedURLException e )
        {
            throw new TemplateCacheException( ErrorCodeEnum.COULD_NOT_CREATE_A_URL, e, "Couldn't create a URL from the meta data property : "
                            + templateProps.getProperty( "file" ), e.getMessage() );
        }

        aTemplateMetaData.setTemplateId( templateProps.getProperty( "id" ) );
        String[] vhimVersions = TemplateMetaDataHelper.buildVhimVersions( templateProps.getProperty( "vhimVersion" ) );
        aTemplateMetaData.setVhimVersions( vhimVersions );
        aTemplateMetaData.setDomainEntryPoints( TemplateMetaDataHelper.buildEntryPoints( templateProps.getProperty( "entryPoint" ) ) );
        aTemplateMetaData.setTemplateName( templateProps.getProperty( "name" ) );
        aTemplateMetaData.setOperation( Operation.valueOf( templateProps.getProperty( "requestType" ) ) );
        // TODO is this still needed --> metaData.setTargetNamespace( templateProps.getProperty( "targetNamespace" )
        // );
        aTemplateMetaData.setTemplateDescription( templateProps.getProperty( "description" ) );
        // TODO is this still needed --> metaData.setTestOnly( templateProps.getProperty( "testOnly", "false" ) );

        return resourceFile;
    }

}
