package Distribution;

use strict;
use warnings;

use LWP;
use Digest::MD5 qw(md5 md5_hex md5_base64);

use lib qw(Core);
use lib qw(Classes);

use Configuration;
use LocalConfiguration;

use Email;
use Logger;
use String;
use PDBUtils;

use TargetsManager;
use GroupsManager;
use JobsManager;

my $RESPONSE_TIMEOUT = 180; # in sec

### METHODS (last update 05/24/2010 M. Wojciechowski):
#   submit (sending requests with targets to the registered servers)
#   process_email_request (sends request to the email server)
#   process_http_request (sends request to the http server depending)
#   build_request_url (setting up the request url for GET)
#   build_post_request (setting up the request url for POST)
#   build_email_request (creates email to be sent to the email server)
#   format_sequence (validates sequence type and formats if needed)
#   save_request (saves request)
#   save_response (saves response)
#   save_file (saves file)
#   log (adds string to the log)
#   receive_response (returns group of server that had sent email to us
#                     and saves information about it)

sub new {
    my ($class, $debug) = @_;
    
    my $self = {
        _database => Database->new($CONFIG->{HOSTNAME}, $CONFIG->{PORT}, $CONFIG->{DATABASE}, $CONFIG->{USERNAME}, $CONFIG->{PASSWORD}),
        _logger => Logger->new()
    };
    
    # by default debug mode is turned on
    $self->{_debug} = !defined($debug) ? 1 : $debug;
    
    $self->{_logger}->set_info_log('distribution.log');
    
    bless $self, $class;
    return $self;
}

