

package gov.va.med.cds.filter;


import gov.va.med.cds.clinicaldata.FilterMetaDataInterface;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exception.FilterCacheException;
import gov.va.med.cds.request.ValidationException;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.SAXException;


/**
 * This class will orchestrate the loading of the Filter Memory Cache from the database. It also validates a given
 * filter XML against the appropriate Filter Schema.
 * 
 */
public class FilterManager
    implements
        FilterManagerMBeanInterface,
        FilterValidatorInterface
{

    private FilterCachePersistenceInterface filterCachePersistence;
    private FilterMemoryCacheInterface filterMemoryCache;
    private FilterServiceInterface filterService = null;

    private static final Log LOGGER = LogFactory.getLog( FilterManager.class );


    /**
     * Constructor for the FilterManager
     * 
     * @param aFilterCachePersistence
     * @param aFilterMemoryCache
     */
    public FilterManager( FilterServiceInterface aFilterService, FilterCachePersistenceInterface aFilterCachePersistence,
                    FilterMemoryCacheInterface aFilterMemoryCache )
    {
        LOGGER.debug( "FilterManager started..." );
        filterService = aFilterService;
        filterCachePersistence = aFilterCachePersistence;
        filterMemoryCache = aFilterMemoryCache;

        initialize();
    }


    /**
     * Load all of the Filter Schema data into memory cache from database.
     * 
     */
    private void initialize( )
    {
        try
        {
            filterService.initialize();
            persistFiltersFromService();
        }
        catch ( Exception e )
        {
            //VHIM Filter Service unavailable - will just load cache from tfrlocal(tfrlocal3)
            LOGGER.warn( "Filter Service Initialization failed", e );
        }

        // loads all filter validators into cache
        filterCachePersistence.loadAllFilterSchemasIntoMemoryCache( filterMemoryCache );
    }


    /**
     * Validates a given XML Filter against the associated Filter Schema found in the Filter Memory Cache.
     */
    public void validateFilterXml( String filterId, String filterXml )
    {

        // Validator filterSchemaValidator = filterMemoryCache.getFilterSchemaValidator( filterId );
        Schema filterSchema = filterMemoryCache.getFilterSchema( filterId );

        if ( filterSchema == null )
        {
            synchronized ( this.filterMemoryCache )
            {
                // load filterSchema from persistence
                filterCachePersistence.loadFilterIntoMemoryCache( filterId, filterMemoryCache );
                filterSchema = filterMemoryCache.getFilterSchema( filterId );

                if ( filterSchema == null )
                {
                    retrieveAndPersistFilterFromService( filterId );
                    filterCachePersistence.loadFilterIntoMemoryCache( filterId, filterMemoryCache );
                    filterSchema = filterMemoryCache.getFilterSchema( filterId );
                    if ( filterSchema == null )
                    {
                        throw new FilterCacheException( ErrorCodeEnum.UNABLE_TO_READ_FILTER_SCHEMA, filterId );
                    }
                }
            }
        }

        // The validator is available
        try
        {
            // InputStream xmlResource = new ByteArrayInputStream( filterXml.getBytes() );
            // filterSchemaValidator.validate( new StreamSource( xmlResource ) );

            // Validator & SchemaFactory are not thread safe so create each time * .2ms
            Validator filterSchemaValidator = filterSchema.newValidator();
            filterSchemaValidator.validate( new StreamSource( new StringReader( filterXml ) ) );
        }
        catch ( SAXException e )
        {
            throw new ValidationException( ErrorCodeEnum.INVALID_FILTER_SCHEMA, e, filterId,
                            ( ExceptionUtils.getRootCause( e ) == null ? e.getMessage() : ExceptionUtils.getRootCause( e ).getMessage() ) );
        }
        catch ( IOException e )
        {
            throw new FilterCacheException( ErrorCodeEnum.UNABLE_TO_READ_FILTER_SCHEMA, e, filterId, e.getMessage() );
        }

    }


    /**
     * Management method that clears and initializes the Filter Memory Cache.
     * 
     * ** Control this method as it can alter filterMemoryCache during validateFilterXml processing and give filter not found error
     */
    public synchronized void reinitializeFilterMemoryCache( )
    {
        FilterMemoryCacheInterface tempCache = filterMemoryCache;
        try
        {
            clearFilterMemoryCache();
            initialize();
        }
        catch ( Exception e )
        {
            filterMemoryCache = tempCache;
        }
    }


    /**
     * Management method used to clear out the cache from memory and as the FilterManager is accessed. Filter Schemas
     * will reload when they are required.
     * 
     * ** Control this method as it can alter filterMemoryCache during validateFilterXml processing and give filter not found error
     */
    public synchronized void clearFilterMemoryCache( )
    {
        if ( null != filterMemoryCache )
        {
            if ( !filterMemoryCache.isEmpty() )
            {
                filterMemoryCache.clear();
            }
        }
    }


    /**
     * Management method used to reset cache size from its current size.
     * 
     * If <newSize> is increasing then the existing or current filters in memory cache will remain and new ones added as
     * they are used/accessed until the capacity is reached and then the least used schema is discarded as new Filter
     * Schemas are accessed.
     * 
     * If <newSize> is decreasing then this method will reinitialize the cache to ne new size and load to that capacity.
     * Filter Schemas accessed that are not in cache will be replace the least used Schema.
     * 
     * ** Control this method as it can alter filterMemoryCache during validateFilterXml processing and give filter not found error
     */
    public synchronized void resizeFilterMemoryCache( int newSize )
    {
        if ( getFilterCacheThreshold() > newSize )
        {
            this.filterMemoryCache.reSize( newSize );
            // reducing filters in memory cache so reinitialize
            reinitializeFilterMemoryCache();
        }
        else
        {
            this.filterMemoryCache.reSize( newSize );
        }
    }


    /**
     * Management method used to find out the current size of filter cache size
     * 
     * Gets the no. of schemas contained in Filter Memory Cache map.
     */
    public int getNumberOfFilterSchemasLoadedIntoFilterCache( )
    {
        return filterMemoryCache.getNumberOfFilterSchemasLoadedIntoFilterCache();
    }


    /**
     * Management method used to find out the current capacity of filter cache map
     * 
     * Gets the size of Filter Memory Cache threshold.
     */
    public int getFilterCacheThreshold( )
    {
        return filterMemoryCache.getFilterCacheThreshold();
    }


    /**
     * gets available list of filterIds in filter cache
     */
    public List<String> getFilterCacheFilterIds( )
    {
        List<String> list = new ArrayList<String>( filterMemoryCache.getFilterCacheFilterIds() );

        return list;
    }


    private void persistFiltersFromService( )
    {
        Collection<String> filterIds = null;
        Collection<String> vhimVersions = filterService.getVhimVersions();

        for ( String vhimVersionName : vhimVersions )
        {
            filterIds = filterService.getActiveFilterIds( vhimVersionName );

            if ( LOGGER.isDebugEnabled() )
            {
                LOGGER.debug( "looping on vhim version: " + vhimVersionName );
            }

            for ( String filterId : filterIds )
            {
                if ( LOGGER.isDebugEnabled() )
                {
                    LOGGER.debug( "\tRetrieving and Persisting filter from service, filter ID: " + filterId );
                }
                retrieveAndPersistFilterFromService( filterId );
            }
        }
    }


    private void retrieveAndPersistFilterFromService( String aFilterId )
    {
        try
        {
            FilterMetaDataInterface filterMetaData = filterService.getFilterMetaData( aFilterId );

            if ( filterMetaData != null )
            {
                filterMetaData.setDateUpdated( new Date() );
                filterCachePersistence.persistFilterSchema( filterMetaData );
            }
        }
        catch ( FilterServiceException e )
        {
            //Ignore exception to continue loading other templates (error captured in client)
            if ( LOGGER.isDebugEnabled() )
            {
                LOGGER.debug( "FilterServiceException in retrieveAndPersistFilterFromService, filter ID: " + aFilterId + " Exception Message: "
                                + e.getMessage() );
            }
        }
        catch ( Exception e )
        {
            //Ignore exception to continue loading other filters
            LOGGER.warn( "Exception in retrieveAndPersistFilterFromService, filter ID: " + aFilterId + " Exception Message: " + e.getMessage() );
        }
    }

}
