package LIMS::Controller::Roles::DataMap;

use Sort::Naturally;
use Data::Dumper;
use Moose::Role;

# most methods take arrayref of request_ids, return hashref of request.id => function

#-------------------------------------------------------------------------------
# maps request to its specimen(s) via request.id => [ sample_code ]
sub specimen_map {
    my $self = shift;
    my $request_ids = shift; # arrayref of request_ids

    return undef unless @$request_ids; # or will die in get_request_specimens();

    # get request_specimen object, ie:
    # SELECT * FROM request_specimen, specimens WHERE request_id IN (@$request_ids)
    my $rs = $self->model('Specimen')->get_request_specimens($request_ids);

    # create specimen_map of keys = request_id, vals = [ sample_code(s) ]
    my %specimen_map;

    foreach my $request_specimen( @{ $rs } ) {
        my $request_id  = $request_specimen->request_id;
        my $sample_code = $request_specimen->specimen->sample_code;
        my $description = $request_specimen->specimen->description;

        # create request_id key with empty hashref (if not aleady exists):
        $specimen_map{ $request_id } ||= {}; # not strictly necessary though
        # sample_code:
        push @{ $specimen_map{$request_id}{sample_code} }, $sample_code;
        # description:
        push @{ $specimen_map{$request_id}{description} }, $description;
    }

    return \%specimen_map
}

# ------------------------------------------------------------------------------
sub anatomical_sites_map {
    my %map = map +($_->id => $_->as_tree),
        @{ shift->model('Coding')->get_anatomical_sites };
    return \%map;
}

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

	my $status_options = LIMS::DB::StatusOption::Manager->get_status_options;

	my %map = map {
		$_->description => $_->as_tree;
	} @$status_options;

	return \%map;
}

# ------------------------------------------------------------------------------
# return hashref of { request_id => $request_options }:
sub request_options_map {
	my ($self, $request_ids) = @_;

	my %map = ();

	my $ro = $self->model('Request')->get_request_options($request_ids);

    foreach my $request_option( @{ $ro } ) {
		next unless $request_option->option->is_active eq 'yes';

        my $request_id = $request_option->request_id;
        my $option     = $request_option->option->option_name;

		$map{$request_id}{$option} = 1;
    } # $self->debug(\%map);

	return \%map;
}

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

	my $error_codes = $self->model('Base')->get_objects('ReportErrorCode');

    my %map = map {
        $_->code => $_->as_tree;
    } @$error_codes;

    return \%map;
}

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

	my $o = $self->model('Diagnosis')->get_diagnosis_context_warnings;

    my %map = map {
        $_->diagnosis_id => $_->context_warning->description;
    } @$o;

    return \%map;
}

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

    my %map;

    my $data = $self->model('Base')->get_objects('PanelLabTest');

    for (@$data) {
        my $panel_test_id = $_->panel_test_id;
        my $lab_test_id   = $_->lab_test_id;

        $map{$panel_test_id}{$lab_test_id}++;
    }
    return \%map;
}

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

    my %map;

    my $data = $self->model('Base')->get_objects('DiagnosisLabTest');

    for (@$data) {
        my $diagnosis_id = $_->diagnosis_id;
        my $lab_test_id  = $_->lab_test_id;

        $map{$diagnosis_id}{$lab_test_id}++;
    }
    return \%map;
}

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

    my %map;

    my $data = $self->model('Base')->get_objects('ResultSummaryLabTest');

    for (@$data) {
        my $summary_id  = $_->result_summary_id;
        my $lab_test_id = $_->lab_test_id;

        $map{$summary_id}{$lab_test_id}++;
    }
    return \%map;
}

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

    my $users = $self->model('Base')->get_objects('User');

    my %map = map {
        $_->id => $_->as_tree;
    } @$users;

    return \%map;
}

# ------------------------------------------------------------------------------
sub user_locations_map { # get list of locations from user_locations table:
	my $self = shift;

    my $locations = $self->model('Base')->get_objects('UserLocation');

    my %map = map {
        $_->location_name => $_->region_code;
    } grep { $_->region_code } @$locations;

    return \%map;

=begin # using email_contacts table to find local network locations:
    my $locations = $self->model('ReferralSource')->get_email_contacts;

    my %locations_map = map {
        # get 1st 3 chars of organisation_code if it's an organisation:
        my $org_code = $_->scope eq 'organisation'
            ? substr $_->referral_source->organisation_code, 0, 3
            : $_->referral_source->organisation_code; # else full 5 chars
        $_->display_name => $org_code;
    } grep $_->scope ne 'department', @$locations; # skip depts.
       # warn Dumper \%locations_map;
	return \%locations_map;
=cut
}

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

	my $functions = $self->model('User')->get_user_functions;

	my %map = map +($_->id => $_->function_detail), @$functions;
	return \%map;
}

