package LIMS::Model::Roles::QueryFormatter;

use Moose::Role;

has search_fields_map => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );
has relationships_map => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );

has search_constraint => ( is => 'rw', isa => 'HashRef', default => sub { {} });

use strict;
use Data::Dumper;

#-------------------------------------------------------------------------------
sub get_args_for_requests_count {
    my ($self, $search_constraints) = @_;  

    $self->search_constraint($search_constraints);

    # require data from these tables:
    my @tables = (); # none for count

    # format args_for_search:
    my $args_for_search = $self->_format_args_for_search(\@tables);

    return $args_for_search; # warn Dumper $args_for_search;
}

#-------------------------------------------------------------------------------
sub get_args_for_request_id { 
    my ($self, $search_constraints)  = @_;

    $self->search_constraint($search_constraints);

    # require data from these tables:
    my @tables = qw( patients specimens referrers referral_sources clinical_trials );

    # format args_for_search:
    my $args_for_search = $self->_format_args_for_search(\@tables);
    return $args_for_search; # warn Dumper $self->args_for_search;
}

#-------------------------------------------------------------------------------
# recieves $args hashref with keys 'search_constraints' & 'args_for_search':
sub get_args_for_find_requests {
    my $self = shift;    
	my $args = shift; # hashref with keys = 'search_constraints' & 'args_for_search'
    
    my $search_constraints = $args->{search_constraints}; # $self->debug($search_constraints);    
    
    # add $search_constraints to $self for _format_relationships():
    $self->search_constraint($search_constraints);
    
    # require data from these tables:
    my @tables = qw( patients referrers referral_sources );
    
    # format args_for_search:
    my @args = (\@tables, $args->{args_for_search});
    my $args_for_search = $self->_format_args_for_search(@args);
    
    return $args_for_search; # warn Dumper $self->args_for_search;
}

#-------------------------------------------------------------------------------
# accepts pairs of params & returns them in a format suitable for RDBO::Manager
sub format_query {
    my ($self, %params) = @_;

    # RBDO::Manager needs query as arrayref of pairs:
    return [ %params ];
}

#-------------------------------------------------------------------------------
# accepts arrayref of table_names, returns arrayref of relationship names:
sub get_relationships {
    my ($self, $tables) = @_; # arrayref
    
	my $relationships_map = $self->relationships_map;
    
	# get relationship names needed for table joins:
	my @relationships = map {
		$relationships_map->{$_}->{rel_name}
	} grep {
		# check entry exists (or rel silently skipped & db gets hit again for missing data):
		$relationships_map->{$_} || die "no such entry '$_' in _build_relationships_map()"
	} @$tables;
    
    return \@relationships;
}

#-------------------------------------------------------------------------------
# accepts $required_relationships arrayref and optional $args_for_search hashref
# or creates new; adds 'query' arrayref from $self->search_constraints; adds
# 'with_objects' & 'require_objects' arrayrefs if required; returns $args_for_search
sub _format_args_for_search {
    my $self            = shift;
    my $tables          = shift; # arrayref    
    my $args_for_search = shift || {}; # optional (contains limit, sort_by, etc)

    my $search_fields_map  = $self->search_fields_map; # warn Dumper $search_fields_map;    
    my $relationships_map  = $self->relationships_map;
    my $search_constraints = $self->search_constraint; # warn Dumper$search_constraints;  
    
    { # convert $search_constraints hashref format into arrayref for query func:
        my @params = %$search_constraints;
    
        # put 'query' into $args_for_search:
        $args_for_search->{query} = \@params;    
    }
    
    # convert $tables array(ref) into a hash to prevent duplicate relationships
    # in FIELD block below:
    my %tables = map { $_ => 1 } @$tables; # warn Dumper \%tables;    
    
    # get any additional table joins required for this query:
    FIELD:
    foreach my $field (keys %$search_constraints) { # warn $field;
        # get table name for 'key' of search_constraints (last_name, unit_number, etc):
        my $table_name = $search_fields_map->{$field}
        || next FIELD; # eg request_number, year - don't need additional table joins
        
        $tables{$table_name}++;        
    }  # warn Dumper \%tables;        

    # create 'with_objects' & 'require_objects' from data in %tables:
    foreach my $table (keys %tables) { # keys are unique so tbl only loaded once
        my $relationship_data = $relationships_map->{$table} # eg with => 'request_trial'
        || die "no such entry '$table' in _build_relationships_map()";
        
        my $join_type = $relationship_data->{join_type}; # 'with_objects' or 'require_objects'
        my $rel_name  = $relationship_data->{rel_name};  # eg 'patient_case.patient', etc
        
        # push relationship name onto $args_for_search $join_type key:
        push @{ $args_for_search->{$join_type} }, $rel_name;
    }

    if ( $args_for_search->{with_objects} ) {
        # needs multi_many_ok => 1 to silence warnings:
        $args_for_search->{multi_many_ok} = 1;
    } # warn Dumper $args_for_search;

	# set default 'order by':
	$args_for_search->{sort_by} ||= 'requests.id'; # not supplied in patient merge
	
    return $args_for_search;
}

