package StructuresManager;

use strict;
use warnings;

use DBI;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use DateTime;

use lib qw(Core);

use PDBUtils;
use Database;
use TargetsManager;
use DomainsManager;

use Configuration;
use LocalConfiguration;

my $structures_manager = undef;

sub new {
    my ($class) = @_;
    
    return $structures_manager if(defined($structures_manager));
    
    my $self = {
        _id => undef,
        _database => Database->new($CONFIG->{HOSTNAME}, $CONFIG->{PORT}, $CONFIG->{DATABASE}, $CONFIG->{USERNAME}, $CONFIG->{PASSWORD})
    };
    
    $structures_manager = bless $self, $class;
    return $structures_manager;
}

sub exist {
    my ($self, $id) = @_;
    
    my $result = 0;
    
    my $query = sprintf("SELECT COUNT(id) FROM casp13.structures WHERE (id = %d)", $id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth) && ($sth->rows() > 0)) {
        my ($count) = $sth->fetchrow_array();
        
        $result = ($count == 1) ? 1 : 0;
    }
    
    return $result;
}

sub get_id_by_target {
    my ($self, $target_id) = @_;
    
    my $result = 0;
    
    my $query = sprintf("SELECT id FROM casp13.structures WHERE (targets_id = %d)", $target_id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth) && ($sth->rows() > 0)) {
        ($result) = $sth->fetchrow_array();
    }
    
    return $result;
}

sub get_id_by_target_published {
    my ($self, $target_id) = @_;
    
    my $result = 0;
    
    my $query = sprintf("SELECT id FROM casp13.structures WHERE (targets_id = %d) and is_published = 1", $target_id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth) && ($sth->rows() > 0)) {
        ($result) = $sth->fetchrow_array();
    }
    
    return $result;
}

sub publish {
    my ($self, $id) = @_;
    
    my $result = 0;
    
    return $result if(! $self->exist($id));
    
    my $query = sprintf("UPDATE casp13.structures SET is_published = 1, date = now() WHERE (id = %d)", $id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth)) {
        # add logger
        $result = defined($sth) ? 1 : 0;
    }
    
    return $result;
}

sub unpublish {
    my ($self, $id) = @_;
    
    my $result = 0;
    
    return $result if(! $self->exist($id));
    
    my $query = sprintf("UPDATE casp13.structures SET is_published = 0 WHERE (id = %d)", $id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth)) {
        # add logger
        $result = defined($sth) ? 1 : 0;
    }
    
    return $result;
}

sub info {
    my ($self, $target_id, $id) = @_;
    
    my %result = (
        TARGET => '',
        TARGET_ID => '',
        IS_PUBLISHED => '',
        RESOLUTION => '',
        R_FACTOR => '',
        SPACE_GROUP => '',
        NUMBER_OF_CHAINS => '',
        LIGANDS => '',
        NUMBER_OF_RESIDUES => '',
        MISSING => '',
        CHANGES => '',
        COMMENTS => '',
	DOMAIN_PARSER => '',
	CHAIN_SIMILARITY => '',
	CHAIN_REPRESENTATIVE => '',
	B_VALUE => '',
    );
    
    my $targets_manager = new TargetsManager();
    if(!defined($id) || ($id eq '-') || (int($id) <= 0)) {
        $result{TARGET} = $targets_manager->name($target_id);
        return %result;
    }
    
    return %result if(! $self->exist($id));
    
    my $query = sprintf("SELECT id, is_published, targets_id, resolution, r_factor, space_group, number_of_chains, ligands, number_of_residues, missing, changes, comments, domain_parser, chain_similarity, chain_representative, b_value FROM casp13.structures WHERE id = %d", $id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth) && ($sth->rows() > 0)) {
        ($result{ID}, $result{IS_PUBLISHED}, $result{TARGET_ID}, $result{RESOLUTION}, $result{R_FACTOR}, $result{SPACE_GROUP}, $result{NUMBER_OF_CHAINS}, $result{LIGANDS}, $result{NUMBER_OF_RESIDUES}, $result{MISSING}, $result{CHANGES}, $result{COMMENTS}, $result{DOMAIN_PARSER}, $result{CHAIN_SIMILARITY}, $result{CHAIN_REPRESENTATIVE}, $result{B_VALUE}) = $sth->fetchrow_array();
        $result{TARGET} = $targets_manager->name($result{TARGET_ID});
    }
    
    return %result;
}

