package EnsEMBL::Web::Factory::DASCollection;
=head1 NAME

EnsEMBL::Web::Factory::DASCollection;

=head1 SYNOPSIS

Module to create EnsEMBL::Web::Factory::DASCollection objects.

=head1 DESCRIPTION

Example:

my $dasfact = EnsEMBL::Web::Proxy::Factory->new( 'DASCollection', { '_databases' => $dbc, '_input' => $input } );
$dasfact->createObjects();
my( $das_collection) = @{$dasfact->DataObjects};

Creates DASCollection Data objects to be used within the web_api.

=head1 LICENCE

This code is distributed under an Apache style licence:
Please see http://www.ensembl.org/code_licence.html for details

=head1 CONTACT

Eugene Kulesha - ek3@sanger.ac.uk

=cut

use strict;
use warnings;
no warnings "uninitialized";

use Bio::EnsEMBL::ExternalData::DAS::DASAdaptor;
use Bio::EnsEMBL::ExternalData::DAS::DAS;

use EnsEMBL::Web::Factory;
use EnsEMBL::Web::Problem;
use EnsEMBL::Web::Proxy::Object;
use SOAP::Lite;
use Data::Dumper;
use vars qw( @ISA );
@ISA = qw(  EnsEMBL::Web::Factory );

#----------------------------------------------------------------------

=head2 _createObjects

  Arg [1]   : none
  Function  : Creates EnsEMBL::Web::Data::DASCollection objects
              1. examines SpeciesDefs for DAS config
              2. examines Input for DAS config
  Returntype: 
  Exceptions: 
  Caller    : $self->createObjects
  Example   : $self->_createObjects

=cut

