package EnsEMBL::Web::DASConfig;

use strict;
use warnings;
no warnings 'uninitialized';

use base qw(Bio::EnsEMBL::ExternalData::DAS::Source);

use Time::HiRes qw(time);
use Bio::EnsEMBL::Utils::Exception qw(warning);
use Bio::EnsEMBL::ExternalData::DAS::CoordSystem;
use Bio::EnsEMBL::ExternalData::DAS::SourceParser qw(%GENE_COORDS %PROT_COORDS is_genomic);

# Create a new SourceConfig using a hash reference for parameters.
# Can also use an existing Bio::EnsEMBL::ExternalData::DAS::Source or
# EnsEMBL::Web::DASConfig object.
# Hash should contain:
#   url
#   dsn
#   coords
#   logic_name    (optional, defaults to dsn)
#   label         (optional)
#   caption       (optinal short label)
#   description   (optional)
#   homepage      (optional)
#   maintainer    (optional)
#   on            (views enabled on)
#   category      (menu location)
sub new_from_hashref {
  my ( $class, $hash ) = @_;
  $hash->{'coords'} = [ map {
    ref $_ ? Bio::EnsEMBL::ExternalData::DAS::CoordSystem->new_from_hashref($_)
           : Bio::EnsEMBL::ExternalData::DAS::CoordSystem->new_from_string($_)
  } @{$hash->{'coords'}||[]} ];

  # Convert old-style type & assembly parameters to single coords
  if (my $type = $hash->{type}) {
    my $c = $GENE_COORDS{$type} || $PROT_COORDS{$type};
    if ( $c ) {
      push @{ $hash->{coords} }, $c;
    } else {
      $type =~ s/^ensembl_location_//;
      push @{ $hash->{coords} }, Bio::EnsEMBL::ExternalData::DAS::CoordSystem->new(
        -name    => $type,
        -version => $hash->{assembly},
        -species => $ENV{ENSEMBL_SPECIES},
      );
    }
  }
  # Create a Bio::EnsEMBL::ExternalData::DAS::Source object to wrap
  # Valid params: url, dsn, coords, logic_name, label, description, homepage, maintainer
  my %params = map { '-'.uc $_ => $hash->{$_} } keys %{ $hash };
  my $self   = $class->SUPER::new( %params );
  bless $self, $class;
  # Map "old style" view names to the new:
  my %views = ( geneview   => 'Gene/ExternalData',
                protview   => 'Transcript/ExternalData',
                contigview => 'contigviewbottom');
  if ($hash->{enable} || $hash->{on}) {
    $hash->{on} = [ map { $views{$_} || $_ } @{$hash->{on}||[]},@{$hash->{enable}||[]} ] ;
  }
  for my $var ( qw( on category caption )  ) {
    if ( exists $hash->{$var} ) {
      $self->$var( $hash->{$var} );
    }
  }
  return $self;
}

# DAS sources can be added from URL
# URL will have to be of the following format:
# /geneview?gene=BRCA2&add_das_source=(url=http://das1:9999/das+dsn=mouse_ko_allele+type=markersymbol+name=MySource+active=1)
# other parameters also can be specified, but those are optional ..
#  !!! You have to make sure that the name is unique before calling this function !!!
# TODO: move this code elsewhere?
sub new_from_URL {
  my ( $class, $URL ) = @_;
  $URL =~ s/[\(|\)]//g;                                # remove ( and |
  return unless $URL;
  $URL =~ s/\\ /###/g; # Preserve escaped spaces in source label
  my @das_keys = split(/\s/, $URL);                    # break on spaces...
  my %das_data = map { split (/\=/, $_,2) } @das_keys; # split each entry on =
  unless( exists $das_data{url} && exists $das_data{dsn} && (exists $das_data{type} || exists $das_data{coords}) ) {
    warning("DAS source ".$das_data{name}." ($URL) has not been added: Missing parameters");
    next;
  }
  # Don't allow external setting of category, ensure default is kept (external)
  delete $das_data{category};
  # Expand multi-value parameters
  $das_data{on}     = [split /,/, $das_data{on}];
  $das_data{enable} = [split /,/, $das_data{enable}];
  $das_data{coords} = [split /,/, $das_data{coords}];
  # Restore spaces in the label
  if ($das_data{label}) {
    $das_data{label} =~ s/###/ /g ;
  }
  # Re-encode link URL
  if ($das_data{linkurl}) {
    $das_data{linkurl} =~ s/\$3F/\?/g;
    $das_data{linkurl} =~ s/\$3A/\:/g;
    $das_data{linkurl} =~ s/\$23/\#/g;
    $das_data{linkurl} =~ s/\$26/\&/g;
  }
  push @{$das_data{enable}}, $ENV{'ENSEMBL_SCRIPT'};
  # TODO: not sure about these, we're handling coordinate systems differently
  #push @{$das_data{mapping}} , split(/\,/, $das_data{type});
  #$das_data{conftype} = 'external';
  #$das_data{type}     = 'mixed'    if scalar @{$das_data{mapping}} > 1;

  my $self = $class->new_from_hashref(\%das_data);
  return $self;
}

