=head1 LICENSE

See the NOTICE file distributed with this work for additional information
regarding copyright ownership.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

=cut

=head1 NAME

Bio::EnsEMBL::Compara::RunnableDB::ReindexMembers::ReindexGenomedbs

=head1 DESCRIPTION

This runnable takes the reindexing maps previously generated
by L<PrepGenomedbReindexing>, and updates the genome_db_id
and method_link_species_set_id values in a table that has
been copied from a previous gene-tree database.

If the given table is method_link_species_set_tag, the runnable also
updates homology MLSS tags that are known to contain a genome_db_id.

=cut

package Bio::EnsEMBL::Compara::RunnableDB::ReindexMembers::ReindexGenomedbs;

use strict;
use warnings;

use File::Spec::Functions qw(catfile);
use JSON qw(decode_json);

use base ('Bio::EnsEMBL::Compara::RunnableDB::BaseRunnable');


sub fetch_input {
    my $self = shift @_;

    my $reindexing_dir = $self->param_required('genome_reindexing_dir');

    my $gdb_map_file = catfile($reindexing_dir, 'genome_db_id.json');
    my $gdb_reindexing_map = decode_json($self->_slurp($gdb_map_file));
    $self->param('gdb_reindexing_map', $gdb_reindexing_map);

    my $mlss_map_file = catfile($reindexing_dir, 'method_link_species_set_id.json');
    my $mlss_reindexing_map = decode_json($self->_slurp($mlss_map_file));
    $self->param('mlss_reindexing_map', $mlss_reindexing_map);
}


