=head1 LICENSE

  Copyright (c) 1999-2012 The European Bioinformatics Institute and
  Genome Research Limited.  All rights reserved.

  This software is distributed under a modified Apache license.
  For license details, please see

    http://www.ensembl.org/info/about/code_licence.html

=head1 CONTACT

  Please email comments or questions to the public Ensembl
  developers list at <dev@ensembl.org>.

  Questions may also be sent to the Ensembl help desk at
  <helpdesk@ensembl.org>.

=cut

=head1 NAME

Bio::EnsEMBL::DBSQL::MiscSetAdaptor - Provides database interaction for
Bio::EnsEMBL::MiscSet objects.

=head1 SYNOPSIS

  my $msa = $registry->get_adaptor( 'Human', 'Core', 'MiscSet' );

  my $misc_set = $msa->fetch_by_dbID(1234);

  $misc_set = $msa->fetch_by_code('clone');

=head1 DESCRIPTION

This class provides database interactivity for MiscSet objects.
MiscSets are used to classify MiscFeatures into groups.

=head1 METHODS

=cut

package Bio::EnsEMBL::DBSQL::MiscSetAdaptor;

use strict;
use warnings;

use Bio::EnsEMBL::MiscSet;
use Bio::EnsEMBL::DBSQL::BaseAdaptor;

use Bio::EnsEMBL::Utils::Exception qw(throw warning);

use vars qw(@ISA);

@ISA = qw(Bio::EnsEMBL::DBSQL::BaseAdaptor);


=head2 new

  Arg [...]  : Superclass args.  See Bio::EnsEMBL::DBSQL::BaseAdaptor
  Description: Instantiates a Bio::EnsEMBL::DBSQL::MiscSetAdaptor and
               caches the contents of the MiscSet table.
  Returntype : Bio::EnsEMBL::MiscSet
  Exceptions : none
  Caller     : MiscFeatureAdaptor
  Status     : Stable

=cut


sub new {
  my $class = shift;

  my $self = $class->SUPER::new(@_);

  $self->{'_id_cache'} = {};
  $self->{'_code_cache'} = {};

  # cache the entire contents of the misc set table
  # the table is small and it removes the need to repeatedly query the
  # table or join to the table

  $self->fetch_all();

  return $self;
}




=head2 fetch_all

  Arg [1]    : none
  Example    : foreach my $ms (@{$msa->fetch_all()}) {
                 print $ms->code(), ' ', $ms->name(), "\n";
               }
  Description: Retrieves every MiscSet defined in the DB.
               NOTE:  In a multi-species database, this method will
               return all the entries matching the search criteria, not
               just the ones associated with the current species.
  Returntype : listref of Bio::EnsEMBL::MiscSets
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut

sub fetch_all {
  my $self = shift;

  my $sth = $self->prepare
    ('SELECT misc_set_id, code, name, description, max_length FROM misc_set');

  $sth->execute();

  my ($dbID, $code, $name, $desc, $max_len);
  $sth->bind_columns(\$dbID, \$code, \$name, \$desc, \$max_len);

  my @all;

  while($sth->fetch()) {
    my $ms = Bio::EnsEMBL::MiscSet->new
      (-DBID     => $dbID,
       -ADAPTOR  => $self,
       -CODE     => $code,
       -NAME     =>  $name,
       -DESCRIPTION => $desc,
       -LONGEST_FEATURE => $max_len);

    $self->{'_id_cache'}->{$dbID} = $ms;
    $self->{'_code_cache'}->{lc($code)} = $ms;
    push @all, $ms;
  }

  $sth->finish();

  return \@all;
}



=head2 fetch_by_dbID

  Arg [1]    : int $dbID
               The internal identifier of the misc set to retrieve
  Example    : my $ms = $msa->fetch_by_dbID($dbID);
  Description: Retrieves a misc set via its internal identifier
  Returntype : Bio::EnsEMBL::MiscSet
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut

sub fetch_by_dbID {
  my $self = shift;
  my $dbID = shift;

  if(!$self->{'_id_cache'}->{$dbID}) {
    # on a cache miss reread the whole table and reload the cache
    $self->fetch_all();
  }

  return $self->{'_id_cache'}->{$dbID};
}



