package LIMS::Controller::Roles::DataImport;

use Moose::Role;
use Data::Dumper;
use LIMS::Local::Utils;

has $_ => ( is => 'ro', isa => 'HashRef', lazy_build => 1)
	for qw( datafile_field_map datafiles_config );
	
has param_mismatch => (
    is      => 'ro',
    isa     => 'ArrayRef[Str]',
    default => sub { [] },
    lazy    => 1,
	traits  => ['Array'],
	handles => {
        has_param_mismatch => 'count',
		add_param_mismatch => 'push',
		all_mismatches     => 'elements',
	},
);

has datafile_params => (
    is      => 'ro',
    isa     => 'ArrayRef[Str]',
    default => sub { [] },
    lazy    => 1,
	traits  => ['Array'],
	handles => {
		add_datafile_param => 'push',
	},
);

use Data::Dumper;

#-------------------------------------------------------------------------------
sub get_result_data_file {
	my ($self, $request_id) = @_;
	
	my $request = $self->model('Request')->get_request($request_id);

	# get list of all files in requests' images dir:
	my $contents = $self->load_image_files($request); # hashref
	my $files = $contents->{files}; # $self->debug($files); # arrayref
	
    # get list of permitted file type definition (eg PB_HIV, PB_RIT, etc)
    my $cfg = $self->get_yaml_file('datafiles') || return []; # expects arrayref

    my @file_types = map {
        $cfg->{$_}->{file_id};
    } keys %$cfg; # warn Dumper \@file_types;    
    
	my @data_files;

	# should only be 1 data file but capture any and test for in caller:
    for my $file(@$files) { # warn $file;
        next unless grep {
            # requested format eg: 09_00123_PB_HIV.txt
            $file =~ /\d{2}_\d{5}_($_)\.txt\Z/;
        } @file_types;
        push @data_files, $file;
    }

	return \@data_files;
}

#-------------------------------------------------------------------------------
sub get_datafile_contents {
	my ($self, $data_file) = @_; # warn $data_file;

	# capture component of filename before .txt:
	my ($data_file_id) = $data_file =~ /(.*)\.txt/; # warn $file_id;
	
	# capture 1st 2 blocks of digits into $yr & $request_number vars, and last
    # block of chars into $type var: 
	my ($yr, $req_number, $type) = $data_file_id =~ /\A(\d{2})_(\d{5})_\w+_(\w+)\Z/;
    
    # store request_type and data_file_id for later:
    $self->stash(
        request_type => $type,
        data_file_id => $data_file_id,
    );
    
	# create hashref for Role::DataFile::slurp_file_contents():
	my %request = (
		request_number => int($req_number), # get rid of leading zeros
		year => 2000 + $yr, 
	); # $self->debug(\%request);	

	# get file contents from data file (as arrayref of rows):
	my $file_contents = $self->slurp_file_contents(\%request, $data_file);
	# $self->debug($file_contents);
	
	return $file_contents;	
}

#-------------------------------------------------------------------------------
sub parse_data_file {
	my ($self, $file_contents) = @_; # warn Dumper $file_contents;
	
	# capture content after 1st tab:
	my @data = map {
		s/\"|\r|\n//g; # remove quote marks & line-endings (win & unix)
		/^.*\t(.*)$/; # capture content after 1st tab
	} @$file_contents; # $self->debug(\@data);	

	# add to $self->datafile_params:
	$self->add_datafile_param($_) for @data;
}

#-------------------------------------------------------------------------------
# checks datafile params match expected values, sets 'param_mismatch()' on error:
sub check_datafile_integrity {
	my $self = shift;

	# get datafile params: 
	my $data = $self->datafile_params; # warn Dumper $data;
	
    # get request_type (ie currently HIV, RIT or CMP):
    my $request_type = $self->stash->{request_type}; # set in parse_data_file()

	my $request_id = $self->param('id'); # also available in stash->{id};

 	# get patient object:
	my $patient
		= $self->model('Patient')->get_patient_from_request_id($request_id);

	{ # check datafile filename matches its internal filename ref:
        # get array index position for 'filename' field:
		my $array_index = $self->_get_array_index_for_param('filename');
        # get value of 'filename' field:
		my $internal_file_id = $data->[$array_index]; # warn $internal_file_id;
        
        unless ($internal_file_id) {
            $self->add_param_mismatch('missing_filename');
        }
        else {
            # get datafile name (set in parse_data_file()):
            my $data_file_id = $self->stash->{data_file_id}; # warn $data_file_id;
    
            # check internal_file_id matches data_file_id:
            unless ( $internal_file_id eq $data_file_id ) {
                $self->add_param_mismatch('filename_error');
            }
        }
	}
	
	{ # check datafile 'surname' field matches patient last_name:
        # get array index position for 'surname' field:
		my $array_index = $self->_get_array_index_for_param('last_name');
        # get value of 'last_name' field:
		my $val = $data->[$array_index];
        
        unless ($val) {
            $self->add_param_mismatch('missing_name');
        }
        else { # warn $val; warn $patient->last_name;    
            # check datafile surname val matches patient last_name:
            if ( lc $val ne $patient->last_name ) {
                $self->add_param_mismatch('surname_error');
            }
        }
	}
	
	{ # check datafile 'dob' field matches patient dob:
        # get array index position for 'dob' field:
		my $array_index = $self->_get_array_index_for_param('dob'); # warn $array_index;
        
        # get value of 'dob' field:
		my $val = $data->[$array_index];
        
        unless ($val) {
            $self->add_param_mismatch('missing_dob'); 
        }
        else { # warn $val; warn $patient->dob;
            # get day, month & year vals from 'dob' field:
            my ($d,$m,$y) = $val =~ /(\d{2})(\d{2})(\d{4})/; # eg 01121974
            my $dob;
            eval { # will fail if day, month or year invalid or out-of-range:                    
                $dob = DateTime->new(year => $y, month => $m, day => $d);               
            };
            if ($@) {
                $self->add_param_mismatch('invalid_date');
            }
            elsif ( $dob ne $patient->dob ) {
                $self->add_param_mismatch('dob_error');
            }
        }
	}
	
	if ( $self->has_param_mismatch ) {
		my @error_names = $self->all_mismatches;
		return \@error_names;
	}	
}