sub info_extended {
    my ($self, $target_id, $id) = @_;

    my %result = (
        TARGET => '',
        TARGET_ID => '',
        IS_PUBLISHED => '',
        RESOLUTION => '',
        R_FACTOR => '',
        SPACE_GROUP => '',
        NUMBER_OF_CHAINS => '',
        LIGANDS => '',
        NUMBER_OF_RESIDUES => '',
        MISSING => '',
        CHANGES => '',
        COMMENTS => '',
        DOMAIN_PARSER => '',
        CHAIN_SIMILARITY => '',
        CHAIN_REPRESENTATIVE => '',
        B_VALUE => '',
	OLIGOMERIC_STATE => '',
	BLAST_INFO => '',
	HHSEARCH_INFO => '',
	SEQUENCE_FAMILY => '',
	CHAIN_SIMILARITY => '',
	DOMAIN_PARSER => ''
    );

    my $targets_manager = new TargetsManager();
    if(!defined($id) || ($id eq '-') || (int($id) <= 0)) {
        $result{TARGET} = $targets_manager->name($target_id);
        return %result;
    }

    return %result if(! $self->exist($id));

    my $query = sprintf("SELECT s.id, s.is_published, s.targets_id, s.resolution, s.r_factor, s.space_group, s.number_of_chains, s.ligands, s.number_of_residues, s.missing, s.changes, s.comments, s.domain_parser, s.chain_similarity, s.chain_representative, s.b_value, ss.oligomeric_state, ss.blast_info, ss.hhsearch_info, ss.sequence_family, s.chain_similarity, s.domain_parser FROM casp13.structures s JOIN casp13.targets t ON (t.id=s.targets_id) JOIN casp13.submitted_sequences ss ON (t.submitted_sequences_id=ss.id)  WHERE s.id = %d", $id);
    my $sth = $self->{_database}->query($query);

    if(defined($sth) && ($sth->rows() > 0)) {
        ($result{ID}, $result{IS_PUBLISHED}, $result{TARGET_ID}, $result{RESOLUTION}, $result{R_FACTOR}, $result{SPACE_GROUP}, $result{NUMBER_OF_CHAINS}, $result{LIGANDS}, $result{NUMBER_OF_RESIDUES}, $result{MISSING}, $result{CHANGES}, $result{COMMENTS}, $result{DOMAIN_PARSER}, $result{CHAIN_SIMILARITY}, $result{CHAIN_REPRESENTATIVE}, $result{B_VALUE}, $result{OLIGOMERIC_STATE}, $result{BLAST_INFO}, $result{HHSEARCH_INFO}, $result{SEQUENCE_FAMILY}, $result{CHAIN_SIMILARITY}, $result{DOMAIN_PARSER}) = $sth->fetchrow_array();
        $result{TARGET} = $targets_manager->name($result{TARGET_ID});
    }

    return %result;
}


sub update {
    my ($self, $id, $target_id, $resolution, $r_factor, $space_group, $number_of_chains, $ligands, $number_of_residues, $missing, $changes, $comments, $update_date, $domain_parser, $chain_similarity, $chain_representative, $b_value) = @_;
    
    if (!defined($domain_parser)){
        $domain_parser = '';
    }

    if (!defined($chain_similarity)){
        $chain_similarity = '';
    }

    if (!defined($chain_representative)){
        $chain_representative = '';
    }

    if (!defined($b_value)){
        $b_value = '';
    }

    my $result = 0;
    
    return $result if(! $self->exist($id));
    
    my $sub_query = '';
    if($update_date == 1) {
        $sub_query = ', date=now() ';
    }
    
    my $query = sprintf("UPDATE casp13.structures SET targets_id = %d, resolution = '%s', r_factor = '%s', space_group = '%s', number_of_chains = '%s', ligands = '%s', number_of_residues = '%s', missing = '%s', changes = '%s', comments = '%s', domain_parser='%s', chain_similarity='%s', chain_representative='%s', b_value='%s'  %s WHERE (id = %d)", $target_id, quotemeta($resolution), quotemeta($r_factor), quotemeta($space_group), quotemeta($number_of_chains), quotemeta($ligands), quotemeta($number_of_residues), quotemeta($missing), quotemeta($changes), quotemeta($comments), $domain_parser, $chain_similarity, $chain_representative, $b_value, $sub_query, $id);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth)) {
        $result = 1;
    }
    
    return $result;
}

