#!/usr/bin/perl

use strict;


use IO::Socket::INET;
use IO::Select;


my($stdin,$select,$CLEAR,$VERSION,$SEARCHING,$PROMPTING,$STATTING,$RemoteHost,$RemotePort,$DEBUG,$stdin_fd,$rh,$stdin_data,$socket_data, %CUR_DOWNLOADS,%seen_items,%hashes,$host,$port,$path,$name,$length,$search_results, %downloads,%DOWNLOADING,$href,$search_opts,@STATS,%socket,$fds,$socket_fd);

$select = new IO::Select();

$CLEAR = `clear` or $CLEAR = `cls`;
print_stdout($CLEAR);

$VERSION = "1.0.8";

$SEARCHING = undef;
$PROMPTING = undef;
$STATTING = undef;

$RemoteHost = "127.0.0.1";
$RemotePort = "1213";

$DEBUG = 1 and shift(@ARGV) if $ARGV[0] =~ /^-d(ebug)?$/i;

$stdin = new IO::Handle;
$stdin->fdopen(fileno(STDIN),"r");
$stdin_fd = $stdin->fileno;
print_stdout("** select (Added filehandle: $stdin_fd)\n") if $DEBUG;
$stdin->autoflush(1);
$select->add($stdin);

&show_intro;

&usage and exit(1) if $ARGV[0] =~ /^-h(elp)?$/i;
&start_daemon_stats if $ARGV[0] =~ /^-s(tats)?$/i;
&start_share and exit(0) if $ARGV[0] =~ /^-b(uildshares)?$/i;
exit(1) if $ARGV[0] =~ /^-v(ersion)?$/i;

&start_prompt;

while (1) {
  my @rh_set = $select->can_read();

  # take all readable handles in turn
  foreach $rh (@rh_set) {

     # if it is the input handle
     if ($rh == $stdin) {
        if ($stdin_data = <$stdin>) {
           # Do something with $stdin_data
           &parse_stdin($stdin_data);
        }
        else {
           $select->remove($stdin);
           close($stdin);
           print_stdout("** select (Removed filehandle: $stdin)\n") if $DEBUG;
           &parse_stdin("$stdin_data");
        }    
     }

     # if it is a socket handle
     else {
        if ($socket_data = <$rh>) {
           # Do something with $socket_data
           parse_socket($rh,$socket_data);
        }
        else {
           my $lost = $rh;
           $select->remove($rh);
           close($rh);
           lost_socket($lost);
        }
     }
  }
}

sub parse_daemon_stats {
   #<NODE HOST="ip" PORT="port" CLASS="class"/>
   my $tag = $_[0];

   my %attrs = ();
   my $atext = 0;

   $tag =~ /^<node\s+(.*?)\s*\/>/i || next;

   # Extract the attributes from the tag
   $atext = $1;
   while($atext =~ /(\S+?)=\"(.*?)\"/g) {
      $attrs{"\L$1\E"} = $2;
   }

   print_stdout("\n* $attrs{host} (Class: \L$attrs{class}\E)");
   if ($attrs{class} =~ /^search$/i) {
      print_stdout(" (Users: $attrs{users}) (Shares: $attrs{shares}) (Megs: $attrs{megs})");
   }
}

sub parse_download_results {
   #<TRANSFER NAME="filename" RECV="received" TOTAL="total"/>
   my $filehandle = $_[0];
   my $tag = $_[1];

   my %attrs = ();
   my $atext = 0;

   $tag =~ /^<transfer\s+(.*?)\s*\/>/i || next;

   # Extract the attributes from the tag
   $atext = $1;
   while($atext =~ /(\S+?)=\"(.*?)\"/g) {
      $attrs{"\L$1\E"} = $2;
   }

   $CUR_DOWNLOADS{$filehandle}{kbps} = int(($attrs{recv} - $CUR_DOWNLOADS{$filehandle}{lrcv}) / 1024) . "kb/s";
   if (length($attrs{name}) > 40) {
      $attrs{name} = substr($attrs{name},0,37) . "...";
   } 
   while (length($attrs{name}) < 42) {
      $attrs{name} .= " ";
   } 
   $CUR_DOWNLOADS{$filehandle}{name} = "$attrs{name}";
   $CUR_DOWNLOADS{$filehandle}{recv} = "($attrs{recv}/$attrs{total})";
   $CUR_DOWNLOADS{$filehandle}{prgs} = int($attrs{recv} / $attrs{total} * 100) . "%";
   $CUR_DOWNLOADS{$filehandle}{lrcv} = $attrs{recv};
   $CUR_DOWNLOADS{$filehandle}{size} = $attrs{total};
}