#-------------------------------------------------------------------------------
sub get_datafile_results {
    my $self = shift;
    
	# get datafile params: 
	my $data = $self->datafile_params; # warn Dumper $data;
    
    # get array indexes for first and last result:
	my $array_index_start = $self->_get_array_index_for_param('result_first');
	my $array_index_end   = $self->_get_array_index_for_param('result_last');
    
    my @results = map {
        $data->[$_];
    } ($array_index_start .. $array_index_end); # warn $results;
    
    return \@results;
}

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

	# get datafile params: 
	my $data = $self->datafile_params;
    
    # get array index for 'date_acquired' field:
    my $array_index = $self->_get_array_index_for_param('date_acquired');
    
    # get 'date_acquired' val:
    my $val = $data->[$array_index]; # warn $val; # eg 21-Jul-2009 10:59:09
    
    # check format or parsedate() will probably throw fatal error:
    $val =~ /\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}/ || return 0;
    
    # create DateTime object using Time::ParseDate::parsedate():
    my $dt = LIMS::Local::Utils::to_datetime_using_parsedate($val); # warn $dt;
    
    return $dt;
}

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

    my $ok = $self->model('Result')->check_request_lab_test($args);
    return $ok; # returns true if lab-test *has* been requested
}

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

	# get datafile params: 
	my $data = $self->datafile_params;

    # get array index for 'acquired_by' field:
    my $array_index = $self->_get_array_index_for_param('acquired_by');

    # get 'acquired_by' username:
    my $username = $data->[$array_index]; # warn $username; 
    
    return $username;
}

#-------------------------------------------------------------------------------
sub get_analysis_user_id {
    my ($self, $username) = @_; # warn Dumper $username;

    # get map of ( username => id ) for all users:
    my $users_map = $self->_get_users_map; # warn Dumper $users_map;
    
    # get user_id from users_map:
    my $user_id = $users_map->{lc $username};
    
    return $user_id;
}

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

    # get request_type (eg HIV or RIT):
    my $request_type = $self->stash->{request_type}; # set in parse_data_file()
	
	# get datafile settings as hashref (or return if no settings):
	my $datafile_cfg = $self->datafiles_config || return 0;

	# get lab_test data from datafile cfg:
	my $lab_test = $datafile_cfg->{$request_type}->{lab_test};
	
	# return empty unless sign-off required:
	return $lab_test->{sign_off} ? $lab_test : 0;	
}

#-------------------------------------------------------------------------------
sub get_report_params {
    my $self = shift;
    
	# get datafile params: 
	my $data = $self->datafile_params;

    # get request_type (eg HIV or RIT):
    my $request_type = $self->stash->{request_type}; # set in parse_data_file()
    
	# get datafile settings as hashref:
	my $datafile_cfg = $self->datafiles_config;

	my $cfg_data = $datafile_cfg->{$request_type}->{report};
	
	my %params;
	
	# get data from datafile fields, or from datafile config:
	my @fields = qw(comment clinical_details specimen_quality status diagnosis);
	foreach my $field(@fields) {
		# get array index for field:
		my $array_index = $self->_get_array_index_for_param($field);
		
		# if array index, get data from datafile, else from config file:
		my $param = $array_index 
			? $data->[$array_index] # load param from datafile     
			: $cfg_data->{$field};  # load param from config file
			
		$params{$field} = $param;
	}

    return \%params;
}

#-------------------------------------------------------------------------------
sub get_lab_section {
    my $self = shift;
    
    # get request_type (eg HIV or RIT):
    my $request_type = $self->stash->{request_type}; # set in parse_data_file()
	
	# get datafile settings as hashref:
	my $datafile_cfg = $self->datafiles_config;

	# get result_section from datafile cfg:
	my $lab_section = $datafile_cfg->{$request_type}->{result_section};

	# return lab_section:
    return $lab_section;
}

#-------------------------------------------------------------------------------
sub _get_array_index_for_param {
    my ($self, $param) = @_;
    
    # get request_type (ie currently HIV, RIT, CMP):
    my $request_type = $self->stash->{request_type}; # set in parse_data_file()

    # get map of ( field name => array position ) for datafiles:
	my $field_map = $self->datafile_field_map; # warn Dumper $field_map;		

    # return array index for field:
    return $field_map->{$request_type}->{$param};
}

#-------------------------------------------------------------------------------
sub _get_users_map {
    my $self = shift;
    
    my $users = $self->model('Base')->get_objects('User');
    
    my %map = map { $_->username => $_->id } @$users;
    
    return \%map;
}

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

	# get settings as hashref:
	my $settings = $self->datafiles_config;

	my %map = ();	
	while ( my($type, $data) = each %$settings ) {
		my $i = 0; # reset counter
		my $fields = $data->{fields};
		map { $map{$type}{$_} = $i++ } @$fields; 
	} # $self->debug(\%map);

	return \%map;
}

#-------------------------------------------------------------------------------
sub _build_datafiles_config {
	my $self = shift;
	
	my $yaml = $self->get_yaml_file('datafiles') || return 0;
	return $yaml;
}

1;