sub createObjects {
  my $self = shift;
# Get the view that is requesting DASCollection Factory
  my $conf_script = $self->param("conf_script") || $self->script;

  my %sources_conf;
  my $daslist = $self->get_session->get_internal_das; 
  for my $source( keys %$daslist ) {
    my $source_config = $daslist->{$source}->get_data;
    my %valid_scripts = map{ $_, 1 } @{$source_config->{enable} || [] };
    $valid_scripts{$conf_script} || next;
    my $das_species = $source_config->{'species'};
    next if( $das_species && $das_species ne '' && $das_species ne $ENV{'ENSEMBL_SPECIES'} );
    $source_config->{conftype} ||= 'internal';
    $sources_conf{$source}    = $source_config;
  }
# Add external sources (ones added by user)
  $daslist = $self->get_session->get_das;
  for my $source ( keys %$daslist ) {
    my $source_config = $daslist->{$source}->get_data;
    my %valid_scripts = map{ $_, 1 } @{$source_config->{enable} || [] };
    $valid_scripts{$conf_script} || next;
    my $das_species = $source_config->{'species'};
    next if( $das_species && $das_species ne '' && $das_species ne $ENV{'ENSEMBL_SPECIES'} );
    if( $source_config->{url} =~ /\/das$/ ) {
      $source_config->{url} .= "/$source_config->{dsn}";
    }
    $source_config->{type} = $source_config->{mapping}->[0] unless $source_config->{type};
    $source_config->{conftype} ||= 'external';
    $sources_conf{$source}    = $source_config;
  }

# Get parameters of the view that has called upon dasconfview
  my %script_params = ();
  my @udas = ();
  my @conf_params = split('zzz', $self->param("conf_script_params") || '');

  foreach my $p (@conf_params) {
    next if ($p =~ /^=/);
    my @plist = split('=', $p);
    if ($plist[0] eq 'data_URL') {
      push(@udas, $plist[1]);
    } elsif ($plist[0] eq 'h' || $plist[0] eq 'highlight') {
      if (defined($plist[1])) {
        my @hlist = split(/\|/, $plist[1]);
        foreach my $h (@hlist) {
          if ($h =~ /URL:/) {
            $h =~ s/URL://;
            push(@udas, $h);
          } else {
            push(@{$script_params{$plist[0]}}, $plist[1] || '');
          }
        }
      }
    } else {
      push(@{$script_params{$plist[0]}}, $plist[1] || '');
    }
  }
# Add sources that are attached via URL
  my $urlnum = 1;
  foreach my $u (@udas) {
    my $das_name = "_URL_$urlnum";
#  warn ("ADD URL");
    $sources_conf{$das_name}->{name} = $das_name;
    $sources_conf{$das_name}->{url} = $u;
    $sources_conf{$das_name}->{conftype} = 'url';
    $urlnum ++;
  }

# Get the sources selection, i.e which sources' annotation should be displayed
  my $config = $self->get_imageconfig( 'dasconfview' );
  my $section = $conf_script;

  $config->reset_subsection($section);
#    warn("DAS Select : ".join('*', $self->param('das_sources')));
  foreach my $src ( $self->param('das_sources')) {
    $config->set($section, $src, "on", 1);
  }
  $config->save( );

  my %DASsel = map {$_ => 1} $self->param('das_sources');

# Process the dasconfig form input - Get DAS sources to add/delete/edit;
  my %das_submit = map{$_,1} ($self->param( "_das_submit" ) || ());
  my %das_del    = map{$_,1} ($self->param( "_das_delete" ) || ());
  my %urldas_del = map{$_,1} ($self->param( "_urldas_delete" ) || ());
  my %das_edit   = map{$_,1} ($self->param( "_das_edit" ) || ());

  foreach (keys (%das_del)){
    $self->get_session->remove_das_source($_);
    delete($sources_conf{$_});
  }
  foreach (keys %urldas_del){
    delete($sources_conf{$_});
  }
  if (defined(my $dedit  = $self->param("DASedit"))) {
    $sources_conf{$dedit}->{conftype} = 'external_editing';
  }

  my @confkeys = qw( name type);
  my @allkeys = ('strand', 'labelflag', 'label', 'url', 'conftype', 'group', 'stylesheet', 'score', 'fg_merge', 'fg_grades', 'fg_data', 'fg_min', 'fg_max', 'caption', 'active', 'color', 'depth', 'help', 'linktext', 'linkurl', 'assembly' );
  my @arr_keys = ('enable', 'mapping');

    # Add '/das' suffix to _das_domain param
  if( my $domain = $self->param( "DASdomain" ) ){
    if ($domain ne $self->species_defs->DAS_REGISTRY_URL) {
      $domain =~ s/(\/das)?\/?\s*$/\/das/;
      $self->param('DASdomain',$domain );
    }
  }
    # Have we got new DAS? If so, validate, and add to Input

  my $source_type = $self->param("DASsourcetype");
  if( $self->param("_das_submit") ){
    if ($self->param("DASsourcetype") eq 'das_url') {
      my $url = $self->param("DASurl") || ( warn( "_error_das_url: Need a url!") &&  $self->param( "_error_das_url", "Need a url!" ));
      my $das_name = "_URL_$urlnum"; 
      $sources_conf{$das_name}->{name} = $das_name;
      $sources_conf{$das_name}->{url} = $url;
      $sources_conf{$das_name}->{conftype} = 'url';
    } elsif (my $das_name = $self->param("DASedit")) { # Edit source
      my $das_dsn = $self->param("DASdsns");
      my $das_data = $sources_conf{$das_name};
      foreach my $key( @confkeys, @allkeys){
        if (defined($self->param("DAS${key}"))) {
          $das_data->{$key} = $self->param("DAS${key}");
        }
      }
      $das_data->{active} = 1; # Enable by default
      # Add to the conf list
      $das_data->{name} = $das_name;
      $das_data->{label} ||= $das_data->{name};
      $das_data->{caption} ||= $das_data->{name};
      @{$das_data->{enable}} = $self->param('DASenable');
      @{$das_data->{mapping}} = grep { $_ } $self->param('DAStype');
      $das_data->{type} = 'mixed' if (scalar(@{$das_data->{mapping}}>1));

      $das_data->{conftype} = 'external';
      $sources_conf{$das_name} ||= {};

      foreach my $key( @confkeys, @allkeys, 'dsn', 'enable', 'mapping') {
        $sources_conf{$das_name}->{$key} = $das_data->{$key};
      }

      $self->session->add_das_source_from_hashref($das_data);
      $DASsel{$das_name} = 1;
    } else {
      my $err = 0;
      my @das_sources;
      if (defined( my $usersrc = $self->param("DASuser_source") || undef)) {
        push @das_sources, {
          'url' => join('/','http:/',$self->species_defs->ENSEMBL_DAS_UPLOAD_SERVER,'das', $usersrc),
          'id' => $usersrc,
          'dsn' => $usersrc
        };
      } else {
        my $shash = $self->getServerSources($self->param('DASdomain'));
 #       warn Dumper ($shash);
        foreach my $id ($self->param("DASdsns")) {
#	  warn "SRC: $id";
#	  warn Dumper $shash->{$id};
	  if (exists $shash->{$id} ) {
#	  	warn "E";
	  } else {
	    $shash->{$id} = {
            'id'  => $id,
            'url' => join ('/', $self->param('DASdomain'), $id),
            'url' => $self->param('DASdomain'),
            'dsn' => $id,
	    'name' => $id,
            };
	  }
          foreach my $key( @confkeys, @allkeys){
            if (defined($self->param("DAS${key}"))) {
              $shash->{$id}->{$key} = $self->param("DAS${key}");
            }
          }
          push @das_sources, $shash->{$id};
	}
      }
# warn "FF:",Dumper(\@das_sources);

      foreach my $das_data (@das_sources) {
        my $das_name = $das_data->{'name'} or next;
        if( exists( $sources_conf{$das_name} ) and  (! defined($sources_conf{$das_name}->{conftype}) or $sources_conf{$das_name}->{conftype} ne 'external_editing' )){ 
          my $das_name_ori = $das_name;
          for( my $i = 1; 1; $i++ ){
            $das_name = $das_name_ori ."_$i";
            if( ! exists( $sources_conf{$das_name} ) ){
              $das_data->{name} =  $das_name;
              last;
            }
          }
        }
	$das_data->{'name'} = $das_name;
        $das_data->{active} = 1; # Enable by default
    # Add to the conf list
        $das_data->{id} = $das_data->{name} if ($das_data->{name} ne $das_name);
        $das_data->{label} ||= $das_data->{name};
        $das_data->{caption} ||= $das_data->{name};
        @{$das_data->{enable}} = $self->param('DASenable');
        $das_data->{mapping} or @{$das_data->{mapping}} = grep { $_ } $self->param('DAStype');
        $das_data->{type} = 'mixed' if (scalar(@{$das_data->{mapping}}>1));
        $das_data->{conftype} = 'external';
        $das_data->{species} = $self->species;
        $sources_conf{$das_name} ||= {};
        foreach my $key( @confkeys, @allkeys, 'dsn', 'enable', 'mapping') {
          $sources_conf{$das_name}->{$key} = $das_data->{$key};
        }
#	warn "DATA ($das_name)";
#warn Dumper($das_data);
        $self->session->add_das_source_from_hashref($das_data);
        $DASsel{$das_name} = 1;
      }
    }
  }
    # Clean up any 'dangling' _das parameters
  if( $self->delete_param( "_das_delete" ) ){
    foreach my $key( @confkeys ){ $self->delete_param("DAS$key") }
  }
  my @udaslist = ();
  my @das_objs = ();
# Now we have a list of all active das sources - for each of them  create a DAS adaptor capable of retrieving das features 
  foreach my $source( sort keys %sources_conf ){
  # Create the DAS adaptor from the (valid) conf
    my $source_conf = $sources_conf{$source};
#warn Dumper($source_conf);
    push (@udaslist, "URL:$source_conf->{url}") if ($source_conf->{conftype} eq 'url');
    if( ! $source_conf->{url} and ! ( $source_conf->{protocol} && $source_conf->{domain} ) ){
      next;
    }
    $source_conf->{active} = defined ($DASsel{$source}) ? 1 : 0;
    $source_conf->{label} ||= $source_conf->{name};
    my $das_adapt = Bio::EnsEMBL::ExternalData::DAS::DASAdaptor->new( 
      -name       => $source,
      -timeout    => $self->species_defs->ENSEMBL_DAS_TIMEOUT,
      -url        => $source_conf->{url}       || '',
      -type       => $source_conf->{type}      || '',
      -authority  => $source_conf->{authority} || '',
      -label      => $source_conf->{label}     || '',
      -labelflag  => $source_conf->{labelflag} || '',
      -caption    => $source_conf->{caption}   || '',
      -color      => $source_conf->{color} || $source_conf->{col} || '',
      -linktext   => $source_conf->{linktext}  || '',
      -linkurl    => $source_conf->{linkurl}   || '',
      -strand     => $source_conf->{strand}    || '',
      -depth      => $source_conf->{depth},
      -group      => $source_conf->{group}     || '',
      -stylesheet => $source_conf->{stylesheet}|| '',
      -score      => $source_conf->{score} || '',
      -fg_merge   => $source_conf->{fg_merge} || '',
      -fg_grades  => $source_conf->{fg_grades},
      -fg_data    => $source_conf->{fg_data} || '',
      -fg_min     => $source_conf->{fg_min},
      -fg_max     => $source_conf->{fg_max},
      -conftype   => $source_conf->{conftype}  || 'external',
      -active     => $source_conf->{active}    || 0, 
      -description=> $source_conf->{description}    || '', 
      -types      => $source_conf->{types} || [],
      -on         => $source_conf->{on}    || [],
      -enable     => $source_conf->{enable}    || [],
      -help       => $source_conf->{help}    || '',
      -mapping    => $source_conf->{mapping}    || [],
      -fasta      => $source_conf->{fasta} || [],
      -assembly_version	  => $source_conf->{assembly} || '',
    );        
    if ($das_adapt) {
      $das_adapt->ensembldb( $self->DBConnection('core') );
      if( my $p = $self->species_defs->ENSEMBL_WWW_PROXY ){
        $das_adapt->proxy($p);
      }
  # Create the DAS object itself
      my $das_obj = Bio::EnsEMBL::ExternalData::DAS::DAS->new( $das_adapt );
      push @das_objs, $das_obj;
    } else {
      $DASsel{$source} = 0;
### Replace with sesssion call
      $self->get_session->remove_das_source( $source );
    }
  }
  my @selection = grep {$DASsel{$_}} keys %DASsel;
  $self->param('das_sources', @selection);
    # Create the collection object
  my $dataobject = EnsEMBL::Web::Proxy::Object->new( 'DASCollection', [@das_objs], $self->__data );
  $self->DataObjects( $dataobject );
  return 1; #success
}