sub parse_search_results {
   my $tag = $_[0];
   $_ = $tag;

   my %attrs = ();
   my $atext = 0;

   # Make sure this is an <item/> tag
   /^<item\s+(.*?)\s*\/>/i || return;

   # Extract the attributes from the tag
   $atext = $1;
   while($atext =~ /(\S+?)=\"(.*?)\"/g) {
      $attrs{"\L$1\E"} = $2;
   }

   # We insist there be an href attribute
   return unless defined $attrs{'href'};

   # Weed out duplicates
   return if defined $seen_items{href}{$attrs{'href'}};
   $seen_items{href}{$attrs{'href'}} = 1;

   if ($hashes{$attrs{hash}}) { push(@{$hashes{$attrs{hash}}},$attrs{href}) and return; }
   push(@{$hashes{$attrs{hash}}},$attrs{href});

   # Parse out the host/port pair, and the filename
   $attrs{'href'} =~ /^http:\/\/(.*?)\:(.*?)\/(.*)\/(.*)$/ or return;
   ($host, $port, $path, $name) = ($1, $2, $3, $4);
   #next if ($hostport =~ /^172\.168\./ || $hostport =~ /^192\.168\./ || $hostport =~ /^10\./ || $hostport =~ /^169\.254\./ || $hostport =~ /^127\./);
   #delete($attrs{'href'});

   # Do we have a listed file size?
   if (defined $attrs{'filesize'}) {
      $length = "$attrs{'filesize'}";
   } else {
     $length = '';
   }

   if ($search_results == 1) { print_stdout("\n(Press Enter to view results)\n"); }
   print_stdout ("(" . $search_results . " results)\r");

   $downloads{$search_results}{name} = "$name";
   $downloads{$search_results}{href} = "$attrs{href}";
   $downloads{$search_results}{hash} = "$attrs{hash}";
   $downloads{$search_results}{size} = "$length";

   $search_results++;
}

sub show_search_results {
   #&remove_socket($SEARCHING);
   $SEARCHING = undef;
   my $number = 1;

   if ($search_results == 1) { print_stdout("\n[No search results]\n\n"); return &start_prompt; }
   print_stdout("\n\n");

   while($number < $search_results) {
      print_stdout("[$number] " . &escape("$downloads{$number}{name}") . " ($downloads{$number}{size} bytes)\n");

      if ($number % 10 == 0) {
         print_stdout("\n-- (<return> to continue, Q <return> to quit) -- ");
         my $continue = <STDIN>;
         last if ($continue =~ /^q(uit)?$/i);
         print_stdout("\n");
      }

      $number++;
   }

   print_stdout("\n");
   &start_prompt;
}

sub parse_stdin {
   chomp(my $data = "@_");
   print_stdout("** parse_stdin ($data)\n") if $DEBUG;

   if ($SEARCHING) { &show_search_results; }
   elsif ($STATTING) { &parse_daemon_stats; }
   elsif ($PROMPTING) { &parse_prompt($data); }
}

sub parse_socket {
   my $fd = $_[0];
   my $data = $_[1];

   print_stdout("** parse_socket " . get_fileno($fd) . " ($data)\n") if $DEBUG;

   if ($fd == get_fd($SEARCHING)) {
      &parse_search_results("$data");
   }
   elsif ($fd == get_fd($STATTING)) {
      &parse_daemon_stats("$data");
   }
   elsif ($DOWNLOADING{$fd}) {
      &parse_download_results($DOWNLOADING{$fd},"$data");
   }
}

sub parse_sources {
   my $download_number = $_[0];
   my $i;
   my $address;

   print_stdout("\n(No such ID, try doing a search first)\n\n") and return 0 unless $downloads{$download_number};
   print_stdout("\n(Sources for " . &escape("$downloads{$download_number}{name}") . ")\n");
   foreach $href (@{$hashes{$downloads{$download_number}{hash}}}) {
      $i++;
      $href =~ /^http:\/\/(.+?)\//i;
      $address = $1;
      print_stdout("[$i] $address\n");
   }
   print_stdout("\n");
}