# ------------------------------------------------------------------------------
sub lab_test_status_for_field_label_map {
	my $self = shift;
	my $args = shift; # warn Dumper $args;

	my $request_ids = $args->{request_ids};
	my $field_label = $args->{field_label}; # warn Dumper $field_label;

	my %status_map; # hash of request.id's => { $status => 1 }

    my $lab_name  = $self->cfg('settings')->{lab_name_abbreviation};
    my $users     = $self->model('User')->get_users_for_location($lab_name);
    my @usernames = map { uc $_->username } @$users; # warn Dumper \@usernames;

	my $test_status = sub {
		my ($history, $test) = @_; # warn $entry; warn $test;

		my $expr = qr{set $test status to (.*)};

		if ( my ($status) = $history->action =~ $expr ) { # warn $status;
            # remove 'for USERNAME' if exists:
			map { $status =~ s/(\sfor\s$_)\Z// } @usernames;
			return $status;
		}
	};

	REQUEST:
	for my $id (@$request_ids) { # warn $id;
		my $history = $self->model('History')->get_lab_test_history($id);
		HISTORY: # is arrayref:
		for my $entry (@$history) { # warn $_->action;
			if (ref $field_label eq 'ARRAY') { # warn 'ARRAY';
				LBL:
				for my $lbl (@$field_label) { # warn $lbl;
					my $status = &$test_status($entry, $lbl) || next LBL;
					$status_map{$id}{$lbl}{$status}++;
				}
			}
			else {
				my $status = &$test_status($entry, $field_label) || next HISTORY;
				$status_map{$id}{$status}++;
			}
		}
	} # warn Dumper \%status_map;
	return \%status_map;
}

# ------------------------------------------------------------------------------
sub lab_test_status_options_map {
    my $self = shift;
    my $key  = shift; # which col to use as key

    my $status_options = # OK until need to target options to section:
        $self->model('Base')->get_objects('LabTestStatusOption');

    my %map = map {
        $_->$key => $_->as_tree;
    } @$status_options;

    return \%map;
}

# ------------------------------------------------------------------------------
sub request_history_map {
    my ($self, $request_id) = @_;

    my $request_history
        = $self->model('History')->get_request_history($request_id);

    my %map = map {
        $_->action => $_->as_tree(deflate => 0); # or datetime object converted to scalar
    } @$request_history;

    return \%map;
}

# ------------------------------------------------------------------------------
sub request_section_notes_map {
	my ($self, $request_ids) = @_;

	my $lab_section = $self->stash->{lab_section}; # $self->debug($lab_section);

	# does section have lab_test_details?
    return unless $lab_section->has_section_notes eq 'yes';

	# set template flag:
	$self->tt_params(section_has_test_details => 1);

	my %map = ();

	my $o = $self->model('Request')->get_section_notes($request_ids);

    foreach my $row( @{ $o } ) {
        next unless $row->lab_section_id == $lab_section->id;

        my $request_id = $row->request_id;
		$map{$request_id} = $row->details;
    }

	return \%map;
}

# ------------------------------------------------------------------------------
sub time_duration_map {
	my $self = shift;
	my $data = shift;

	my %map = map {
		my $duration = $_->created_at->delta_days(DateTime->today)->delta_days;
		$_->id => $duration;
	} @$data; # $self->debug(\%map);

	return \%map;
}

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

    my $lab_tests = $self->model('LabTest')->get_lab_tests;

    my %map = map {
        $_->id => $_->field_label;
    } @$lab_tests;

    return \%map;
}

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

    my $lab_tests = $self->model('LabTest')->get_lab_tests;

    my %map = map {
        $_->id => {
            field_label => $_->field_label,
            lab_section => $_->lab_section->section_name,
        }
    } @$lab_tests;

    return \%map;
}

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

    my $lab_sections = $self->model('Base')->get_objects('LabSection');

    my %map = map {
        $_->section_name => $_->as_tree;
    } @$lab_sections;

    return \%map;
}

#-------------------------------------------------------------------------------
sub lab_test_data_type_map { # !!! very similar name to lab_test_data_types_map()
    my $self = shift;

	my $lab_test_data_type = $self->model('LabTest')->get_lab_test_data_types;
	$self->stash( lab_test_data_type => $lab_test_data_type ); # stash for later

	# create hash of label => id for each lab_test_id in lab_test_data_type table:
	my %map;
	for my $t (@$lab_test_data_type) {
		my $test_id = $t->lab_test_id; # warn $test_id;

		$map{$test_id}{data_type}{active} = $t->is_active;
		$map{$test_id}{data_type}{type}   = $t->data_type->description;
		$map{$test_id}{data_type}{id}     = $t->data_type_id;
        $map{$test_id}{field_label}       = $t->lab_test->field_label;
    }

	return \%map;
}