sub getEnsemblMapping {
  my ($self, $cs) = @_;
  my ($realm, $base, $species) = ($cs->{name}, $cs->{category}, $cs->{organismName});
  my $smap ='unknown';
  if ($base =~ /Chromosome|Clone|Contig|Scaffold/) {
    $smap = 'ensembl_location_'.lc($base);
  } elsif ($base eq 'NT_Contig') {
    $smap = 'ensembl_location_supercontig';
  } elsif ($base eq 'Gene_ID') {
    $smap = $realm eq 'Ensembl'       ? 'ensembl_gene'
          : $realm eq 'HUGO_ID'       ? 'hgnc'
          : $realm eq 'HGNC_ID'       ? 'hgnc'
          : $realm eq 'MGI'           ? 'mgi_acc'
          : $realm eq 'MarkerSymbol'  ? 'mgi'
          : $realm eq 'MGISymbol'     ? 'mgi'
          : $realm eq 'EntrezGene'    ? 'entrezgene_acc'
          : $realm eq 'IPI_Accession' ? 'ipi_acc'
          : $realm eq 'IPI_ID'        ? 'ipi_id'
          :                             'unknown'
          ;
  } elsif ($base eq 'Protein Sequence') {
    $smap = $realm eq 'UniProt'       ? 'uniprot/swissprot_acc'
          : $realm eq 'TrEMBL'        ? 'uniprot/sptrembl'
          : $realm =~ /Ensembl/       ? 'ensembl_peptide'
          :                             'unknown'
          ;
  }

  $species or $species = '.+';
#    warn "B:$cs#".join('*', $realm, $base, $species)."#$smap";
  return wantarray ? ($smap, $species) : $smap;
}