=head2 fetch_by_code

  Arg [1]    : string $code
               The unique code of the MiscSet to retrieve
  Example    : my $ms = $msa->fetch_by_code('clone');
  Description: Retrieves a MiscSet via its code
  Returntype : Bio::EnsEMBL::MiscSet
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut

sub fetch_by_code {
  my $self = shift;
  my $code = shift;

  if(!$self->{'_code_cache'}->{lc($code)}) {
    # on cache miss, reread whole table and reload cache
    $self->fetch_all();
  }

  return $self->{'_code_cache'}->{lc($code)};
}



=head2 store

  Arg [1]    : list of MiscSets @mist_sets
  Example    : $misc_set_adaptor->store(@misc_sets);
  Description: Stores a list of MiscSets in the database, and sets the
               dbID and adaptor attributes of the stored sets.
  Returntype : none
  Exceptions : throw on incorrect arguments
               warning if a feature is already stored in this database
  Caller     : MiscFeatureAdaptor::store
  Status     : Stable

=cut

sub store {
  my $self = shift;
  my @misc_sets = @_;

  # we use 'insert ignore' so that inserts can occur safely on the farm
  # otherwise 2 processes could try to insert at the same time and one
  # would fail

  my $sth = $self->prepare
    ("INSERT IGNORE INTO misc_set " .
     "SET code = ?, " .
     "    name = ?, " .
     "    description = ?, " .
     "    max_length  = ?");

  my $db = $self->db();

 SET:
  foreach my $ms (@misc_sets) {
    if(!ref($ms) || !$ms->isa('Bio::EnsEMBL::MiscSet')) {
      throw("List of MiscSet arguments expected.");
    }

    if($ms->is_stored($db)) {
      warning("MiscSet [".$ms->dbID."] is already stored in this database.");
      next SET;
    }

    $sth->bind_param(1,$ms->code,SQL_VARCHAR);
    $sth->bind_param(2,$ms->name,SQL_VARCHAR);
    $sth->bind_param(3,$ms->description,SQL_LONGVARCHAR);
    $sth->bind_param(4,$ms->longest_feature,SQL_INTEGER);

    my $num_inserted = $sth->execute();

    my $dbID;

    if($num_inserted == 0) {
      # insert failed because set with this code already exists
      my $sth2 = $self->prepare("SELECT misc_set_id from misc_set " .
                                "WHERE code = ?");
      $sth2->bind_param(1,$ms->code,SQL_VARCHAR);
      $sth2->execute();

      if($sth2->rows() != 1) {
        throw("Could not retrieve or store MiscSet, code=[".$ms->code."]\n".
              "Wrong database user/permissions?");
      }

      ($dbID) = $sth2->fetchrow_array();
    } else {
      $dbID = $sth->{'mysql_insertid'};
    }

    $ms->dbID($dbID);
    $ms->adaptor($self);

    # update the internal caches
    $self->{'_id_cache'}->{$dbID} = $ms;
    $self->{'_code_cache'}->{lc($ms->code())} = $ms;
  }

  return;
}

=head2 update

  Arg [1]    : Bio::EnsEMBL::MiscSet $miscset
  Example    : $adaptor->update($miscset)
  Description: Updates this misc_set in the database
  Returntype : int 1 if update is performed, undef if it is not
  Exceptions : throw if arg is not an misc_set object
  Caller     : ?
  Status     : Stable

=cut

sub update {
  my $self = shift;
  my $m    = shift;

  if (!ref($m) || !$m->isa('Bio::EnsEMBL::MiscSet')) {
    throw("Expected Bio::EnsEMBL::MiscSet argument.");
  }

  if(!$m->is_stored($self->db())) {
    return undef;
  }

  my $sth = $self->prepare("UPDATE misc_set ".
			   "SET code =?, name =?, description = ?, max_length = ? ".
			   "WHERE misc_set_id = ?");

  $sth->bind_param(1,$m->code,SQL_VARCHAR);
  $sth->bind_param(2,$m->name,SQL_VARCHAR);
  $sth->bind_param(3,$m->description,SQL_VARCHAR);
  $sth->bind_param(4,$m->longest_feature,SQL_INTEGER);
  $sth->bind_param(5,$m->dbID,SQL_INTEGER);

  $sth->execute();
  $sth->finish();

 # update the internal caches
  $self->{'_id_cache'}->{$m->dbID} = $m;
  $self->{'_code_cache'}->{lc($m->code())} = $m;
}

1;
