package Bio::Graphics::Browser2::DataLoader::bigwig;

# $Id$
use strict;
use base 'Bio::Graphics::Browser2::DataLoader';
use Bio::DB::BigWig;
use Bio::Graphics::Browser2::Util 'shellwords';
use File::Basename 'basename','dirname';

# we will sort out the sections corresponding to each track line
sub load_line { 
    my $self = shift;
    my $line = shift;

    my $prefix = $self->strip_prefix;
    if ($prefix) {
	$line =~ s/^$prefix//;
	$line =~ s/chrom=$prefix/chrom=/;
    }

    if ($line =~ /^track/) {  # starting or ending a section
	$self->finish_track_declaration;
	$self->start_track_declaration($line);
    } elsif ($line =~ /\S/ && $line !~ /^#/ && !$self->{current_track}) {
	warn "no track declaration; faking it";
	$self->start_default_track_declaration();
	$self->write_line($line);
    }
    else {
	$self->write_line($line);
    }
}


sub start_track_declaration {
    my $self = shift;
    my $line = shift;
    my (undef,@pairs) = shellwords($line);
    my %options = map{split '='} @pairs;
    $self->{current_track}{options}     = \%options;
    $self->{current_track}{name}      ||= $self->default_track_name;

    my $dest = File::Spec->catfile($self->data_path,$self->{current_track}{name}).'.wig';
    $self->{current_track}{wigfile}    = $dest;

    open my $fh,'>',$dest or die "Can't open $dest for writing: $!";
    $self->{current_track}{fh}         = $fh;
}

sub start_default_track_declaration {
    my $self = shift;
    $self->start_track_declaration('track type=wiggle_0');
}

sub finish_track_declaration {
    my $self = shift;
    return unless $self->{current_track};
    if ($self->{current_track}{fh}) {
	close  $self->{current_track}{fh} 
	   or die "Error closing wig file $self->{current_track}{wigfile}:$!";
    }
    $self->wig2bigwig($self->{current_track}{wigfile});
    push @{$self->{tracks}},$self->{current_track};
    delete $self->{current_track};
}

sub write_line {
    my $self = shift;
    my $line = shift;
    my $fh   = $self->{current_track}{fh} or return;
    print $fh $line or die "Couldn't write to $self->{current_track}{wigfile}: $!";
}

sub wig2bigwig {
    my $self = shift;
 
    my $src          = $self->{current_track}{wigfile} or return;
    (my $dest=$src)  =~ s/\.wig$/\.bw/;
    my $cs           = $self->chrom_sizes;

    $self->{current_track}{bigwig} = $dest;

    $self->set_status('creating bigwig file');
    # the cause for this complicated code is that we first look for the
    # external wigToBigWig and run that in a fashion that allows us to
    # recover its stderr. Otherwise we fork (safely) and call our internal
    # library routine from Bio::DB::BigFile. This can exit out in an 
    # uncatchable way so we have to capture stderr as well.
    {
	local $SIG{CHLD} = 'DEFAULT';
	my $fh;
	if (my $wigtobigwig = $self->wigtobigwig_path) {
	    open $fh,"$wigtobigwig -clip '$src' '$cs' '$dest' 2>&1 |";
	} else {
	    eval "use IO::Pipe" unless IO::Pipe->can('new');
	    $fh  = IO::Pipe->new;
	    my $child = Bio::Graphics::Browser2::Render->fork();
	    die "Couldn't fork" unless defined $child;
	    if (!$child) { # child process
		$fh->writer();
		my $fileno = fileno($fh);
		open STDERR,">&$fileno";
		open STDOUT,">&$fileno";
		Bio::DB::BigFile->createBigWig($src,$cs,$dest,
					       {clipDontDie=>1}
		    );
		print STDERR "success";
		exit 0;
	    }
	    $fh->reader;
	}
	my @lines = <$fh>;
	close $fh;
	die @lines unless $? == 0 || $lines[-1] =~ /success/; 
    }

    unlink $self->{current_track}{wigfile};
}

sub finish_load {
    my $self = shift;

    $self->finish_track_declaration;
    die "no tracks defined!" unless $self->{tracks};

    my $loadid     = $self->loadid;

    $self->set_status('creating configuration');
    my $conf      = $self->conf_fh;
    my @tracks = @{$self->{tracks}};
    for my $track (@tracks) {
	my $dbid       = $self->new_track_label;
	my $bigwig     = $track->{bigwig} or die "no bigwig for $track->{wigfile}";
	$track->{dbid} = $dbid;
	print $conf <<END;
[$dbid:database]
db_adaptor    = Bio::DB::BigWig
db_args       = -bigwig '$bigwig'

END
    ;
    }
    print $conf "#>>>>>>>>>> cut here <<<<<<<<\n";

    my $trackno = 1;
    for my $track (@tracks) {
	my $dbid = $track->{dbid};
	my $options = $track->{options};
	my $description = $options->{description};
	$options->{name}       ||= sprintf("%s.%03d",$self->track_name,$trackno++);
	$options->{visibility} ||= 'full';
        $options->{maxHeightPixels} ||= $options->{visibility} eq 'full' ? 50 : 20;
	$options->{autoScale}  ||= 'on';
	my @options;
	push @options,"database = $dbid";
	push @options,"feature  = summary";
	push @options,"key      = $options->{name}";
	push @options,'glyph = '. ($options->{visibility} eq 'pack' 
	                               ? 'wiggle_density' 
				       : 'wiggle_whiskers' );
	push @options,'autoscale = '.($options->{autoScale}  eq 'on'   
	                               ? 'local' 
				       : 'global');
	if (exists $options->{viewLimits} && 
	    (my ($low,$hi) = split ':',$options->{viewLimits})) {
	    push @options,"min_score = $low";
	    push @options,"max_score = $hi";
	}

	if (exists $options->{maxHeightPixels} &&
	    (my ($max,$default,$min) = split ':',$options->{maxHeightPixels})) {
	    $default ||= $max;
	    push @options,"height  = $default";
	}
        push @options,"description = $description";
	my $config_lines = join "\n",@options;
        print $conf <<END;
[$dbid]
$config_lines

END
    }
    
}

sub wigtobigwig_path {
    my $self = shift;
    return $self->{_wig2bigwig_path} if exists $self->{_wig2bigwig_path};
    return $self->{_wig2bigwig_path} ||= $self->search_for_binary('wigToBigWig');
}

sub search_for_binary {
    my $self   = shift;
    my $target = shift;
    my @path   = split ':',$ENV{PATH};
    for my $d (@path) {
	my $tgt = File::Spec->catfile($d,$target);
	return  $tgt if -e $tgt && -x _;
    }
    return;
}

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);
    $self->{default_track_name} = 'track000';
    $self;
}

sub default_track_name {
    my $self = shift;
    return $self->{default_track_name}++;
}

1;
