#!/usr/bin/perl

package PoCoBewarez;

use strict;
use warnings;
use include;

use POE ("Component::Client::UserAgent");
use HTTP::Request;
use File::Basename;
use Time::HiRes ("time");

# Create a new download session.
sub new {
  my ($class, %opts) = @_;
  $opts{Alias} ||= "leecher";
  $opts{UA_Alias} ||= "leech";
  $opts{Progress} ||= sub {};

  # Set up the session to talk HTTP.
  POE::Component::Client::UserAgent->new(alias => $opts{UA_Alias});

  my $dl = bless {
    Alias    => $opts{Alias},
    GetURL   => $opts{URL} || croak("Give me a url to download!"),
    Leech    => $opts{UA_Alias},
    Progress => $opts{Progress}
  }, $class;

  # Set up our session to wrap PoCo::UA and handle downloading a single file.
  POE::Session->create(
    inline_states => {
      _start => sub {
        $_[KERNEL]->alias_set($opts{Alias});
      },
    },
    object_states => [
      $dl => {
        resume   => "resume",
        pause    => "pause",
        cancel   => "cancel",
        response => "response",
        shutdown => "stop"
      }
    ]
  );
  
  return $dl;
}

# If we're doing something, we have more than {GetURL} and the {Leech} alias.
sub active {
  my $dl = shift;
  return keys %$dl != 4;
}

# Start a download.
sub resume {
  my $dl = $_[OBJECT];
  return if $dl->active;
  ($dl->{URL}, $dl->{Name}) = scrape($dl->{GetURL});

  $dl->{SoFar} = -s $dl->{Name} // 0;
  open $dl->{File}, ">>$dl->{Name}" or croak "Can't append to $dl->{Name}: $!";
  $dl->{LastTime} = time;
  $dl->{LastSize} = $dl->{SoFar};
  $dl->{Speed} = 0;
  $dl->{ETA} = "Never";

  $_[KERNEL]->post($dl->{Leech} => "request",
    request   => HTTP::Request->new("GET", $dl->{URL}, ["Range" => "bytes=$dl->{SoFar}-"]),
    response  => $_[SESSION]->postback("response"),
    callback  => sub {
      $dl->progress(@_);
    }
    #chunksize => 1024
  );
}

# Stop (temporarily) a download.
sub pause {
  my ($dl, $stop) = ($_[OBJECT], $_[ARG0]);
  return unless $dl->active;

  # For whatever reason, all hell breaks loose if we send a cancel event to PoCo::UA when
  # we're trying to shutdown.
  $_[KERNEL]->post($dl->{Leech} => "cancel") unless $stop;

  close $dl->{File};
  delete @{$dl}{qw(URL Name SoFar File Request LastTime LastSize Speed ETA)}
}

# Stop a download permanently and quit.
sub cancel {
  my $dl = $_[OBJECT];

  $_[KERNEL]->call($dl->{Alias} => "pause");
  unlink($dl->{Name}) if $_[ARG1];
  $_[KERNEL]->yield("shutdown");
}

# Handle a stream of data. For whatever reason, we can't be a normal POE handler.
sub progress {
  my ($dl, $data, $response) = @_;

  debug("got a chunk, not not alive"), return unless $dl->active;

  # Meaningful data from the response object.
  my $http_code = $response->code;
  my $http_req = $response->request;
  my $http_total = $response->header("content-length");

  # The below block executes when we first start a download.
  unless ($dl->{Total}) {
    # Errors and stuff are handled by Poco::Client::UserAgent, so all we have to deal with
    # is good ol' data. :P
    debug "scary http code $http_code" unless $http_code == 206 or $http_code == 200;
    if ($http_total) {
      $dl->{Total} = $dl->{SoFar} + $http_total;
    } else {
      debug "no content-length header. stupid server. bugginess expected.";
      $dl->{Total} = -1;
    }
  }

  debug "sofar=total but got data" if $dl->{SoFar} == $dl->{Total} and $dl->{Total};
  debug "empty chunk received" unless $data;

  my $fh = $dl->{File};
  print $fh $data;
  $dl->{SoFar} += length($data);

  # Calculate speed
  my ($time, $size) = (time, $dl->{SoFar});
  if ($time - $dl->{LastTime} > 1) {
    $dl->{Speed} = ($size - $dl->{LastSize}) / ($time - $dl->{LastTime}) / 1024;
    ($dl->{LastTime}, $dl->{LastSize}) = ($time, $size);

    # Recalculate ETA
    my $secs = ($dl->{Total} - $dl->{SoFar}) / 1024 / $dl->{Speed};
    $dl->{ETA} = sprintf("%02d:%02d:%02d", int($secs / 3600), ($secs % 3600) / 60, ($secs %3600) % 60);
  }

  # Perform a callback
  $dl->{Progress}->($dl);
}

# Handle the end of a stream.
sub response {
  # We get this when a download is finished.
  my $response = $_[ARG1][1];
  my $http_code = $response->code;
  if ($response->is_success or $http_code == 416) {
    # 416 means we've already got the whole file.
    $_[KERNEL]->yield("shutdown");
  } elsif ($response->is_error) {
    debug "SCREAM! error $http_code"
  } elsif ($response->is_redirect) {
    debug "redirecting...";
  } else {
    debug "Well wtf IS the response, then?!";
  }
}

# Gracefully end our session.
sub stop {
  my $dl = $_[OBJECT];
  $_[KERNEL]->call($dl->{Alias} => "pause", "stop");
  $_[KERNEL]->call($dl->{Leech} => "shutdown");
}

# Give a filename and URL from a random URL.
sub scrape {
  my $get = shift;
  if ($get =~ m/youtube\.com/) {
    return clive_scrape($get);
  } elsif ($get =~ m/rapidshare/) {
  } else {
    return ($get, basename($get));
    my $name = basename($get);
    # Ew, nasty URL encoding. Spaces aren't that much better, but eh...
    $name =~ s/%20/ /g;
    return ($get, $name);
  }
}

# Scrape youtube URL.
sub clive_scrape {
  my $raw = shift;
  chomp(my $out = (`clive --emit $raw`)[-1]);
  my (undef, $url, $fname, undef) = split(",", $out);
  $url =~ s/"//g;
  $fname =~ s/"//g;
  return ($url, $fname);
}

42;