sub parse_prompt {
   my $command = "@_";

   if ($command =~ /^$/) {
      $PROMPTING = undef;
      return &start_prompt;
   }
   elsif ($command =~ /^(help|\?)$/i) {
      $PROMPTING = undef;
      &prompt_help;
      return &start_prompt;
   }
   elsif ($command =~ /^download (.*)/i) {
      $PROMPTING = undef;
      &start_download("$1") or return &start_prompt;
      return &start_prompt;
   }
   elsif ($command =~ /^pause (.*)/i) {
      $PROMPTING = undef;
      &pause_download("$1") or return &start_prompt;
      return &start_prompt;
   }
   elsif ($command =~ /^cancel (.*)/i) {
      $PROMPTING = undef;
      &cancel_download("$1") or return &start_prompt;
      return &start_prompt;
   }
   elsif ($command =~ /^resume (.*)/i) {
      $PROMPTING = undef;
      &resume_download("$1") or return &start_prompt;
      return &start_prompt;
   }
   elsif ($command =~ /^search (.*)/i) {
      $PROMPTING = undef;
      &start_search("$1") or return &start_prompt;
   }
   elsif ($command =~ /^source (.*)/i) {
      $PROMPTING = undef;
      &start_source("$1");
   }
   elsif ($command =~ /^stats$/i) {
      $PROMPTING = undef;
      &start_toot_stats;
      return &start_prompt;
   }
   elsif ($command =~ /^info (.+)$/i) {
      $PROMPTING = undef;
      &parse_sources($1) or return &start_prompt;
      return &start_prompt;
   }
   elsif ($command =~ /^share$/i) {
      $PROMPTING = undef;
      &start_share or return &start_prompt;
      return &start_prompt;
   }
   elsif ($command =~ /^clear$/i) {
      $PROMPTING = undef;
      print_stdout($CLEAR);
      return &start_prompt;
   }
   elsif ($command =~ /^q(uit)?$/i) {
      print_stdout("\nGoodbye!\n\n");
      exit(1);
   }
   else {
      $PROMPTING = undef;
      &prompt_help;
      return &start_prompt;
   }

   $PROMPTING = undef;
}

sub print_stdout {
   select(STDOUT); $|=1;
   print STDOUT "@_";
}

sub print_socket {
   my $this_sock = get_fd($_[0]);
   print_stdout("** print_socket " . get_fileno(get_fd($_[0])) . " ($_[1])\n") if $DEBUG;
   print $this_sock "$_[1]\n";
}

sub pause_download {
   my $download_number = "@_";

   unless (defined $CUR_DOWNLOADS{$download_number}) {
      print_stdout("No such download in progress. Check `stats'\n\n");
      return 0;
   }

   print_socket($download_number,"<transfer method=\"download\" action=\"stop\"/>");
}

sub cancel_download {
   my $download_number = "@_";

   unless (defined $CUR_DOWNLOADS{$download_number}) {
      print_stdout("No such download in progress. Check `stats'\n\n");
      return 0;
   }

   print_socket($download_number,"<transfer method=\"download\" action=\"close\"/>");
}

sub resume_download {
   my $download_number = "@_";

   unless (defined $CUR_DOWNLOADS{$download_number}) {
      print_stdout("No such download in progress. Check `stats'\n\n");
      return 0;
   }

   print_socket($download_number,"<transfer method=\"download\" action=\"resume\"/>");
}

sub start_download {
   my $download_number = "@_";
   my $href;
   my $address;

   unless (defined $downloads{$download_number}{hash}) {
      print_stdout("No such download, try searching first\n\n");
      return 0;
   }

   my $socket_num = &create_socket or return 0;
   $DOWNLOADING{get_fd($socket_num)} = $socket_num;

   print_stdout("(Downloading " . &escape("$downloads{$download_number}{name}") . ")\n");
   print_socket($socket_num,"<connect protocol=\"OpenFT\"/>");
   print_socket($socket_num,"<transfer method=\"download\" action=\"start\" name=\"" . &escape("$downloads{$download_number}{name}") . "\" size=\"$downloads{$download_number}{size}\"/>");

   foreach $href (@{$hashes{$downloads{$download_number}{hash}}}) {
      $href =~ /^http:\/\/(.+?)\//i;
      $address = $1;
      print_stdout("+ Adding source: $address\n");
      print_socket($socket_num,"<transfer method=\"download\" action=\"addsource\" location=\"$href\"/>");
   }
   print_stdout("\n");

   my $down_name = &escape($downloads{$download_number}{name});

   if (length($down_name) > 40) {
      $down_name = substr($down_name,0,37) . "...";
   }
   while (length($down_name) < 42) {
      $down_name .= " ";
   }
   $CUR_DOWNLOADS{$socket_num}{name} = $down_name;
   $CUR_DOWNLOADS{$socket_num}{prgs} = "Processing...";
}

sub start_search {
   my $search_query = "@_";

   %downloads = ();
   %hashes = ();
   %seen_items = ();
   $search_results = 1;

   $SEARCHING = &create_socket or return 0;

   if ($search_query =~ /(.+?) (.+)/) { $search_opts = "realm=\"$1\" query=\"$2\""; }
   else { $search_opts = "query=\"$search_query\""; }

   print_socket($SEARCHING,"<connect protocol=\"OpenFT\"/>");
   print_socket($SEARCHING,"<search $search_opts/>");
}

sub start_prompt {
   return if ($PROMPTING || $STATTING);
   print_stdout("(toot) ");
   $PROMPTING = 1;
}

sub start_daemon_stats {
   @STATS = ();

   $STATTING = &create_socket or return 0;

   print_socket($STATTING,"<connect protocol=OpenFT/>");
   print_socket($STATTING,"<stats/>");
}