sub getRegistrySources {
  my $self = shift;
  if (defined($self->{data}->{_das_registry})) {
    return $self->{data}->{_das_registry};
  }
  my $filterT = sub { return 1; };
  my $filterM = sub { return 1; };
  my $keyText = $self->param('keyText');
  my $keyMapping = $self->param('keyMapping');

  if (defined (my $dd = CGI::param('_apply_search=registry.x'))) {
    if ($keyText) {
      $filterT = sub { 
        my $src = shift; 
        return 1 if ($src->{url} =~ /$keyText/); 
        return 1 if ($src->{nickname} =~ /$keyText/); 
        return 1 if ($src->{description} =~ /$keyText/); 
        return 0;
      };
    }
    if ($keyMapping ne 'any') {
      $filterM = sub { 
        my $src = shift; 
        foreach my $cs (@{$src->{coordinateSystem}}) {
          return 1 if ($self->getEnsemblMapping($cs) eq $keyMapping);
          return 1 if ($self->getEnsemblMapping($cs) =~ /^$keyMapping/);
        }
        return 0;
      };
    }
  }
  my $das_url = $self->species_defs->DAS_REGISTRY_URL;
  my $source_arr = SOAP::Lite->service("${das_url}/services/das:das_directory?wsdl")->listServices();
  my $i = 0;
  my %registryHash = ();
  my $spec = $ENV{ENSEMBL_SPECIES};
  $spec =~ s/\_/ /g;
  while(ref $source_arr->[$i]){
    my $dassource = $source_arr->[$i++];
    next if ("@{$dassource->{capabilities}}" !~ /features/);
    foreach my $cs (@{$dassource->{coordinateSystem}}) {
      my ($smap, $sp) = $self->getEnsemblMapping($cs);
      if ($smap ne 'unknown' && ($spec =~ /$sp/) && $filterT->($dassource) && $filterM->($dassource)) {
        my $id = $dassource->{id};
        $registryHash{$id} = $dassource; 
        last;
      }
    }
  }
  $self->{data}->{_das_registry} = \%registryHash;
  return $self->{data}->{_das_registry};
}
sub getSourceData {
  my ($self, $dassource, $dasconf) = @_;

  if ($dassource->{url} =~ m!(\w+)://(.+/das)/([\w\-]+)/?!) {
    ($dasconf->{protocol}, $dasconf->{domain}, $dasconf->{dsn}) = ($1, $2, $3);
    my ($smap, $species);
    foreach my $cs (@{$dassource->{coordinateSystem}}) {
      ($smap, $species) = $self->getEnsemblMapping($cs);
      push (@{$dasconf->{mapping}}, $smap) if ($smap ne 'unknown' && (! grep {$_ eq $smap} @{$dasconf->{mapping}}));
    }
    $dasconf->{name} = $dassource->{nickname};
    ($dasconf->{url} = $dassource->{url}) =~ s!/$!!;
    $dasconf->{type} = scalar(@{$dasconf->{mapping}}) > 1 ? 'mixed' : $smap;
  }
}