sub submit {
    (@_ == 4) || die("Invalid arguments supplied");
    my ($self, $target_id, $group_id, $is_qa_request) = @_;
    
    my $uid = md5_hex(sprintf("casp9%s%d%d%d", join(':', localtime()), $target_id, $group_id, rand(9999))); # generate unique uid for response and request
    
    my $result = 0;
    
    $target_id = int($target_id);
    $group_id = int($group_id);
    
    $self->log($uid, "START PROCESSING");
    $self->log($uid, sprintf("Input Parameters: Target: %d, Group: %d, Request type: %s", $target_id, $group_id, ($is_qa_request) ? "QA" : "Regular"));
    
    my $targets_manager = new TargetsManager();
    
    if(!$targets_manager->exist($target_id)) {
        $self->log($uid, "ERROR! Target doesn't exist!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    } elsif(!$targets_manager->is_published($target_id)) {
        $self->log($uid, "ERROR! Target wasn't published yet!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    }
    
    my $groups_manager = new GroupsManager();
    
    my %server_info = $groups_manager->info_server($group_id);
    
    if(!$groups_manager->exist($group_id)) {
        $self->log($uid, "ERROR! Group doesn't exist!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    }
    
    my $group_name = $groups_manager->name($group_id);
    my $target_name = $targets_manager->name($target_id);
    $self->log($uid, sprintf("Info: %s, %s", $group_name, $target_name));
    
    my $type = $server_info{TYPE};
    
    my $is_email_server = 0;
    if($type == 0) {
        $self->log($uid, "ERROR! Group isn't server!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    } elsif($type == 1) {
        $is_email_server = 0;
        $self->log($uid, "Group is Regular Server");
    } elsif($type == 2) {
        $is_email_server = 1;
        $self->log($uid, "Group is Email Server");
    } else {
        $self->log($uid, "ERROR! Invalid group type!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    }
    
    my $is_blocked = $groups_manager->is_blocked($group_id);
    if($is_blocked) {
        $self->log($uid, "ERROR! Group is blocked by administrator!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    }
    
    my $jobs_manager = new JobsManager();
    
    my $job_id = $jobs_manager->register($target_id, $group_id, $uid);
    if($job_id <= 0) {
        $self->log($uid, "ERROR! Job registration FAILED!");
        $self->log($uid, "PROCESSING FAILED!");
        return $result;
    }
    
    $self->log($uid, sprintf("Job was successfully registered, ID: %d", $job_id));
    
    my $request_method = $server_info{REQUEST_METHOD};
    
    if(!$self->{_debug}) {
        my $request_send_status = 0;
        if(!$is_email_server) {
            $request_send_status = $self->process_http_request($target_id, $group_id, $job_id, $uid, $request_method, $is_qa_request);
        } else {
            $request_send_status = $self->process_email_request($target_id, $group_id, $job_id, $uid, $is_qa_request);
        }
        
        if(! $request_send_status) {
            $self->log($uid, "ERROR! Processing request FAILED!");
            $self->log($uid, "PROCESSING FAILED!");
            return $result;
        }
    }
    
    $self->log($uid, "END PROCESSING");
    $result = 1;
    
    return $result;
}

sub process_email_request {
    (@_ == 6) || die("Invalid arguments supplied");
    my ($self, $targets_id, $groups_id, $job_id, $uid, $is_qa_request) = @_;
    
    my $result = 0;
    
    $self->log($uid, "Email request initialized");
    
    my $body = $self->build_email_request($targets_id, $groups_id, $uid, $is_qa_request);
    
    if($body eq '') {
        $self->log($uid, "ERROR! Request wasn't generated!");
        return $result;
    }
    
    my $targets_manager = new TargetsManager();
    my %target_info = $targets_manager->info($targets_id);
    
    my $request_text = sprintf("Subject: %s\n\n", $target_info{NAME});
    $request_text = sprintf("%sBody:\n%s\n", $request_text, $body);
    
    my $groups_manager = new GroupsManager();
    my %server_info = $groups_manager->info_server($groups_id);
    
    my $email = $server_info{REQUEST_EMAIL};
    
    if(trim($email) eq '') {
        $self->log($uid, "ERROR! Invalid recepient email!");
        return $result;
    }
    
    Email::send_email($LOCAL_CONFIG->{SERVERS_REQUEST_META_EMAIL}, $email, $LOCAL_CONFIG->{SERVERS_REQUEST_META_EMAIL}, "", $target_info{NAME}, $body);
    
    my $jobs_manager = new JobsManager();
    
    # add request into database
    if($jobs_manager->request($job_id) != 1) {
        $self->log($uid, "ERROR! Request wasn't saved to database!");
        return $result;
    }
    
    # save request into uid.request file into request directory
    if($self->save_request($uid, $request_text) != 1) {
        $self->log($uid, "ERROR! Request file wasn't saved!");
        return $result;
    }
    
    $self->log($uid, "Email has been sent");
    
    my @log_request = split(/\n/, $request_text);
    
    for(my $i = 0; $i < scalar(@log_request); $i++) { 
        $self->log($uid, $log_request[$i]);
    }
    
    # till this point everything looks OK
    $result = 1;
    
    return $result;
}

sub process_http_request {
    (@_ == 7) || die("Invalid arguments supplied");
    my ($self, $targets_id, $groups_id, $job_id, $uid, $request_method, $is_qa_request) = @_;
    
    my $result = 0;
    
    my $agent = LWP::UserAgent->new();
    $agent->timeout($RESPONSE_TIMEOUT);
    
    my $request_text = '';
    my $response;
    
    $self->log($uid, "HTTP request initialized");
    
    if(lc($request_method) eq 'get') {
        my $request_url = $self->build_request_url($targets_id, $groups_id, $uid, $is_qa_request);
        my $request = HTTP::Request->new(GET => $request_url);
        if($request_url eq '') {
            $self->log($uid, "ERROR! Request url wasn't built!");
            return $result;
        }
        
        $self->log($uid, sprintf("URL: %s", $request_url));
        
        $response = $agent->request($request);
        
        $request_text = sprintf("GET REQUEST\n");
        $request_text = sprintf("%s%s", $request_text, $request_url);
    } elsif(lc($request_method) eq 'post') {
        my @post_request = $self->build_post_request($targets_id, $groups_id, $uid, $is_qa_request);
        
        if(scalar(@post_request) == 0) {
            $self->log($uid, "ERROR! Request post parameters string are not defined!");
            return $result;
        }
        
        $self->log($uid, sprintf("URL: %s", $post_request[0]));
        
        my @parameters = @{$post_request[1]};
        
        $request_text = sprintf("POST REQUEST\n");
        $request_text = sprintf("%s%s\n", $request_text, $post_request[0]);
        
        for(my $i = 0; $i < scalar(@parameters); $i++) {
            my $request_string = sprintf("%s=>%s", $parameters[$i], $parameters[$i + 1]);
            $self->log($uid, sprintf("\t%s", $request_string));
            $request_text = sprintf("%s%s\n", $request_text, $request_string);
            $i++;
        }
        
        $response = $agent->post(@post_request);
    } else {
        $self->log($uid, "ERROR! Invalid request method! Should be 'get' or 'post'");
        return $result;
    }
    
    my $jobs_manager = new JobsManager();
    
    # add request into database
    if($jobs_manager->request($job_id) != 1) {
        $self->log($uid, "ERROR! Request wasn't saved to database!");
        return $result;
    }
    
    # save request into uid.request file into request directory
    if($self->save_request($uid, $request_text) != 1) {
        $self->log($uid, "ERROR! Request file wasn't saved!");
        return $result;
    }
    
    $self->log($uid, "Request has been sent");
    
    if(!$response->is_success) {
        $self->log($uid, "ERROR! Response wasn't success!");
        $self->log($uid, sprintf("ERROR! Status line: %s", $response->status_line));
        return $result;
    }
    
    my $response_text = $response->content;
    
    # add response into database
    if($jobs_manager->response($job_id, $response_text) != 1) {
        $self->log($uid, "ERROR! Response wasn't saved to database!");
        return $result;
    }
    
    # save response into uid.response file into response directory
    if($self->save_response($uid, $response_text) != 1) {
        $self->log($uid, "ERROR! Response file wasn't saved!");
        return $result;
    }
    
    # till this point everything looks OK
    $result = 1;
    
    return $result;
}

sub build_request_url {
    (@_ == 5) || die("Invalid arguments supplied");
    my ($self, $targets_id, $groups_id, $uid, $is_qa_request) = @_;
    
    my $result = '';
    
    my $targets_manager = new TargetsManager();
    my $groups_manager = new GroupsManager();
    
    my %target_info = $targets_manager->info($targets_id);
    my %server_info = $groups_manager->info_server($groups_id);
    
    my $url = String::trim($server_info{REQUEST_CGI});
    
    if($url eq '') {
        $self->log($uid, "ERROR! REQUEST_CGI is empty");
        return $result;
    } elsif(($url =~ /\?/) || ($url =~ /\&/) || ($url =~ / /)) {
        $self->log($uid, sprintf("WARNING! REQUEST_CGI contains invalid characters! It may cause problems. Group: %s. URL: %s", $server_info{NAME}, $server_info{REQUEST_CGI}));
    }
    
    $url = sprintf("%s?", $url);
    $url = sprintf("%s%s=%s&", $url, $server_info{TARGET_NAME_KEY}, $target_info{NAME});   # target name
    $url = sprintf("%s%s=%s&", $url, $server_info{SEQUENCE_KEY}, $self->format_sequence($target_info{NAME}, $target_info{SEQUENCE}, $server_info{SEQUENCE_FORMAT}));   # sequence name
    $url = sprintf("%s%s=%s&", $url, $server_info{EMAIL_KEY}, $LOCAL_CONFIG->{SERVERS_DISTRIBUTION_EMAIL});   # email 
    
    if($is_qa_request && $groups_manager->is_qa_server($groups_id)) {
        $url = sprintf("%s%s=%s/%s.3D.srv.tar.gz&", $url, $server_info{TARBALL_KEY}, $LOCAL_CONFIG->{QA_TARBALL_LOCATION}, $targets_manager->name($targets_id));   # tarball
    }
    
    if(String::trim($server_info{OTHER}) ne '') {
        $url = sprintf("%s%s", $url, $server_info{OTHER});   # other parameters
    }
    
    $result = $url;
    
    return $result;
}

sub build_post_request {
    (@_ == 5) || die("Invalid arguments supplied");
    my ($self, $targets_id, $groups_id, $uid, $is_qa_request) = @_;
    
    my @result = ();
    
    my $targets_manager = new TargetsManager();
    my $groups_manager = new GroupsManager();
    
    my %target_info = $targets_manager->info($targets_id);
    my %server_info = $groups_manager->info_server($groups_id);
    
    my $url = String::trim($server_info{REQUEST_CGI});
    
    if($url eq '') {
        $self->log($uid, "ERROR! REQUEST_CGI is empty");
        return @result;
    } elsif(($url =~ /\?/) || ($url =~ /\&/) || ($url =~ / /)) {
        $self->log($uid, sprintf("WARNING! REQUEST_CGI contains invalid characters! It may cause problems. Group: %s. URL: %s", $server_info{NAME}, $server_info{REQUEST_CGI}));
    }
    
    push(@result, $url);
    my %parameters = ();
    
    $parameters{$server_info{TARGET_NAME_KEY}} = $target_info{NAME};
    $parameters{$server_info{SEQUENCE_KEY}} = $self->format_sequence($target_info{NAME}, $target_info{SEQUENCE}, $server_info{SEQUENCE_FORMAT});
    $parameters{$server_info{EMAIL_KEY}} = $LOCAL_CONFIG->{SERVERS_DISTRIBUTION_EMAIL};
    
    if($is_qa_request && $groups_manager->is_qa_server($groups_id)) {
        $parameters{$server_info{TARBALL_KEY}} = sprintf("%s/%s.3D.srv.tar.gz", $LOCAL_CONFIG->{QA_TARBALL_LOCATION}, $targets_manager->name($targets_id));   # tarball
    }
    
    if(String::trim($server_info{OTHER}) ne '') {
        my @pairs = split(/&/, $server_info{OTHER});
        
        foreach my $pair (@pairs) {
            my ($name, $value) = split(/=/, $pair);
            $parameters{$name} = $value;
        }
    }
    
    push(@result, [%parameters]);
    
    return @result;
}

sub build_email_request {
    (@_ == 5) || die("Invalid arguments supplied");
    my ($self, $targets_id, $groups_id, $uid, $is_qa_request) = @_;
    
    my $result = "";
    
    my $targets_manager = new TargetsManager();
    my $groups_manager = new GroupsManager();
    
    my %target_info = $targets_manager->info($targets_id);
    my %server_info = $groups_manager->info_server($groups_id);
    
    $result = sprintf("%s%s=%s\n", $result, $server_info{TARGET_NAME_KEY}, $target_info{NAME});
    $result = sprintf("%s%s=%s\n", $result, $server_info{SEQUENCE_KEY}, $self->format_sequence($target_info{NAME}, $target_info{SEQUENCE}, $server_info{SEQUENCE_FORMAT}));
    $result = sprintf("%s%s=%s\n", $result, $server_info{EMAIL_KEY}, $LOCAL_CONFIG->{SERVERS_DISTRIBUTION_EMAIL});
    #$result = sprintf("%sCASP9_ID=%s\n", $result, $uid);
    
    if($is_qa_request && $groups_manager->is_qa_server($groups_id)) {
        $result = sprintf("%s%s=%s/%s.3D.srv.tar.gz\n", $result, $server_info{TARBALL_KEY}, $LOCAL_CONFIG->{QA_TARBALL_LOCATION}, $targets_manager->name($targets_id));
    }
    
    if(String::trim($server_info{OTHER}) ne '') {
        my @pairs = split(/&/, $server_info{OTHER});
        
        foreach my $pair (@pairs) {
            my ($name, $value) = split(/=/, $pair);
            $result = sprintf("%s%s=%s\n", $result, $name, $value);
        }
    }
    
    return $result;
}

sub format_sequence {
    (@_ == 4) || die("Invalid arguments supplied");
    my ($self, $target_name, $sequence, $type) = @_;
    
    $sequence = validate_sequence($sequence);
    
    if($type eq 'fasta') {
    	$sequence = sprintf(">%s\n%s", $target_name, reformat_sequence($sequence));
    }
    
    return $sequence;
}

sub save_request {
    (@_ == 3) || die("Invalid arguments supplied");
    my ($self, $uid, $request) = @_;
    
    my $result = 0;
    
    my $file = sprintf("%s/%s.request", $LOCAL_CONFIG->{REQUEST_DIR}, $uid);
    
    $result = $self->save_file($file, $request);
    
    return $result;
}

sub save_response {
    (@_ == 3) || die("Invalid arguments supplied");
    my ($self, $uid, $response) = @_;
    
    my $result = 0;
    
    my $file = sprintf("%s/%s.response", $LOCAL_CONFIG->{RESPONSE_DIR}, $uid);
    
    $result = $self->save_file($file, $response);
    
    return $result;
}

sub save_file {
    (@_ == 3) || die("Invalid arguments supplied");
    my ($self, $file, $content) = @_;
    
    my $result = 0;
    
    if(open(FILE_HANDLER, sprintf("> %s", $file))) {
        print(FILE_HANDLER $content);
        close(FILE_HANDLER);
        
        $result = 1;
    }
    
    return $result;
}

sub log {
    my ($self, $uid, $message) = @_;
    
    $self->{_logger}->info("Distribution.pm", sprintf("%-40s%s", $uid, $message));
}

sub receive_response {
    my ($self, $email, $target, $subject) = @_;
    
    my $result = '';
    
    my $groups_manager = new GroupsManager();
    my $group_id = $groups_manager->get_server_group_id_by_request_email($email);
    
    if($group_id <= 0) {
        $group_id = $groups_manager->get_server_group_id_by_response_email($email);
        if($group_id <= 0) {
            $group_id = $groups_manager->get_http_server_group_id_by_name($subject);
            if($group_id <= 0) {
                $self->log('', sprintf("ERROR! Server by email %s wasn't found!", $email));
                return $result;
	    }
        }
    }
    
    my $targets_manager = new TargetsManager();
    my $target_id = $targets_manager->get_id_by_name($target);
    
    my $jobs_manager = new JobsManager();
    my %job = $jobs_manager->get_job($group_id, $target_id);
    
    if(scalar(%job) == 0) {
        $self->log('', sprintf("ERROR! No job was found for the group with id %s (target %s)!", $group_id, $target));
        return $result;
    } elsif($job{STATUS} == 3) {
        $self->log('', sprintf("ERROR! Group with id %s Already responded (target %s)!", $group_id, , $target));
        return $result;
    } elsif($job{STATUS} != 2) {
        $self->log('', sprintf("ERROR! Invalid job for the group with id %s (target %s) was found!", $group_id, $target));
        return $result;
    }
    
    $jobs_manager->response($job{ID});
    
    $result = $job{UID};
    
    return $result;
}

sub DESTROY {
    my ($self) = @_;
    # not implemented yet
}

1;
