package LIMS::Model::Roles::ScreenUpdate;

use Moose::Role;

has status_option_new => (
    is => 'ro',
    isa => 'LIMS::DB::LabTestStatusOption',
    lazy_build => 1,
);

has skipped_lab_tests => ( 
    is      => 'ro',
    isa     => 'ArrayRef[Int]',
    default => sub { [] },
    lazy    => 1,
	traits  => ['Array'],
	handles => {
		add_to_skipped_lab_tests => 'push',
        have_skipped_lab_tests   => 'elements',
	},
);

has requested_lab_tests => ( 
    is      => 'ro',
    isa     => 'ArrayRef[Int]',
    default => sub { [] },
    lazy    => 1,
	traits  => ['Array'],
	handles => {
		add_to_requested_lab_tests => 'push',
        have_requested_lab_tests   => 'elements',
	},
);

=begin - for _have_required_sample_type_for_lab_test() - replaced with LIMS::DB queries 
has $_ => ( is => 'ro', isa => 'HashRef', lazy_build => 1 )
    for qw(
        specimen_sample_type_map
        section_sample_type_map
        lab_test_section_map
    );
=cut

use Data::Dumper;

#-------------------------------------------------------------------------------
# creates new lab_tests according to ScreenLabTest data for this screen_id
# doesn't overwrite existing tests
sub do_lab_tests {
    my $self = shift;
    my $data = shift;
    
    my %args = (
        query => [ screen_id => $data->{screen_id} ],
    );

    my $lab_tests
        = LIMS::DB::ScreenLabTest::Manager->get_screen_lab_tests(%args);

    foreach my $t (@$lab_tests) { # warn Dumper $t;
        $data->{lab_test_id} = $t->lab_test_id;
        $self->do_new_lab_test($data);
    }
}

#-------------------------------------------------------------------------------
sub do_new_lab_test { # shared by M::Request::new_request()
    my $self = shift;
    my $data = shift; # requires lab_test_id && user_id
   
    # if sample type checking is active, check we have valid specimen for lab test:
    if ( $self->lims_cfg->{settings}->{lab_section_sample_type} ) {
        return 0 unless $self->_have_required_sample_type_for_lab_test($data);
    }    
   
    my $status_option = $self->status_option_new;
    my $lab_test_id   = $data->{lab_test_id};
    
    my $o = LIMS::DB::RequestLabTestStatus->new(
        status_option_id => $status_option->id,
        lab_test_id      => $lab_test_id,
        request_id       => $data->{_request_id},
        user_id          => $self->user_profile->{id},
    );
    
    if ( $o->load(speculative => 1) ) { # already exists so just add to skipped:
       $self->add_to_skipped_lab_tests($lab_test_id)
    }
    else {
        $o->save;
        $self->add_to_requested_lab_tests($lab_test_id);
    }
}

#-------------------------------------------------------------------------------
sub do_lab_test_details {
    my $self = shift;
    my $data = shift;
    
    # if sample type checking is active, check we have valid specimen for lab test:
    if ( $self->lims_cfg->{settings}->{lab_section_sample_type} ) {
        return 0 unless $self->_have_required_sample_type_for_lab_test($data);        
    }    

    my %args = (
        query => [ screen_id => $data->{screen_id} ],
    );

    my $lab_test_details
        = LIMS::DB::ScreenLabTestDetail::Manager->get_screen_lab_test_details(%args);
    
    foreach my $detail (@$lab_test_details) { # should only be 0 or 1:
        LIMS::DB::RequestLabSectionNote->new(
            details        => $detail->test_details,
            request_id     => $data->{_request_id},
            lab_section_id => $detail->lab_section_id,
        )->load_or_insert; # in case test_detail already exists
    }
}

#-------------------------------------------------------------------------------
sub do_auto_screen {
    my $self = shift;

    # get hashref or required data (keys = lab_tests, presentation & trial_name):
    my $auto_screen_data = $self->_get_auto_screen_data();
	return 0 unless %$auto_screen_data; # warn Dumper $auto_screen_data;

	my $data = $self->form_data; # warn Dumper $data;
    
    my $request_id = $data->{_request_id};
    my $user_id = $self->user_profile->{id};
    
    # first save presentation info:
    if ( my $presentation = $auto_screen_data->{presentation} ) {
        my $description = $presentation->{description};
        my $category    = $presentation->{category};
        
        my @args = (
            query => [
                description => $description,
                name        => $category,
            ],
            require_objects => 'category',
        );
        
        my $screen = LIMS::DB::Screen::Manager->get_screens(@args)->[0];
        
        my %data = (
            request_id => $request_id,
            screen_id  => $screen->id,
        ); # warn Dumper \%data;
        LIMS::DB::RequestInitialScreen->new(%data)->save;
        
        # update request status:
        $self->update_request_status('screened', $request_id);
    }
    # if trial name required:
    if ( my $trial_name = $auto_screen_data->{trial_name} ) {
        my $trial_id
            = LIMS::DB::ClinicalTrial->new( trial_name => $trial_name)->load->id;
        my %data = (
            request_id => $request_id,
            trial_id   => $trial_id,
        );
        LIMS::DB::RequestTrial->new(%data)->load_or_insert; # in case already set in form
    }
    # if lab tests generated:
    if ( my $lab_tests = $auto_screen_data->{lab_tests} ) {
        while ( my($lab_test_id, $test_name) = each %$lab_tests ) {
            my %data = (
                _request_id => $request_id,
                lab_test_id => $lab_test_id,
            );
            $self->do_new_lab_test(\%data);
        }
    }
    $self->add_to_actions('screened');
}