sub getServerSources {
  my $self = shift;
  my ($url) = @_;
  if (defined($self->{data}->{_das_sources}->{$url})) {
    return $self->{data}->{_das_sources}->{$url};
  }
#    next if ("@{$dassource->{capabilities}}" !~ /features/);
#    foreach my $cs (@{$dassource->{coordinateSystem}}) {
#      my ($smap, $sp) = $self->getEnsemblMapping($cs);

  my $spec = $ENV{ENSEMBL_SPECIES};

  my $filterT = sub { return 1; };
  my $filterM = sub { my $src = shift;  return 1 unless defined ($src->{species}); return 1 if ($src->{species} eq $spec);  return 0};
  my $keyText = $self->param('keyText');
  my $keyMapping = $self->param('keyMapping');

  if (defined (CGI::param('_apply_search=registry.x')) || defined ($self->param('_das_filter'))) {
    if ($keyText) {
      $filterT = sub { 
        my $src = shift; 
        return 1 if ($src->{url} =~ /$keyText/); 
        return 1 if ($src->{nickname} =~ /$keyText/); 
        return 1 if ($src->{description} =~ /$keyText/); 
        return 0;
      };
    }
    if ($keyMapping ne 'any') {
      $filterM = sub { 
        my $src = shift; 
        foreach my $cs (@{$src->{coordinateSystem}}) {
          return 1 if ($self->getEnsemblCoordinateSystem($cs) eq $keyMapping);
          return 1 if ($self->getEnsemblCoordinateSystem($cs) =~ /^$keyMapping/);
        }
        return 0;
      };
    }
  }

  my $adaptor = Bio::EnsEMBL::ExternalData::DAS::DASAdaptor->new(
    -url  => $url, -timeout   => $self->species_defs->ENSEMBL_DAS_TIMEOUT,
    -proxy_url => $self->species_defs->ENSEMBL_WWW_PROXY
  );
  my $das = Bio::EnsEMBL::ExternalData::DAS::DAS->new ( $adaptor );
  my %dsnhash = map {$_->{id}, $_} grep {$filterT->($_) && $filterM->($_) } @{ $das->fetch_sources_info };
  if( %dsnhash ){
    $self->{data}->{_das_sources}->{$url} = \%dsnhash;
  } else {
    $self->param('_error_das_domain', 'No sources for domain');
  }
  return $self->{data}->{_das_sources}->{$url};
}
1;