#================================#
#  Web-specific get/set methods  #
#================================#

=head2 on

  Arg [1]    : $views (arrayref)
  Description: get/set for the views the source is available on (arrayref)
  Returntype : arrayref
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut
sub on {
  my $self = shift;
  if ( @_ ) {
    $self->{'on'} = shift;
  }
  return $self->{'on'} && scalar @{$self->{'on'}} ? $self->{'on'} : $self->_guess_views();
}

=head2 is_on

  Arg [1]    : $view (string)
  Description: whether the source is available on the given view
  Returntype : boolean
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut
sub is_on {
  my ( $self, $view ) = @_;
  return grep { $_ eq $view } @{ $self->on || [] }
}

=head2 category

  Arg [1]    : $category (scalar)
  Description: get/set for the data category (menu location) of the source
  Returntype : scalar
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut
sub category {
  my ( $self, $category ) = @_;
  if ( defined $category ) {
    $self->{'category'} = $category;
  }
  return $self->{'category'};
}

=head2 caption

  Arg [1]    : $caption (scalar)
  Description: get/set for the short label (for images) of the source
  Returntype : scalar
  Exceptions : none
  Caller     : general
  Status     : Stable

=cut
sub caption {
  my ( $self, $caption ) = @_;
  if ( defined $caption ) {
    $self->{'caption'} = $caption;
  }
  return $self->{'caption'} || $self->label;
}
sub is_session {
  my $self = shift;
  return ($self->category || '') eq 'session';
}

sub is_user {
  my $self = shift;
  return ($self->category || '') eq 'user';
}

sub is_group {
  my $self = shift;
  return ($self->category || '') eq 'group';
}

sub is_external {
  my $self = shift;
  return $self->is_session || $self->is_user || $self->is_group ? 1 : 0;
}

#================================#
#   Audit/modification methods   #
#================================#

sub is_deleted {
  my $self = shift;
  return $self->{'_deleted'};
}

sub mark_clean {
  my $self = shift;
  $self->{'_altered'} = 0;
  $self->{'_deleted'} = 0;
}

sub mark_deleted {
  my $self = shift;
  $self->{'_deleted'} = 1;
  $self->{'_altered'} = 1;
}

sub mark_altered {
  my $self = shift;
  $self->{'_altered'} = 1;
}

sub is_altered {
  my $self = shift;
  return $self->{'_altered'};
}

sub _guess_views {
  my ( $self ) = @_;
  my $positional    = 0;
  my $nonpositional = 0;
  for my $cs (@{ $self->coord_systems() }) {
    # assume genomic coordinate systems are always positional
    if ( is_genomic($cs) || $cs->name eq 'toplevel' ) {
      $positional = 1;
    }
    # assume gene coordinate systems are always non-positional
    elsif ( $GENE_COORDS{ $cs->name } ) {
      $nonpositional = 1;
    } else {
      $positional = 1;
      $nonpositional = 1;
    }
  }
  my @views = ();

  push @views, qw(
    cytoview
    contigviewtop
    contigviewbottom
    gene_summary
  ) if $positional;

  push @views, qw(
    Gene/ExternalData
    Transcript/ExternalData
  ) if $nonpositional;
  return \@views;
}

1;