class TrackedEntityCriteriaMapper {

    @Transactional( readOnly = true )
    public TrackedEntityInstanceQueryParams map( TrackedEntityInstanceCriteria criteria )
    {
        TrackedEntityInstanceQueryParams params = new TrackedEntityInstanceQueryParams();

        final Date programEnrollmentStartDate = ObjectUtils.firstNonNull( criteria.getProgramEnrollmentStartDate(),
            criteria.getProgramStartDate() );

        final Date programEnrollmentEndDate = ObjectUtils.firstNonNull( criteria.getProgramEnrollmentEndDate(),
            criteria.getProgramEndDate() );

        Set<OrganisationUnit> possibleSearchOrgUnits = new HashSet<>();

        User user = currentUserService.getCurrentUser();

        if ( user != null )
        {
            possibleSearchOrgUnits = user.getTeiSearchOrganisationUnitsWithFallback();
        }

        QueryFilter queryFilter = getQueryFilter( criteria.getQuery() );

        if ( criteria.getAttribute() != null )
        {
            for ( String attr : criteria.getAttribute() )
            {
                QueryItem it = getQueryItem( attr );

                params.getAttributes().add( it );
            }
        }

        if ( criteria.getFilter() != null )
        {
            for ( String filt : criteria.getFilter() )
            {
                QueryItem it = getQueryItem( filt );

                params.getFilters().add( it );
            }
        }

        for ( String orgUnit : criteria.getOrgUnits() )
        {
            OrganisationUnit organisationUnit = organisationUnitService.getOrganisationUnit( orgUnit );

            if ( organisationUnit == null )
            {
                throw new IllegalQueryException( "Organisation unit does not exist: " + orgUnit );
            }

            if ( !organisationUnitService.isInUserHierarchy( organisationUnit.getUid(), possibleSearchOrgUnits ) )
            {
                throw new IllegalQueryException( "Organisation unit is not part of the search scope: " + orgUnit );
            }

            params.getOrganisationUnits().add( organisationUnit );
        }

        validateAssignedUser( criteria );

        if ( criteria.getOuMode() == OrganisationUnitSelectionMode.CAPTURE && user != null )
        {
            params.getOrganisationUnits().addAll( user.getOrganisationUnits() );
        }
        Program program = validateProgram( criteria );
        params.setQuery( queryFilter )
            .setProgram( program )
            .setProgramStage( validateProgramStage( criteria, program) )
            .setProgramStatus( criteria.getProgramStatus() )
            .setFollowUp( criteria.getFollowUp() )
            .setLastUpdatedStartDate( criteria.getLastUpdatedStartDate() )
            .setLastUpdatedEndDate( criteria.getLastUpdatedEndDate() )
            .setLastUpdatedDuration( criteria.getLastUpdatedDuration() )
            .setProgramEnrollmentStartDate( programEnrollmentStartDate )
            .setProgramEnrollmentEndDate( programEnrollmentEndDate )
            .setProgramIncidentStartDate( criteria.getProgramIncidentStartDate() )
            .setProgramIncidentEndDate( criteria.getProgramIncidentEndDate() )
            .setTrackedEntityType( validateTrackedEntityType( criteria ) )
            .setOrganisationUnitMode( criteria.getOuMode() )
            .setEventStatus( criteria.getEventStatus() )
            .setEventStartDate( criteria.getEventStartDate() )
            .setEventEndDate( criteria.getEventEndDate() )
            .setAssignedUserSelectionMode( criteria.getAssignedUserMode() )
            .setAssignedUsers( criteria.getAssignedUsers() )
            .setSkipMeta( criteria.isSkipMeta() )
            .setPage( criteria.getPage() )
            .setPageSize( criteria.getPageSize() )
            .setTotalPages( criteria.isTotalPages() )
            .setSkipPaging( PagerUtils.isSkipPaging( criteria.getSkipPaging(), criteria.getPaging() ) )
            .setIncludeDeleted( criteria.isIncludeDeleted() )
            .setIncludeAllAttributes( criteria.isIncludeAllAttributes() )
            .setUser( user )
            .setOrders( getOrderParams( criteria ) );

        return params;

    }

    public  TrackedEntityCriteriaMapper( CurrentUserService currentUserService,
        OrganisationUnitService organisationUnitService, ProgramService programService,
        TrackedEntityAttributeService attributeService, TrackedEntityTypeService trackedEntityTypeService );

}