sub run {
    my $self = shift;

    my $member_type = $self->param_required('member_type');
    my $table_name = $self->param_required('table');

    my $gdb_reindexing_map = $self->param('gdb_reindexing_map');
    my $mlss_reindexing_map = $self->param('mlss_reindexing_map');

    # We don't currently need to reindex columns such as 'dnafrag_id',
    # but we do need to know if that situation changes.
    my @unsupported_col_names = ('dnafrag_id', 'species_set_id');
    foreach my $unsupported_col_name (@unsupported_col_names) {
        my $col_name_found = scalar(@{$self->_list_matching_column_names($table_name, $unsupported_col_name)}) > 0 ? 1 : 0;
        if ($col_name_found) {
            $self->die_no_retry(
                sprintf(
                    "cannot reindex genomes: table %s has a %s column, but reindexing of this column has not been implemented",
                    $table_name,
                    $unsupported_col_name,
                )
            );
        }
    }


    my @reindexing_statements;

    my $gdb_id_col_names = $self->_list_matching_column_names($table_name, '%genome_db_id');  # e.g. genome_db_id, qgenome_db_id, hgenome_db_id
    foreach my $gdb_id_col_name (@{$gdb_id_col_names}) {
        while (my ($prev_gdb_id, $curr_gdb_id) = each %{$gdb_reindexing_map}) {
            my $statement = "UPDATE $table_name SET $gdb_id_col_name = $curr_gdb_id WHERE $gdb_id_col_name = $prev_gdb_id";
            push(@reindexing_statements, $statement);
        }
    }

    my $mlss_id_col_names = $self->_list_matching_column_names($table_name, 'method_link_species_set_id');
    foreach my $mlss_id_col_name (@{$mlss_id_col_names}) {
        while (my ($prev_mlss_id, $curr_mlss_id) = each %{$mlss_reindexing_map}) {
            my $statement = "UPDATE $table_name SET $mlss_id_col_name = $curr_mlss_id WHERE $mlss_id_col_name = $prev_mlss_id";
            push(@reindexing_statements, $statement);
        }
    }


    if ($table_name eq 'method_link_species_set_tag') {

        # Some MLSS tags include the previous genome_db_id in the tag string, so we delete these
        # before reindexing, then reinsert them afterwards with the current genome_db_id.

        my %mlss_tag_map;
        while (my ($prev_gdb_id, $curr_gdb_id) = each %{$gdb_reindexing_map}) {
            # Map tags generated by Bio::EnsEMBL::Compara::RunnableDB::OrthologQM::FlagHighConfidenceOrthologs
            my $prev_high_conf_tag = "n_${member_type}_high_confidence_${prev_gdb_id}";
            my $curr_high_conf_tag = "n_${member_type}_high_confidence_${curr_gdb_id}";
            $mlss_tag_map{$prev_high_conf_tag} = $curr_high_conf_tag;

            # Map tags generated by Bio::EnsEMBL::Compara::RunnableDB::GeneTrees::OrthologyStats
            foreach my $c1 ('one', 'many') {
                foreach my $c2 ('one', 'many') {
                    my $prev_avg_perc_id_tag = "avg_${member_type}_${c1}-to-${c2}_${prev_gdb_id}_perc_id";
                    my $curr_avg_perc_id_tag = "avg_${member_type}_${c1}-to-${c2}_${curr_gdb_id}_perc_id";
                    $mlss_tag_map{$prev_avg_perc_id_tag} = $curr_avg_perc_id_tag;

                    my $prev_n_genes_tag = "n_${member_type}_${c1}-to-${c2}_${prev_gdb_id}_genes";
                    my $curr_n_genes_tag = "n_${member_type}_${c1}-to-${c2}_${curr_gdb_id}_genes";
                    $mlss_tag_map{$prev_n_genes_tag} = $curr_n_genes_tag;
                }
            }
        }

        my @known_prev_mlss_tags = keys %mlss_tag_map;
        my $known_prev_mlss_tag_placeholders = '(' . join(',', ('?') x @known_prev_mlss_tags) . ')';

        my @delete_statements;
        my @insert_statements;
        my $helper = $self->compara_dba->dbc->sql_helper;
        while (my ($prev_mlss_id, $curr_mlss_id) = each %{$mlss_reindexing_map}) {

            my $mlss_tag_query = qq/
                SELECT
                    *
                FROM
                    method_link_species_set_tag
                WHERE
                    method_link_species_set_id = ?
                AND
                    tag IN $known_prev_mlss_tag_placeholders
            /;

            my $results = $helper->execute(
                -SQL => $mlss_tag_query,
                -PARAMS => [$prev_mlss_id, @known_prev_mlss_tags],
            );

           foreach my $result (@{$results}) {
                my ($prev_mlss_id, $prev_mlss_tag, $value) = @{$result};
                my $curr_mlss_tag = $mlss_tag_map{$prev_mlss_tag};

                my $delete_statement = qq/
                    DELETE FROM
                        method_link_species_set_tag
                    WHERE
                        method_link_species_set_id = $prev_mlss_id
                    AND
                        tag = '$prev_mlss_tag'
                /;
                push(@delete_statements, $delete_statement);

                my $insert_statement = qq/
                    INSERT INTO
                        method_link_species_set_tag
                    VALUES
                        ($curr_mlss_id, '$curr_mlss_tag', '$value')
                /;
                push(@insert_statements, $insert_statement);
           }
        }

        @reindexing_statements = (@delete_statements, @reindexing_statements, @insert_statements);
    }


    if (scalar(@reindexing_statements) == 0) {
        $self->complete_early("No reindexing statements generated for table $table_name; skipping");
    }

    $self->warning(
        sprintf(
            "Generated %d reindexing statements for table %s",
            scalar(@reindexing_statements),
            $table_name,
        )
    );

    $self->param('reindexing_statements', \@reindexing_statements);
}


sub write_output {
    my ($self) = @_;

    my $reindexing_statements = $self->param('reindexing_statements');

    # Quite a big transaction ahead. Make sure hive_capacity is set to 1 to avoid timeout on other threads !
    $self->call_within_transaction( sub {
        foreach my $statement (@{$reindexing_statements}) {
            $self->compara_dba->dbc->do($statement);
        }
    } );
}


sub _list_matching_column_names {
    my ($self, $table_name, $column_pattern) = @_;

    my $sth = $self->compara_dba->dbc->db_handle->column_info(undef, undef, $table_name, $column_pattern);
    my $column_info = $sth->fetchall_hashref('COLUMN_NAME');
    my @matching_col_names = keys %{$column_info};

    return \@matching_col_names;
}


1;
