package EnsEMBL::Web::Session;

use strict;
use Storable qw(nfreeze thaw);
use Bio::EnsEMBL::ColourMap;
use Apache2::RequestUtil;
use Data::Dumper qw(Dumper);
use Time::HiRes qw(time);
use Class::Std;

use EnsEMBL::Web::Data::User;
use EnsEMBL::Web::Tools::Encryption 'checksum';
use EnsEMBL::Web::Cookie;
use EnsEMBL::Web::ExtURL;
use EnsEMBL::Web::ViewConfig;
use EnsEMBL::Web::ImageConfig;
use EnsEMBL::Web::DASConfig;
use EnsEMBL::Web::Data::Session;
use Bio::EnsEMBL::ExternalData::DAS::SourceParser;

use EnsEMBL::Web::Root;
our @ISA = qw(EnsEMBL::Web::Root);
our %DAS_IMAGE_DEFAULTS = ( 'display' => 'off' );

{
## Standard initialized attributes...
  my %Cookie_of        :ATTR( :name<cookie>       );
  my %Adaptor_of       :ATTR( :name<adaptor>      );
  my %Input_of         :ATTR( :get<input>    :set<input>    );
  my %Request_of       :ATTR( :get<request>  :set<request>  );
  my %SpeciesDef_of    :ATTR( :name<species_defs> );
  my %Session_id_of    :ATTR( :name<session_id>   );
## Modified parameters built in BUILD fnuction...
  my %Configs_of       :ATTR;
  my %Das_sources_of   :ATTR( :get<das_sources>  );
  my %Das_parser_of    :ATTR;
  my %ImageConfigs_of  :ATTR;
  my %Path_of          :ATTR( :get<path> );
## Lazy loaded objects....
  my %ExtURL_of        :ATTR( :get<exturl> :set<exturl>  );
  my %Species_of       :ATTR( :name<species>      );
  my %ColourMap_of     :ATTR;

## Common data (tmp upload, upload, url, etc) ...
  my %Data_of       :ATTR( :get<tmp> :set<tmp>  );
### New Session object - passed around inside the data object to handle storage of
### ViewConfigs/ImageConfigs in the web_user_db
###
### How it is used...
###
### The session object is "lazily" attached to {{EnsEMBL::Web::Proxiable}} objects and is
### accessed via {{EnsEMBL::Web::Proxiable::get_session}} method. You usually won't need
### to do this explicitly - because it is done implicitly by methods such as:
###
### {{EnsEMBL::Web::Proxiable::get_imageconfig}},
### {{EnsEMBL::Web::Proxiable::image_config_hash}},
### {{EnsEMBL::Web::Proxiable::get_viewconfig}},
### {{EnsEMBL::Web::Proxiable::attach_image_config}} all of which create either
### {{EnsEMBL::Web::ViewConfig}} or {{EnsEMBL::Web::ImageConfig}} objects.
###
### These commands in turn access the database if we already have a session (whose is
### accessible by {{session_id}}) and if the appropriate viewconfig is defined as
### storable. (In this way it replaces the ViewConfigAdaptor/ImageConfigAdaptor modules
###
### At the end of the configuration section of the webpage if any data needs to be
### saved to the session this is done so (and if required a session cookie set and
### stored in the users browser. (See {{EnsEMBL::Web::Document::WebPage}} to see
### where this is done (by the {{EnsEMBL::Web::Proxiable::fix_session}} method.
###

sub get_site { return ''; }

sub BUILD {
  my( $class, $ident,  $arg_ref ) = @_;
### Most of the build functions is done automagically by Class::Std, two unusual ones
### are the path and Cookie object..
  $Configs_of      { $ident } = {}; # Initialize empty hash!
  $ImageConfigs_of { $ident } = {}; # Initialize emtpy hash!
  $Data_of         { $ident } = {}; # Initialize empty hash!
  $Path_of         { $ident } = ['EnsEMBL::Web', reverse @{$arg_ref->{'path'}||[]}];
}

sub input {
### Wrapper accessor to keep code simple...
  my $self = shift;
  return $self->get_input(@_);
}

sub exturl {
  my $self = shift;
  return $ExtURL_of{ident $self} ||= EnsEMBL::Web::ExtURL->new( $self->get_species, $self->get_species_defs );
}

sub colourmap {
### Gets the colour map
  my $self = shift;
  return $ColourMap_of{ident $self} ||= Bio::EnsEMBL::ColourMap->new( $self->get_species_defs );
}

sub create_session_id {
### Gets session ID if the session ID doesn't exist
### a new one is grabbed and added to the users cookies
  my ($self, $r) = @_;
  $r = (!$r && Apache2::RequestUtil->can('request')) ? Apache2::RequestUtil->request() : undef;
  my $session_id = $self->get_session_id;
  return $session_id if $session_id;
  $session_id = EnsEMBL::Web::Data::Session->create_session_id;
  $self->set_session_id( $session_id );
  $self->get_cookie->create( $r, $session_id ) if $r;  
  return $session_id;
}

sub _temp_store {
  my( $self, $name, $code ) = @_;
### At any point can copy back value from image_config into the temporary storage space for the config!!
#warn "$name .... ", $Configs_of{ ident $self }{$name}," ... ",
#   $Configs_of{ ident $self }{$name}{'image_config_data'}{$code}," ... ",
#   $ImageConfigs_of{ident $self}{$code}{'user'};
  $Configs_of{ ident $self }{$name}{'image_config_data'}{$code} =
  $Configs_of{ ident $self }{$name}{'user'}{'image_configs'}{$code} =
    $self->deepcopy( $ImageConfigs_of{ident $self}{$code}{'user'} );
# warn Dumper( $self->{'configs'}{$name}{'user'}{'image_configs'}{$code} );
}

sub reset_config {
### Reset the config given by $config name in the storage hash
  my( $self, $configname ) = @_;
  return unless exists $Configs_of{ ident $self }{ $configname };
  $Configs_of{ ident $self }{ $configname }{ 'config' }->reset();
}

sub store {
### Write session back to the database if required...
### Only work with storable configs and only if they or attached
### image configs have been altered!
### 
### Comment: not really, we also have das and tmp data which needs
### to be stored as well
  my ($self, $r) = @_;
  my @storables = @{ $self->storable_data($r) };
  foreach my $storable (@storables) {
    EnsEMBL::Web::Data::Session->set_config(
      session_id => $self->create_session_id($r),
      type       => 'script',
      code       => $storable->{config_key},
      data       => $storable->{data},
    ) if $storable->{config_key};
  }
  $self->save_das;
}

sub storable_data {
  ### Returns an array ref of hashes suitable for dumping to a database record. 
  my($self,$r) = @_;
  my $return_data = [];
  foreach my $config_key ( keys %{$Configs_of{ ident $self }||{}} ) {
    my $sc_hash_ref = $Configs_of{ ident $self }{$config_key}||{};
    ## Cannot store unless told to do so by script config
    next unless $sc_hash_ref->{'config'}->storable;
    ## Start by setting the to store flag to 1 if the script config has been updated!
    my $to_store = $sc_hash_ref->{'config'}->altered;
    my $data = {
      'diffs'         => $sc_hash_ref->{'config'}->get_user_settings(),
      'image_configs' => {}
    };

    ## get the script config diffs
    foreach my $image_config_key ( keys %{$sc_hash_ref->{'config'}->{'_image_config_names'}||{} }) {
      my $image_config = $ImageConfigs_of{ ident $self }{$image_config_key};
      $image_config = $self->getImageConfig($image_config_key,$image_config_key) unless $image_config;
      next          unless $image_config->storable; ## Cannot store unless told to do so by image config
      $to_store = 1 if     $image_config->altered;  ## Only store if image config has changed...
      $data->{'image_configs'}{$image_config_key}  = $image_config->get_user_settings();
    }
    push @{ $return_data }, { config_key => $config_key, data => $data } if $to_store;
  }
  return $return_data; 
}
###################################################################################################
##
## Tmp data stuff
##
###################################################################################################

sub get_cached_data {
### Retrieve the data from cache
  my $self = shift;
  my %args = (
    type => 'tmp',
    @_,
  );

  if ($args{code}) {
    ## Code is spcified
    return $Data_of{ ident $self }{$args{type}}{$args{code}}
      if $Data_of{ ident $self }{$args{type}}{$args{code}};
  } elsif ($Data_of{ ident $self }{$args{type}}) {
    ## Code is not spcified // wantarray or not?
    my ($code) = keys %{ $Data_of{ ident $self }{$args{type}} };
    return wantarray ? values %{ $Data_of{ ident $self }{$args{type}} }
                     : $Data_of{ ident $self }{$args{type}}{$code};
  }

}

sub get_data {
### Retrieve the data
  my $self = shift;
  my %args = (
    type => 'upload',
    @_,
  );

  EnsEMBL::Web::Data::Session->propagate_cache_tags(
    session_id => $self->get_session_id,
    type       => $args{type},
    code       => $args{code},
  );

  ## No session so cannot have anything configured!
  return unless $self->get_session_id;

  ## Have a look in the cache
  return $self->get_cached_data(%args)
      if $self->get_cached_data(%args);

  $Data_of{ ident $self }{$args{type}} ||= {};

  ## Get all data of the given type from the database!
  my @entries = EnsEMBL::Web::Data::Session->get_config(
    session_id => $self->get_session_id,
    %args,
  );
  $Data_of{ ident $self }{$args{type}}{$_->code} = $_->data for @entries;

###  use Data::Dumper; warn Dumper($Data_of{ ident $self });

  ## Make empty {} if none found
  #$Data_of{ ident $self }{$args{type}}{$args{code}} ||= {} if $args{code};

  return $self->get_cached_data(%args);
}

sub set_data {
  my $self = shift; 
  my %args = (
    type => 'upload',
    @_,
  );

  return unless $args{type} && $args{code};

  my $data = $self->get_data(
    type => $args{type},
    code => $args{code},
  );

  $Data_of{ ident $self }{$args{type}}{$args{code}} = {
    %{ $data || {} },
    type => $args{type},
    code => $args{code},
    %args,
  };
  $self->save_data(
    type => $args{type},
    code => $args{code},
  );
}

sub purge_data {
### $object->get_session->purge_data()
  my $self = shift; 
  my %args = (
    type => 'upload',
    @_,
  );
  if ($args{code}) {
    delete $Data_of{ ident $self }{$args{type}}{$args{code}};
  } else {
    $Data_of{ ident $self }{$args{type}} = {};
  }
  $self->save_data(%args);
}

## For multiple objects, such as upload or urls
sub add_data {
### $object->get_session->add_data()
  my $self = shift; 
  my %args = (
    type => 'upload',
    @_,
  );
  ## Unique code
  $args{code} ||= time;
  $self->set_data(%args);

  return $self->get_data(type => $args{type}, code => $args{code});
}

sub save_data {
### Save all data back to the database
  my $self = shift;
  my %args = (
    type => 'upload',
    @_,
  );
  $self->create_session_id;
  EnsEMBL::Web::Data::Session->reset_config(
    %args,
    session_id => $self->get_session_id,
  );

  foreach my $data ($self->get_data(%args)) {
    next unless $data && %$data;
    EnsEMBL::Web::Data::Session->set_config(
      session_id => $self->get_session_id,
      type       => $args{type},
      code       => $data->{code},
      data       => $data,
    );    
  }

}

###################################################################################################
##
## Share upload data
##
###################################################################################################

sub receive_shared_data {
  # Share
  my ($self, @share_refs) = @_; 
  my (@success, @failure);
  foreach my $share_ref (@share_refs) {
    my $record;
    if ($share_ref =~ /^(\d+)-(\w+)$/) {
      # User record:
      my $id = $1;
      my $checksum = $2;
      die 'Sharing violation' unless checksum($id) ne $checksum;
      $record = EnsEMBL::Web::Data::Record::Upload::User->new($id);
    } else {
      # Session record:
      $record = EnsEMBL::Web::Data::Session->retrieve(code => $share_ref);
    }
    if ($record) {
      $self->add_data(%{$record->data});
      push @success, $record->data->{'name'};
    } else {
      push @failure, $share_ref;
    }
  }
  if (@failure) {
    my $n = scalar @failure;
    my $msg = "The data has been removed from $n of the shared sets that you are looking for.";
    $self->add_data(
      type => 'message', 
      code => 'no_data:' . (join ',', sort @failure), 
      message => $msg, 
      function => '_warning'
    );
  }
  if (@success) {
    my $tracks = join '</li><li>', @success;
    $tracks = "<ul><li>$tracks</li></ul>";
    $self->add_data(
      type => 'message', 
      code => 'shared_data:' . (join ',', map $_[0], @share_refs),
      message => "The following added tracks displayed in the image below are shared data:$tracks", 
      function => '_info'
    );
  }
}

###################################################################################################

# This method gets all configured DAS sources for the current session, i.e. all
# those either added or modified externally.
# Returns a hashref, indexed by logic_name.
sub get_all_das {
  my $self    = shift;
  my $species = shift || $ENV{'ENSEMBL_SPECIES'};
  if ( $species eq 'common' ) {
    $species = '';
  }
  ## TODO: get rid of session getters,
  EnsEMBL::Web::Data::Session->propagate_cache_tags(
    session_id => $self->get_session_id,
    type       => 'das',
  );  

  # If there is no session, there are no configs
  return ({},{}) unless $self->get_session_id;
  # If the cache hasn't been initialised, do it
  if ( ! $Das_sources_of{ ident $self } ) {
    $Das_sources_of{ ident $self } = {};
    # Retrieve all DAS configurations from the database
    my @configs = EnsEMBL::Web::Data::Session->get_config(
      session_id => $self->get_session_id,
      type       => 'das'
    );
    foreach my $config (@configs) {
      $config->data || next;
      # Create new DAS source from value in database...
      my $das = EnsEMBL::Web::DASConfig->new_from_hashref( $config->data );
      $das->category( 'session' ); # paranoia...
      $Das_sources_of{ ident $self }{ $das->logic_name } = $das;
    }
  }
  my %by_name = ();
  my %by_url  = ();
  for my $das ( values %{ $Das_sources_of{ ident $self } } ) {
    unless ($species eq 'ANY') {
      $das->matches_species( $species ) || next;
    }
    $by_name{$das->logic_name} = $das;
    $by_url {$das->full_url  } = $das;
  }
  return wantarray ? ( \%by_name, \%by_url ) : \%by_name;
}

# Save all session-specific DAS sources back to the database
# Usage examples:
#   $session->add_das( $source1 );
#   $source2->mark_deleted;       # delete entirely
#   $source3->category( 'user' ); # move from session to user
#   $source3->mark_altered;       # mark as updated
#   $session->save_das;           # save session data
sub save_das {
  my $self = shift;
  foreach my $source ( values %{ $self->get_all_das('ANY') } ) {
    # If the source hasn't changed in some way, skip it
    next unless $source->is_altered;
    # Delete moved or deleted records
    if( $source->is_deleted || !$source->is_session ) {
      EnsEMBL::Web::Data::Session->reset_config(
        session_id => $self->create_session_id,
        type       => 'das',
        code       => $source->logic_name,
      );
    }
    # Create new source records
    else {
      EnsEMBL::Web::Data::Session->set_config(
        session_id => $self->create_session_id,
        type       => 'das',
        code       => $source->logic_name,
        data       => $source,
      );
    }
  }
}

# This function will make sure that a das source is attached with a unique name
# So in case when you try to attach MySource it will return undef if exactly same
# source is already attached (i.e the same url, dsn and coords).
# If it's only the name that is the same then the function will provide a unique
# name for the new source , e.g name_1
sub _get_unique_source_name {
  my( $self, $source ) = @_;
  my @sources = $EnsEMBL::Web::RegObj::ENSEMBL_WEB_REGISTRY->get_all_das;
  for ( my $i = 0; 1; $i++ ) {
    my $test_name = $i ? $source->logic_name . "_$i" : $source->logic_name;
    my $test_url  = $i ? $source->full_url   . "_$i" : $source->full_url;
    my $test_source = $sources[0]->{$test_name} || $sources[1]->{$test_url};
    if ( $test_source ) {
      if ( $source->equals( $test_source ) ) {
        return;
      }
      next;
    }
    return $test_name;
  }
}

# Add a new DAS source within the session
sub add_das {
  my ( $self, $das ) = @_;
  # If source is different to any thing added so far, add it
warn "ADD $das...";
  if( my $new_name = $self->_get_unique_source_name($das) ) {
warn ">> $new_name <<";
    $das->logic_name( $new_name );
    $das->category  ( 'session' );
    $das->mark_altered;
    $Das_sources_of{ ident $self }{ $new_name } = $das;
    return  1;
  }
  # Otherwise skip it
  return 0;
}

# Switch on a DAS source for the current view/image (if it is suitable)
sub configure_das_views {
  my ($self, $das, $referer_hash, $track_options) = @_;
  my $this_type   = $referer_hash->{'ENSEMBL_TYPE'  } || $ENV{'ENSEMBL_TYPE'};
  my $this_action = $referer_hash->{'ENSEMBL_ACTION'} || $ENV{'ENSEMBL_ACTION'};
  my $this_image  = $referer_hash->{'ENSEMBL_IMAGE'};
  $track_options->{'display'} ||= 'labels';
  my $this_vc     = $self->getViewConfig( $this_type, $this_action );
  my %this_ics    = $this_vc->image_configs();
  # This method has to deal with two types of configurations - those of views
  # and those of images. Non-positional DAS sources are attached to views, and
  # positional sources are attached to images. The source automatically becomes
  # available on all the views/images it is -suitable for-, and this method
  # switches it on for the current view/image provided it is suitable.
  # The DASConfig "is_on" method gives a way to test whether a source is
  # suitable for a view (e.g. Gene/ExternalData) or image (e.g contigview).
  # Find images on the current view that support DAS and for which the DAS
  # source is suitable, optionally filtered with
  # an override. But don't trust the override to always indentify an image that
  # supports DAS!
  my @this_images = grep {
    $this_ics{$_} eq 'das' && # DAS-compatible image
    (!$this_image || $this_image eq $_) && # optional override
    $das->is_on( $_ ) # DAS source is suitable for this image
  } keys %this_ics;
  # If source is suitable for this VIEW (i.e. not image) - Gene/Protein DAS
  if ( $das->is_on( "$this_type/$this_action" ) ) {
    # Need to set default to 'no' before we can set it to 'yes'
    if ( !$this_vc->is_option( $das->logic_name ) ) {
      $this_vc->_set_defaults( $das->logic_name, 'no'  );
    }
    $this_vc->set( $das->logic_name, 'yes' );
  }
  # For all IMAGES the source needs to be turned on for
  for my $image ( @this_images ) {
    my $ic = $self->getImageConfig( $image, $image );
    my $n = $ic->get_node( 'das_' . $das->logic_name );
    if( !$n ) {
      my %tmp = ( map( { ($_=>'') } keys %$track_options ), %DAS_IMAGE_DEFAULTS );
      $n = $ic->tree->create_node( 'das_' . $das->logic_name, \%tmp );
    }
    $n->set_user( %{ $track_options } );
    $ic->altered = 1;
  }
  return;
}

sub get_das_parser {
  my $self = shift;
  $Das_parser_of{ ident $self } ||= Bio::EnsEMBL::ExternalData::DAS::SourceParser->new(
    -timeout  => $self->get_species_defs->ENSEMBL_DAS_TIMEOUT,
    -proxy    => $self->get_species_defs->ENSEMBL_WWW_PROXY,
    -noproxy  => $self->get_species_defs->ENSEMBL_NO_PROXY,
  );
  return $Das_parser_of{ ident $self };
}

sub add_das_from_string {
  my ( $self, $string, $view_details, $track_options ) = @_;

  my @existing = $EnsEMBL::Web::RegObj::ENSEMBL_WEB_REGISTRY->get_all_das();
  my $parser = $self->get_das_parser();
  my ($server, $identifier) = $parser->parse_das_string( $string );
  # If we couldn't reliably parse an identifier (i.e. string is not a URL),
  # assume it is a registry ID
#warn "... $string - $server - $identifier";
  if ( !$identifier ) {
    $identifier = $string;
    $server     = $self->get_species_defs->DAS_REGISTRY_URL;
  }

  # Check if the source has already been added, otherwise add it
  my $source = $existing[0]->{$identifier} || $existing[1]->{"$server/$identifier"};

  unless ($source) {
    # If not, parse the DAS server to get a list of sources...
    eval {
      for ( @{ $parser->fetch_Sources( -location => $server ) } ) {
        # ... and look for one with a matcing URI or DSN
        if ( $_->logic_name eq $identifier || $_->dsn eq $identifier ) {
          if (!@{ $_->coord_systems }) {
            return "Unable to add DAS source $identifier as it does not provide any details of its coordinate systems";
          }
          $source = EnsEMBL::Web::DASConfig->new_from_hashref( $_ );
          $self->add_das( $source );
          last;
        }
      }
    };
    if ($@) {
      return "DAS error: $@";
    }
  }

  if( $source ) {
    # so long as the source is 'suitable' for this view, turn it on
    $self->configure_das_views( $source, $view_details, $track_options );
  } else {
    return "Unable to find a DAS source named $identifier on $server";
  }

  return;
}

sub deepcopy {
### Recursive deep copy of hashrefs/arrayrefs...
  my $self = shift;
  if (ref $_[0] eq 'HASH') {
    return { map( {$self->deepcopy($_)} %{$_[0]}) };
  } elsif (ref $_[0] eq 'ARRAY') {
    return [ map( {$self->deepcopy($_)} @{$_[0]}) ];
  }
  return $_[0];
}

sub attachImageConfig {
  my $self   = shift;
  my $script = shift;
  return unless $Configs_of{ ident $self }{$script};
  foreach my $image_config (@_) {
    $Configs_of{ ident $self }{$script}{'image_configs'}{$image_config}=1;
  }
  return;
}

sub getViewConfig {
### Create a new {{EnsEMBL::Web::ViewConfig}} object for the script passed
### Loops through core and all plugins looking for a EnsEMBL::*::ViewConfig::$script
### package and if it exists calls the function init() on the package to set
### (a) the default values, (b) whether or not the user can over-ride these settings
### loaded in the order: core first, followed each of the plugin directories
### (from bottom to top in the list in conf/Plugins.pm)
###
### If a session exists and the code is storable connect to the database and retrieve
### the data from the session_data table
###
### Then loop through the {{EnsEMBL::Web::Input}} object and set anything in this
### Keep a record of what the user has changed!!
  my $self   = shift;
  my $type   = shift;
  my $action = shift;
  my $do_not_pop_from_params = shift;

  my $key = $type.'::'.$action;

  ## TODO: get rid of session getters,
  EnsEMBL::Web::Data::Session->propagate_cache_tags(
    session_id => $self->get_session_id,
    type       => $type,
    code       => $key,
  );  

  unless ($Configs_of{ ident $self }{$key} ) {
    my $flag = 0;
    my $view_config = EnsEMBL::Web::ViewConfig->new( $type, $action, $self );
    foreach my $root ( @{$self->get_path} ) {
      my $classname = $root."::ViewConfig::$key";
      unless( $self->dynamic_use( $classname ) ) {
        ## If the module can't be required throw an error and return undef;
        (my $message = "Can't locate $classname\.pm in" ) =~ s/::/\//g;
        my $error = $self->dynamic_use_failure($classname);
        warn qq(ViewConfig: failed to require $classname:\n  $error) unless $error=~/$message/;
        next;
      }
      $view_config->push_class( $classname );
      foreach my $part (qw(init)) {
        my $method_name = $classname."::".$part;
        eval { no strict 'refs'; &$method_name( $view_config ); };
        if( $@ ) {
          my $message = "Undefined subroutine &$method_name called";
          if( $@ =~ /$message/ ) {
            warn qq(ViewConfig: init not defined in $classname\n);
          } else {
            warn qq(ViewConfig: init call on $classname failed:\n$@);
          }
        } else {
          $view_config->real = 1;
        }
      }
    }
    my $image_config_data = {};
    if( $self->get_session_id && $view_config->storable ) {
      ## Let us see if there is an entry in the database and load it into the script config!
      ## and store any other data which comes back....
      my $config = EnsEMBL::Web::Data::Session->get_config(
        session_id => $self->get_session_id,
        type       => 'script',
        code       => $key,
      );
      if( $config && $config->data ) {
        $view_config->set_user_settings( $config->data->{'diffs'} );
        $image_config_data = $config->data->{'image_configs'};
      }
    }
#   unless( $do_not_pop_from_params ) {
#     warn "CALLED... update_from_input...";
#      $view_config->update_from_input( $self->input ); ## Needs access to the CGI.pm object...
#   }

    $Configs_of{ ident $self }{$key} = {
      'config'            => $view_config,         ## List of attached
      'image_configs'     => {},                   ## List of attached image configs
      'image_config_data' => $image_config_data    ## Data retrieved from database to define image config settings.
    };
  }
  return $Configs_of{ ident  $self }{$key}{'config'};
}

sub get_view_config_as_string {
  my ($self, $type, $action ) = @_;

  if( $self->get_session_id ) {
    my $config = EnsEMBL::Web::Data::Session->get_config(
      session_id => $self->get_session_id,
      type       => 'view',
      code       => $type.'::'.$action,
    );
    return $config->as_string if $config;
  }
  return undef; 
}

sub set_view_config_from_string {
  my ($self, $type, $action, $string) = @_;
  EnsEMBL::Web::Data::Session->set_config(
    session_id => $self->get_session_id,
    type       => 'view',
    code       => $type.'::'.$action,
    data       => $string,
  );
}

sub getImageConfig {
### Returns an image Config object...
### If passed one parameter then it loads the data (and doesn't cache it)
### If passed two parameters it loads the data (and caches it against the second name - NOTE you must use the
### second name version IF you want the configuration to be saved by the session - otherwise it will be lost
  my( $self, $type, $key ) = @_;

  ## TODO: get rid of session getters,
  EnsEMBL::Web::Data::Session->propagate_cache_tags(
    session_id => $self->get_session_id,
    type       => $type,
    code       => $key,
  );  
## If key is not set we aren't worried about caching it!
  if( $key && exists $ImageConfigs_of{ ident $self }{$key} ) {
    return $ImageConfigs_of{ ident $self }{$key};
  }
  my $image_config = $self->get_ImageConfig( $type ); # $ImageConfigs_of{ ident $self }{ $type };
  foreach my $script ( keys %{$Configs_of{ ident $self }||{}} ) {
    if( $Configs_of{ ident $self }{$script}{'image_config_data'}{$type} ) {
      my $T = $Configs_of{ ident $self }{$script}{'image_config_data'}{$type}||{};
      foreach (keys %$T) {
        $image_config->tree->{_user_data}{$_} = $self->deepcopy( $T->{$_} );
      }
    }
  }
## Store if $key is set!
  $ImageConfigs_of{ ident $self }{ $key } = $image_config if $key;
  return $image_config;
}

sub get_ImageConfig {
### Return a new image config object...
  my $self = shift;
  my $type = shift;
  return undef if $type eq '_page';
  my $classname = '';
## Let us hack this for the moment....
## If a site is defined in the configuration look for
## an the user config object in the namespace EnsEMBL::Web::ImageConfig::{$site}::{$type}
## Otherwise fall back on the module EnsEMBL::Web::ImageConfig::{$type}

  if( $self->get_site ) {
    $classname = "EnsEMBL::Web::ImageConfig::".$self->get_site."::$type";
    eval "require $classname";
  }
  if($@ || !$self->get_site ) {
    my $classname_old = $classname;
    $classname = "EnsEMBL::Web::ImageConfig::$type";
    eval "require $classname";
## If the module can't be required throw and error and return undef;
    if($@) {
      warn(qq(ImageConfigAdaptor failed to require $classname_old OR $classname: $@\n));
      return undef;
    }
  }
## Import the module
  $classname->import();
  $self->colourmap;
  my $image_config = eval { $classname->new( $self, @_ ); };
  if( $@ || !$image_config ) { warn(qq(ImageConfigAdaptor failed to create new $classname: $@\n)); }
## Return the respectiv config.
  return $image_config;
}

}
1;