#-------------------------------------------------------------------------------
sub _build_search_fields_map {
    my $self = shift;
    
    # create map of form field => $self->relationships_map name:
    my %search_fields_map = (
        # field_name        # in table
        last_name           => 'patients',
        first_name          => 'patients',
        dob                 => 'patients',
        nhs_number          => 'patients',
        unit_number         => 'patients',
        name                => 'referrers',
        specimen_id         => 'specimens',
        trial_id            => 'clinical_trials',        
        referral_source_id  => 'referral_sources',
		
		# for simple sql query:
		clinical_details    	=> 'request_report',
		comment             	=> 'request_report',
		detail              	=> 'request_gross_description',
		sample_code         	=> 'sample_code',
		'diagnoses.name'    	=> 'diagnoses',
        'referrers.name'    	=> 'referrers', # or could just use 'name' as above
		'display_name'      	=> 'referral_sources',
		'patients.id'       	=> 'patients',
		'unit_number'       	=> 'patient_case',
		'screens.description' 	=> 'request_initial_screen',
		results_summary       	=> 'results_summary',
        trial_name              => 'clinical_trials',        
    );

    return \%search_fields_map;    
}

#-------------------------------------------------------------------------------
# map of relationship names for tables, relative to LIMS::DB::Request:
sub _build_relationships_map {
    my $self = shift;
    
    my %relationship_map = (
      # table name => join-type / relationship name
		# require = 'inner join'; with = 'left outer join'
        clinical_trials => {
            join_type => 'with_objects',
            rel_name  => 'request_trial.trial',
        },
        diagnoses => {
            join_type => 'with_objects',
            rel_name  => 'request_report.diagnosis',
        },
		diagnostic_categories => {
			join_type => 'with_objects',
            rel_name  => 'request_report.diagnosis.icdo3_category.diagnostic_category',			
		},
        hospital_departments => {
            join_type => 'require_objects',
            rel_name  => 'referrer_department.hospital_department',
        },
		parent_organisations => {
            join_type => 'require_objects',
            rel_name  => 'patient_case.referral_source.parent_organisation',
		},
        patients => {
            join_type => 'require_objects',
            rel_name  => 'patient_case.patient',
        },
        patient_case => {
            join_type => 'require_objects',
            rel_name  => 'patient_case',
        },
        referral_sources => {
            join_type => 'require_objects',
            rel_name  => 'patient_case.referral_source',
        },
        referrers => {
            join_type => 'require_objects',
            rel_name  => 'referrer_department.referrer',
        },
        request_external_ref => {
            join_type => 'with_objects',
            rel_name  => 'request_external_ref',
        },
		request_general_notes => {
            join_type => 'with_objects',
            rel_name  => 'request_general_note',
		},
		request_history => {
            join_type => 'require_objects',
            rel_name  => 'request_history',
		},
        request_gross_description => {
            join_type => 'with_objects',
            rel_name  => 'request_gross_description',
        },
        request_initial_screen => {
            join_type => 'with_objects',
            rel_name  => 'request_initial_screen.screen',
        },
		request_report => {
            join_type => 'require_objects',
            rel_name  => 'request_report',
		},
        results_summary => {
            join_type => 'require_objects',
            rel_name  => 'results_summary',
        },
		sample_code => { # used by simple sql search only
            join_type => 'require_objects',
            rel_name  => 'request_specimen.specimen',
		},
        specimens => {
            join_type => 'require_objects',
            rel_name  => 'request_specimen',
        },
        status_options => {
            join_type => 'require_objects',
            rel_name  => 'status_option',
        },
    );
    
    return \%relationship_map;
}

1;
