/**
 * 
 */


package gov.va.med.cds.persistence.hibernate;


import gov.va.med.cds.filter.EntryFilterInterface;
import gov.va.med.cds.persistence.QueryAssociationInterface;
import gov.va.med.cds.template.generated.JaxBMarshallerUnmarshallerInterface;

import java.util.List;
import java.util.Map;

import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.hibernate.Query;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;


/**
 * @author DNS   egberb
 *
 */
public class PageableQueryWork
    extends
        DefaultQueryWork
{
    
    private static final int INDEX_OFFSET_BY_1 = 1;

   
    public PageableQueryWork( Session session, QueryAssociationInterface queryAssociation, EntryFilterInterface entryFilter,
                    QueryNameBuilderInterface queryNameBuilder, FilterParameterExtractorInterface filterParameterExtractor,
                    Map<String, QueryParameterTransformerInterface> parameterTransformerMap, QueryParameterBinderInterface queryParameterBinder, List<String> personIdentifiers,
                    Class<?> pointInTimeUserType, String applicationName, String siteId )
    {
        super( session, queryAssociation, entryFilter, queryNameBuilder, filterParameterExtractor, parameterTransformerMap, queryParameterBinder, personIdentifiers,
                        pointInTimeUserType, applicationName, siteId );
    }

    public PageableQueryWork( Session session, QueryAssociationInterface queryAssociation, EntryFilterInterface entryFilter,
                    QueryNameBuilderInterface queryNameBuilder, FilterParameterExtractorInterface filterParameterExtractor,
                    Map<String, QueryParameterTransformerInterface> parameterTransformerMap, QueryParameterBinderInterface queryParameterBinder, Map<String, String> templateTimeoutMap,
                    List<String> personIdentifiers, Class<?> pointInTimeUserType, String applicationName, String siteId, JaxBMarshallerUnmarshallerInterface marshallerUnmarshaller )
    {
        super( session, queryAssociation, entryFilter, queryNameBuilder, filterParameterExtractor, parameterTransformerMap, queryParameterBinder, templateTimeoutMap,
                        personIdentifiers, pointInTimeUserType, applicationName, siteId, marshallerUnmarshaller );
    }

    /* (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run( )
    {
        long startTime = System.currentTimeMillis();

        try
        {
            Query query = prepareQuery( session, queryAssociation.getAssociationName(), entryFilter, personIdentifiers );

            if ( query != null )
            {
                startTime = System.currentTimeMillis();

                query.setReadOnly( true );

                // get record count first
                ScrollableResults scrollableResults = query.scroll();

                if ( scrollableResults.last() ) // returns true if there are records in the result set 
                {
                    int indexOfLast = scrollableResults.getRowNumber();     // the index of the last record in the total (non-paged) result.
                    int pagingOffset = 0;                                   // paging offset used to calculate record positions.

                    if ( !entryFilter.isCountOnly() )
                    {
                        if ( entryFilter.isPagingFilter() && isEntryPointQuery() )
                        {
                            // setup the parameters for the page size.
                            pagingOffset = (entryFilter.getPageNumber() - INDEX_OFFSET_BY_1) * entryFilter.getPageSize();
                            query.setFirstResult( pagingOffset );

                            if ( entryFilter.getPageSize() > 0 )
                            {
                                query.setMaxResults( entryFilter.getPageSize() );
                            }

                            // re-scroll so that we can get the page of data requested.
                            scrollableResults = query.scroll();
                        }

                        // move to the first record in preparation for reading the results into the list of results held by the member.
                        scrollableResults.first(); 
                        
                        // initialize the first and last row number.
                        int firstRowNumber = scrollableResults.getRowNumber() + pagingOffset;
                        int lastRowNumber = firstRowNumber;

                        do
                        {
                            this.results.add( ( Element )scrollableResults.get()[0] );
                        }
                        while ( scrollableResults.next() );
                        
                        scrollableResults.last();
                        lastRowNumber = scrollableResults.getRowNumber() + pagingOffset;
                        
                        addPagingInfoToResults( firstRowNumber, lastRowNumber, indexOfLast );

                    }
                    else
                    // Count Only Response
                    {
                        // it's a count only query. Add the result record count to the response if this is the entry point query.
                        addPagingInfoToResults( 0, 0, indexOfLast );
                    }
                }

                long finishTime = System.currentTimeMillis();
                if ( performanceLog.isInfoEnabled() )
                {
                    performanceLog.logFinishTime( startTime, finishTime, "DefaultQueryWork.run()", entryFilter.getDomainEntryPoint(),
                                    entryFilter.getTemplateId(), entryFilter.getRequestId(), siteId, true, "jdbc" );
                }
            }
        }
        catch ( Exception ex )
        {
            long finishTime = System.currentTimeMillis();
            if ( performanceLog.isInfoEnabled() )
            {
                performanceLog.logFinishTime( startTime, finishTime, "DefaultQueryWork.run()", entryFilter.getDomainEntryPoint(),
                                entryFilter.getTemplateId(), entryFilter.getRequestId(), siteId, false, "jdbc" );
            }

            //LOGGER.error( "************************************* TIMEOUT EXCEPTION *********************************************************" );
            addException( ex, applicationName );
        }

    }
    
    /**
     * Method to test whether or not to include the count and paging logic with the query results. 
     * @return If the query association's filter model name is equal to the entry filter's domain entry point name (case insensitive) then return true; otherwise
     * return false.
     */
    private boolean isEntryPointQuery( )
    {
        return queryAssociation.getFilterModelName() != null && this.queryAssociation.getFilterModelName().equalsIgnoreCase( entryFilter.getDomainEntryPoint() );
    }
    
    private void addPagingInfoToResults(int indexPageFirstResult, int indexPageLastResult, int indexLastResult)
    {
        if(isEntryPointQuery())
        {
            if(this.entryFilter.isPagingFilter())
            {
                Element pageInfoElement = DocumentHelper.createElement( "pageInformation" );
                Element pageFirstRecordElement = DocumentHelper.createElement( "pageFirstRecord" );
                Element pageLastRecordElement = DocumentHelper.createElement( "pageLastRecord" );
        
                // add the paging related info to the result info element
                pageInfoElement.add( pageFirstRecordElement );
                pageInfoElement.add( pageLastRecordElement );
                
                // these values are only included in the results if the filter is a page-able filter.
                pageFirstRecordElement.setText( String.valueOf( indexPageFirstResult+INDEX_OFFSET_BY_1 ) );
                pageLastRecordElement.setText( String.valueOf( indexPageLastResult+INDEX_OFFSET_BY_1 ) );
                
                // add the result info to the response for paging filters
                this.results.add( pageInfoElement );
            }

            Element totalResultsElement = DocumentHelper.createElement( "totalResultCount" );
            totalResultsElement.setText( String.valueOf(indexLastResult+INDEX_OFFSET_BY_1) );
        
            this.results.add( totalResultsElement );
        }
    }

}