sub add {
    my ($self, $target_id, $resolution, $r_factor, $space_group, $number_of_chains, $ligands, $number_of_residues, $missing, $changes, $comments, $domain_parser, $chain_similarity, $chain_representative, $b_value) = @_;

    if (!defined($domain_parser)){
	$domain_parser = '';
    }
    
    if (!defined($chain_similarity)){
	$chain_similarity = '';
    }

    if (!defined($chain_representative)){
        $chain_representative = '';
    }

    if (!defined($b_value)){
        $b_value = '';
    }

    my $result = 0;
    
    my $query = sprintf("INSERT INTO casp13.structures (targets_id, resolution, r_factor, space_group, number_of_chains, ligands, number_of_residues, missing, changes, comments, domain_parser, chain_similarity, chain_representative, b_value) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $target_id, $resolution, $r_factor, $space_group, $number_of_chains, $ligands, $number_of_residues, $missing, $changes, $comments, $domain_parser, $chain_similarity, $chain_representative, $b_value);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth)) {
        my $query = sprintf("SELECT MAX(id) FROM casp13.structures");
        my $sth = $self->{_database}->query($query);
        
        ($result) = $sth->fetchrow_array();
    }
    
    return $result;
}

sub releases {
    my ($self, $published_only) = @_;
    
    $published_only = (!defined($published_only) || ($published_only eq 1)) ? 1 : $published_only;
    
    my $published_only_query = '';
    if($published_only) {
        $published_only_query = sprintf(" AND (s.is_published = 1)");
    }

    my @releases = ();
    
    my $query = sprintf("SELECT s.id, s.date, t.id, t.name FROM casp13.structures s JOIN casp13.targets t ON (t.id = s.targets_id) WHERE 1=1 %s ORDER BY s.date DESC", $published_only_query);
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth) && ($sth->rows() > 0)) {
        my @structures = ();
        my $previous_date = '2000-01-01';
        my $index = 1;
        while(my($id, $date, $target_id, $target) = $sth->fetchrow_array()) {
            push(@structures, {
                INDEX => $index,
                ID => $id,
                DATE => $date,
                TARGET_ID => $target_id,
                TARGET_NAME => $target,
            });
            
            if($previous_date ne $date) {
                $previous_date = $date;
                
                push(@releases, { DATE => $date });
            }
            $index++;
        }
        
        for(my $i = 0; $i < scalar(@releases); $i++) {
            my @current_structures = ();
            for(my $j = 0; $j < scalar(@structures); $j++) {
                if($releases[$i]->{DATE} eq $structures[$j]->{DATE}) {
                    push(@current_structures, $structures[$j]);
                }
            }
            $releases[$i]->{TARGETS} = [@current_structures];
            $releases[$i]->{TARGETS_COUNT} = scalar(@current_structures);
        }
    }
    
    return @releases;
}