#-------------------------------------------------------------------------------
sub additional_options {
    my ($self, $action) = @_; # warn Dumper $action;
    
    my $data = $self->form_data; # warn Dumper $data;
    
    my $cfg = $data->{additional_options_config}->{$action} || return 0;

    # get 'screened_as' description:
    my $presentation = LIMS::DB::Screen->new(id => $data->{screen_id})->load;
    my $screened_as = $presentation->description; # warn $screened_as;
    
    # cycle through options to see if screened_as term triggers option flag: 
    while ( my($option, $d) = each %$cfg ) { # warn Dumper [$option, $d];
        # check option (eg DoI) exists and is active:
        my $o = LIMS::DB::AdditionalOption->new(option_name => $option)
            ->load(speculative => 1); # warn Dumper $o->as_tree;
        return 0 unless $o && $o->is_active eq 'yes';

        # set option flag if screened_as term matches one of list:
        if ( grep lc($screened_as) eq lc($_), @$d ) {
            my %data = (
                request_id => $data->{_request_id},
                option_id  => $o->id,
            ); # warn Dumper \%data;
            LIMS::DB::RequestOption->new(%data)->load_or_insert;
        }        
    }
}

#-------------------------------------------------------------------------------
sub _get_auto_screen_data {
    my $self = shift;

    my $form_data = $self->form_data; # warn Dumper $form_data;
    
    my $cfg = $form_data->{auto_screen_config}; # warn Dumper $cfg;

    my $request_id = $form_data->{_request_id};

    # get full request data (request, patient_case & patient tables) as a flat
    # data stucture, in case we need to find any mandatory field (eg nhs_number):
    my $request_data = do {
        my $r = LIMS::DB::Request->new(id => $request_id)
            ->load(with => 'patient_case.patient')->as_tree;
        LIMS::Local::Utils::traverse_hash($r); # uses Data::Traverse to flatten $r
    }; # warn Dumper $request_data;
    
    my %data = ();

    RULE: while ( my ($rule, $d) = each %$cfg ) {
        # check any mandatory request/patient fields (eg nhs_number):
        if ( my $required_fields = $d->{required_fields} ) {
            for my $field(@$required_fields) { # warn $field;
                # try to find required value in submitted form data, or else in
                # request/patient_case/patient data fields:
                next RULE unless $form_data->{$field} || $request_data->{$field};
            }
        }

        my $match_criteria = $d->{match_criteria}; # HoH
        
        # $match_criteria has 1 or more hashref entries:
        while ( my ($field, $value) = each %$match_criteria ) {
            # a match on specimen takes priority - may also generate trial name:
            if ( $field eq 'specimen_type' ) {
                # check specimen type(s) match:
                my $required_specimen_types = $value; # always arrayref
                for my $specimen(@$required_specimen_types) { # warn $specimen;
                    # may need to split specimens and test as array (eg BMA vs BMAT)
                    next RULE unless $form_data->{specimen} =~ /$specimen/i;
                }
            }
            elsif ( $field eq 'trial_name' ) {
                # get trial id from form, or skip rule:
                my $trial_id = $form_data->{trial_id} || next RULE;
                my $trial # get trials.trial_name for trial id:
                    = LIMS::DB::ClinicalTrial->new(id => $trial_id)->load;
                # skip rule unless submitted trial is in config:
                next RULE unless $value eq $trial->trial_name;
            }
            else {
                next RULE; # no matches
            }
        }
        
        # add config presentation & trial_name fields to %data (unless already set
        # from previous cycle through RULES loop, so ensure rules not inconsistent):
        map { $data{$_} ||= $d->{$_} } grep $d->{$_}, qw( presentation trial_name );
        # add lab tests:
        if ( my $lab_tests = $d->{lab_tests} ) {
            $data{lab_tests}{$_} = $lab_tests->{$_} for keys %$lab_tests;
        }
    } # warn Dumper \%data;
    return \%data;
}