sub start_toot_stats {
   print_stdout("\n");
   print_stdout("(Downloads)\n");
   my $key;
   foreach $key (keys(%CUR_DOWNLOADS)) {
      print_stdout("[$key] $CUR_DOWNLOADS{$key}{name}$CUR_DOWNLOADS{$key}{kbps}\t$CUR_DOWNLOADS{$key}{prgs}\t$CUR_DOWNLOADS{$key}{recv}\n");
   }
   #print_stdout("\n");

   #print_stdout("(GB available)\n");
   #print_stdout("\n");

   #print_stdout("(Files available)\n");
   #print_stdout("\n");

   #print_stdout("(Supernodes)\n");

   print_stdout("\n");
}

sub start_source {
   my $download_number = $_[0];
   &start_search("md5 $downloads{$download_number}{hash}");
}

sub start_share {
   my $filehandle = &create_socket or return 0;
   print_socket($filehandle,"<connect protocol=\"OpenFT\"/>");
   print_socket($filehandle,"<share action=\"sync\"/>");
   #&remove_socket($filehandle);
   print_stdout("Daemon synced with ~/.giFT/shares\n\n");
}

sub escape {
   my $tmp = "@_";
   $tmp =~ s/%(..)/pack("c",hex($1))/ge;
   $tmp =~ s/\+/ /g;
   $tmp =~ s/\_/ /g;
   return "$tmp";
}

sub get_fd {
   return $socket{$_[0]};
}

sub get_fileno {
   return $_[0]->fileno;
}

sub lost_socket {
   my $lost = $_[0];
   print_stdout("** lost_socket (" . get_fileno($lost) . ")\n") if $DEBUG;

   if (get_fd($SEARCHING) == $lost) {
      return &show_search_results;
   }
   elsif ($DOWNLOADING{$lost}) {
      $CUR_DOWNLOADS{$DOWNLOADING{$lost}}{prgs} = undef;
      $CUR_DOWNLOADS{$DOWNLOADING{$lost}}{kbps} = undef;
      if ($CUR_DOWNLOADS{$DOWNLOADING{$lost}}{lrcv} == $CUR_DOWNLOADS{$DOWNLOADING{$lost}}{size}) {
         $CUR_DOWNLOADS{$DOWNLOADING{$lost}}{recv} = "Completed";
      }
      else {
         $CUR_DOWNLOADS{$DOWNLOADING{$lost}}{recv} = "Failed!";
      }
   }
   elsif (get_fd($STATTING) == $lost) {
      $STATTING = undef;
      print_stdout("\n\n");
      exit(0);
   }
}

sub create_socket {
   $fds++;
   $socket{$fds} = new IO::Socket::INET (
                     PeerAddr => "$RemoteHost",
                     PeerPort => "$RemotePort",
                     Proto => 'tcp'
   );

   if (!$socket{$fds}) { print_stdout("Couldn't connect to daemon: $!\n\n") and return 0; }

   $socket{$fds}->autoflush(1);
   $select->add($socket{$fds});

   $socket_fd = get_fileno($socket{$fds});
   print_stdout("** create_socket ($socket_fd)\n") if $DEBUG;

   return $fds;
}

sub remove_socket {
   my $remove = $_[0];
   print_stdout("** remove_socket (" . get_fileno($socket{$remove}) . ")\n") if $DEBUG;
   close($socket{$remove});
   #$select->remove($socket{$remove});
   return;
}

sub show_intro {
   print_stdout("\n");
   print_stdout("\t################################\n");
   print_stdout("\t#    Welcome to TooT v$VERSION    #\n");
   print_stdout("\t#     Type 'help' for help     #\n");
   print_stdout("\t################################\n\n");
}

sub usage {
   print_stdout("\n\tUsage: $0 [-d] [-shbv]\n");
   print_stdout("\t-s     -stats        Show current giFT statistics\n");
   print_stdout("\t-d     -debug        Enter Debug Mode\n");
   print_stdout("\t-h     -help         Show this screen\n");
   print_stdout("\t-b     -buildshares  Rebuild shares list\n");
   print_stdout("\t-v     -version      Show version number\n\n");
}

sub prompt_help {
   print_stdout("\n\t[help]\t\tShows this screen\n\n");
   print_stdout("\t[download]\tDownloads specified file\n\n");
   print_stdout("\t[search]\tSearch for specified file\n\n");
   print_stdout("\t[source]\tFind more sources for file\n\n");
   print_stdout("\t[info]\t\tShow info on search result\n\n");
   print_stdout("\t[stats]\t\tShow Toot Stats\n\n");
   print_stdout("\t[share]\t\tSync daemon with shares file\n\n");
   print_stdout("\t[clear]\t\tClear the screen\n\n");
}