sub structures {
    my ($self, $field, $order, $published_only) = @_;
    
    my @structures = ();
    
    $field = (!defined($field) || ($field eq '')) ? 't.name' : $field;
    $order = (!defined($order) || ($order eq '')) ? 'ASC' : $order;
    $published_only = (!defined($published_only) || ($published_only eq 1)) ? 1 : $published_only;
    
    my $published_only_query = '';
    if($published_only) {
        $published_only_query = sprintf(" AND (s.is_published = 1)");
    }
    
    my $query = sprintf("SELECT s.id, s.is_published, t.id, t.name, s.resolution, s.r_factor, s.space_group, s.number_of_chains, s.ligands, s.number_of_residues, s.missing, s.changes, s.comments, ss.sequence, s.domain_parser, s.chain_similarity, s.chain_representative, s.b_value, ss.oligomeric_state, ss.blast_info, ss.hhsearch_info, ss.sequence_family, t.comments FROM casp13.targets t LEFT JOIN casp13.structures s ON (s.targets_id = t.id) JOIN casp13.submitted_sequences ss ON (ss.id = t.submitted_sequences_id) WHERE (t.name similar to 'T[0-9]%%') AND (t.release_date < now()) %s ORDER BY %s %s", $published_only_query, $field, $order);
    #print $query;
    my $sth = $self->{_database}->query($query);
    
    if(defined($sth) && ($sth->rows() > 0)) {
        my $domains_manager = new DomainsManager();
        
        my $index = 1;
        while(my($id, $is_published, $target_id, $target, $resolution, $r_factor, $space_group, $number_of_chains, $ligands, $number_of_residues, $missing, $changes, $comments, $sequence, $domain_parser, $chain_similarity, $chain_representative, $b_value, $oligomeric_state, $blast_info, $hhsearch_info, $sequence_family, $pred_released_info) = $sth->fetchrow_array()) {
            my @domains = $domains_manager->get_domains($target_id);
            my $domains_count = scalar(@domains);
            
            push(@structures, {
                    INDEX => $index,
                    ID => (!defined($id) ? '-' : $id),
                    IS_PUBLISHED => $is_published,
                    TARGET_ID => (!defined($target_id) ? '-' : $target_id),
                    TARGET => (!defined($target) ? '-' : $target),
                    HAS_TARGET_FILE => (defined($target) && (-e sprintf("%s/%s.pdb", $LOCAL_CONFIG->{TARGETS_DIR}, $target))),
                    HAS_CHAIN_SUPERPOSIONS_FILE => (defined($target) && (defined($number_of_chains) && int($number_of_chains) > 1) && (-e sprintf("%s/%s.%dchains.pdb", $LOCAL_CONFIG->{CHAIN_STRUCTURES_DIR}, $target, $number_of_chains))),
                    DOMAINS => [@domains],
                    DOMAINS_COUNT => $domains_count,
                    RESOLUTION => (!defined($resolution) || ($resolution eq '') ? '-' : $resolution),
                    R_FACTOR => (!defined($r_factor) || ($r_factor eq '') ? '-' : $r_factor),
                    SPACE_GROUP => (!defined($space_group) || ($space_group eq '') ? '-' : $space_group),
                    NUMBER_OF_CHAINS => (!defined($number_of_chains) || ($number_of_chains eq '') ? '-' : $number_of_chains),
                    LIGANDS => (!defined($ligands) || ($ligands eq '') ? '-' : $ligands),
                    NUMBER_OF_RESIDUES => (!defined($number_of_residues) || ($number_of_residues eq '') ? '-' : $number_of_residues),
                    MISSING => (!defined($missing) || ($missing eq '') ? '-' : $missing),
                    CHANGES => (!defined($changes) || ($changes eq '') ? '-' : $changes),
                    COMMENTS => (!defined($comments) || ($comments eq '') ? '-' : $comments),
                    NUMBER_OF_RESIDUES_SEQ => sequence_length($sequence),
		    DOMAIN_PARSER => (!defined($domain_parser) || ($domain_parser eq '') ? '-' : $domain_parser),
		    CHAIN_SIMILARITY => (!defined($chain_similarity) || ($chain_similarity eq '') ? '-' : $chain_similarity),
		    CHAIN_REPRESENTATIVE => (!defined($chain_representative) || ($chain_representative eq '') ? '-' : $chain_representative),
		    B_VALUE => (!defined($b_value) || ($b_value eq '') ? '-' : $b_value),
		    OLIGOMERIC_STATE => $oligomeric_state,
		    BLAST_INFO => $blast_info,
		    HHSEARCH_INFO => $hhsearch_info,
		    SEQUENCE_FAMILY => $sequence_family,
		    PREDICTORS_RELEASED_INFO => $pred_released_info,
            	}
            );
            $index++;
        }
    }
    
    return @structures;
}

1;