#-------------------------------------------------------------------------------
sub lab_test_data_types_map { # !!! very similar name to lab_test_data_type_map()
    my $self = shift;

    my $data_types = $self->model('LabTest')->get_lab_test_data_types;

    my %map = map {
        $_->lab_test_id => {
            id => $_->data_type_id,
            active => $_->is_active,
            description => $_->data_type->description,
        };
    } @$data_types;

    return \%map;
}

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

    my $lab_test_data_type = $self->stash->{lab_test_data_type};

    my %map;
    foreach (@$lab_test_data_type) {
        my $section_name = $_->lab_test->lab_section->section_name;
        my $test_label   = $_->lab_test->field_label;
        my $lab_test_id  = $_->lab_test_id;

        $map{$section_name}{$test_label} = $lab_test_id;
    }

    return \%map;
}

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

    my $options = $self->model('LabTest')->get_lab_test_result_options;

    # create hash of data_type_id => sorted array(ref) of lab_test_result_options:
    my %options_map = my %sorted_options_map = ();

    map {
        my $data_type_id = $_->data_type_id;
        $options_map{$data_type_id}{$_->value} = $_->as_tree;
    } @$options; # warn Dumper \%options_map;

    # sort options into alphanumeric order for template:
    while ( my ($id, $data ) = each %options_map ) { # warn Dumper $data;
        my @opts = keys %$data; # warn Dumper \@opts;
        my @sorted_opts = Sort::Naturally::nsort(@opts); # warn Dumper \@sorted_opts;
        $sorted_options_map{$id} = [ @{$data}{@sorted_opts} ]; # ref to array of sorted opts
    } # warn Dumper \%sorted_options_map;

    return \%sorted_options_map;
}

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

	my $diagnoses = $self->model('Diagnosis')->get_all_diagnoses;

	my %map;
	map {
		my $category  = $_->diagnostic_category->description;
		my %data = (
			id     => $_->id,
			name   => $_->name,
            active => $_->active,
		);
		push @{ $map{$category} }, \%data;
	} @$diagnoses;

	return \%map;
}

#-------------------------------------------------------------------------------
sub diagnoses_map {
	my $self = shift;
	my $key  = shift;

	my $diagnoses = $self->model('Diagnosis')->get_all_diagnoses;

	my %map = map {
		$_->$key => $_->as_tree;
	} @$diagnoses;

	return \%map;
}

#-------------------------------------------------------------------------------
sub section_notes_map {
    my ($self, $request_id) = @_;

    my $request_section_notes
        = $self->model('Request')->get_section_notes($request_id);

    my %map = map {
        $_->lab_section_id => $_->details;
    } @$request_section_notes;

    return \%map;
}

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

    my $specimen_lab_tests = $self->model('Specimen')->get_specimen_lab_tests;

    my %map;

    while ( my $row = $specimen_lab_tests->next ) {
        my $specimen_id = $row->specimen_id;
        my $lab_test_id = $row->lab_test_id;
        my $field_label = $row->lab_test->field_label;

#        $map{$specimen_id}{lab_test_id}   = $lab_test_id;
#        $map{$specimen_id}{lab_test_name} = $o->lab_test->field_label;
        push @{ $map{$specimen_id}{lab_test_names} }, $field_label;
        push @{ $map{$specimen_id}{lab_test_ids} },   $lab_test_id;
    }

    return \%map;
}

#-------------------------------------------------------------------------------
sub lab_test_specimen_map {
    my ($self, $lab_test_id) = @_;

	my $o = $self->model('Specimen')->get_specimen_lab_tests;

	# create hashref of lab_test_id => [ specimen_id(s) ]:
	my %map;
	while ( my $row = $o->next ) {
		my $lab_test_id = $row->lab_test_id;
		push @{ $map{$lab_test_id} }, $row->specimen->sample_code;
	}
	return \%map;
}

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

	my $err_codes = $self->model('ErrorCode')->get_error_codes;

	my %map = map {
		$_->code => $_->as_tree;
	} @$err_codes;

	return \%map;
}

#-------------------------------------------------------------------------------
sub lab_section_status_options_map {
	my ($self, $section_id) = @_;

	my $o = $self->model('LabSection')->get_section_status_options($section_id);

	my %map = map {
		$_->status_option->description => $_->status_option_id;
	} @$o;

	return \%map;
}

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

    # get list of section status options table data:
    my $o = do {
        my @args = (
            'LabSectionStatusOption', # class
            { sort_by => 'lab_section.section_name' }, # optional args, passed through
            [ 'lab_section', 'status_option' ], # optional require_objects
        );
        $self->model('Base')->get_objects(@args);
	};

	my %h;
	for ( sort by_position (@$o) ) {
		my $section = $_->lab_section->section_name;
 		push @{ $h{$section} }, $_->as_tree;
	}
	return \%h;
}

sub by_position {
    # use position if configured, otherwise just return status_option_id:
    my $itemA = $a->position || $a->status_option_id;
    my $itemB = $b->position || $b->status_option_id; # warn Dumper [$itemA, $itemB];

    return $itemA <=> $itemB;
}

1;