#-------------------------------------------------------------------------------
sub _have_required_sample_type_for_lab_test {
    my ($self, $data) = @_;
    # what lab section does new lab-test belong to?
    my $lab_test_id = $data->{lab_test_id} || return 0; # warn Dumper $lab_test_id;
    
=begin - section replaced by LIMS::DB queries now - uncomment _build_* subs if required
    my $lab_section_id = $self->lab_test_section_map->{$lab_test_id};

    # what specimen type(s) does the lab section support (arrayref):
    my $section_sample_type_map = $self->section_sample_type_map; # hashref of [sample_type ids]
    my $required_specimen_type = $section_sample_type_map->{$lab_section_id};

    # what specimen type id(s) do we have for this request:
    my %sample_type_ids = ();
    {  # get map of unique sample-type ids for this request:
        my $specimen_id = $self->_get_specimen_id($data->{_request_id}); # arrayref
        my $specimen_sample_type_map = $self->specimen_sample_type_map;        

        foreach my $id (@$specimen_id) {
            my $ary = $specimen_sample_type_map->{$id}; # arrayref
            $sample_type_ids{$_}++ for @$ary;
        } # warn Dumper \%sample_type_ids;
    }

    # set true if one of available sample types matches a required type:
    my $ok = ( grep $sample_type_ids{$_}, @$required_specimen_type );
=cut

=begin # required sql to do above in one go:
    my $sql = q!
    select count(*)
    from request_specimen t4
        join specimen_sample_type t5 on (t5.specimen_id = t4.specimen_id)
    where 
        t4.request_id = ? and
        t5.sample_type_id in (
            select distinct(t3.sample_type_id)
            from lab_tests t1 
                join lab_sections t2 on (t1.lab_section_id = t2.id)
                join lab_section_sample_type t3 on (t3.lab_section_id = t2.id)
            where t1.id = ?
        )!;
=cut # Rose can't do sub-selects, so split query into 2 parts:

    # part 1 - get distinct sample_type_id's:
    my $lab_section_sample_type = do {
        my %args = (
            require_objects => [ qw( lab_section.lab_tests ) ],
            select => [ 'sample_type_id' ],
            query  => [ 'lab_tests.id' => $lab_test_id ],
            distinct => 1,
        );        
        LIMS::DB::LabSectionSampleType::Manager
            ->get_lab_section_sample_types(%args);
    }; # warn Dumper $lab_section_sample_type;
    
    my @sample_type_ids = map { $_->sample_type_id } @$lab_section_sample_type;
    
    # part 2 - count how many request_specimen rows contain required sample_type_id's:
    my $count = do {
        my %args = (
            query => [
                request_id     => $data->{_request_id},
                sample_type_id => \@sample_type_ids,
            ],
            require_objects => [ 'specimen.specimen_sample_type' ],
        );
        LIMS::DB::RequestSpecimen::Manager->get_request_specimens_count(%args);
    }; # warn Dumper $count;
    
    # if we don't have at least 1 required sample type, add lab_test.id to failed list:
    if (! $count) { $self->add_to_skipped_lab_tests($lab_test_id) }        

    return $count; # returns true if $count > 0
}

#-------------------------------------------------------------------------------
sub _get_specimen_id {
    my ($self, $request_id) = @_;
    
    my %args = (
        query => [ request_id => $request_id ],
        select => 'specimen_id',
    );
    my $o = LIMS::DB::RequestSpecimen::Manager->get_request_specimens(%args);
    return [ map $_->specimen_id, @$o ];
}

#-------------------------------------------------------------------------------
sub _build_status_option_new {
    my $self = shift;
    
    my $status_option  
        = LIMS::DB::LabTestStatusOption->new(description => 'new')->load;
        
    return $status_option;
}

=begin - not required, using LIMS::DB queries in _have_required_sample_type_for_lab_test
#-------------------------------------------------------------------------------
sub _build_section_sample_type_map { # return HoA of lab_section.id => [ sample_type.id ]
    my $self = shift;
    
    my $o = $self->get_objects_iterator('LabSectionSampleType');
    
    my $map = {};    
    while ( my $t = $o->next ) {
        my $lab_section_id = $t->lab_section_id;
        my $sample_type_id = $t->sample_type_id;
        push @{ $map->{$lab_section_id} }, $sample_type_id;
    }   
    return $map;
}

#-------------------------------------------------------------------------------
sub _build_specimen_sample_type_map { # return HoA of specimen.id => [ sample_type.id ]
    my $self = shift;
    
    my $o = $self->get_objects_iterator('SpecimenSampleType');
    
    my $map = {};    
    while ( my $t = $o->next ) {
        my $specimen_id = $t->specimen_id;
        my $sample_type_id = $t->sample_type_id;
        push @{ $map->{$specimen_id} }, $sample_type_id;
    }   
    return $map;
}

#-------------------------------------------------------------------------------
sub _build_lab_test_section_map {
    my $self = shift;    
    
    my $o = $self->get_objects_iterator('LabTest');
    
    my $map = {};
    while ( my $t = $o->next ) {
        $map->{$t->id} = $t->lab_section_id;
    }
    return $map;
}
=cut

1;