#!/usr/pkg/bin/perl
# -*- coding: ascii -*-
#
# clive
# Copyright (C) 2010-2011  Toni Gundogdu <legatvs@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

use warnings;
use strict;

use version 0.77 (); our $VERSION = version->declare("2.3.3");

binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

use Getopt::ArgvFile qw(argvFile);

use Getopt::Long qw(:config bundling);
use Encode qw(decode_utf8);
use Carp qw(croak);

my $depr_msg = "Warning:
    '--format list' is deprecated and will be removed in the later
    versions. Use --query-formats instead.";

my $quvi_quiet_switch = '-q';
my %config;
my @queue;
my $media;

exit main();

sub main
{
  init();
  return process_queue();
}

sub init
{
  if (grep {$_ eq "--config-file"} @ARGV)
  {
    argvFile(fileOption => '--config-file');
  }
  else
  {
    @ARGV = (
             @ARGV,
             (
              "@/usr/pkg/etc/cliverc",
              "@/usr/share/clive/cliverc",
              "@/etc/clive/config",
              "@/etc/xdg/clive/clive.conf",
              "@/etc/xdg/clive.conf"
             )
            );

    if ($ENV{HOME})
    {
      @ARGV = (
               @ARGV,
               (
                '@' . "$ENV{HOME}/.cliverc",
                '@' . "$ENV{HOME}/.clive/config",
                '@' . "$ENV{HOME}/.config/clive/config"
               )
              );
    }

    push @ARGV, '@' . "$ENV{CLIVE_CONFIG}" if $ENV{CLIVE_CONFIG};

    argvFile();
  }

  GetOptions(
    \%config,
    'help'    => \&print_help,
    'version' => sub {print "clive version $VERSION\n"; exit 0},
    'license' => \&print_license,
    'quiet|q',
    'query_formats|query-formats|F',
    'format|f=s',
    'output_file|output-file|O=s',
    'no_download|no-download|n',

    # Configuration:
    'quvi=s',
    'get_with|get-with=s',
    'filename_format|filename-format=s',
    'regexp=s',
    'exec=s',
            ) or exit 1;

  $config{format}          ||= 'default';
  $config{filename_format} ||= '%t.%s';
  $config{regexp}          ||= '/(\\w|\\s)/g';

  # Check --quvi.
  unless ($config{quvi})
  {
    print "Detect quvi from \$PATH\n" unless $config{quiet};

    my $s = detect_cmd('quvi');
    if ($s)
    {
      $config{quvi} = "quvi %u";
    }
    else
    {
      croak "error: specify path to quvi(1) command with --quvi\n";
    }
  }
  check_quvi();
  check_format();

  # Check --get-with.
  unless ($config{get_with})
  {

    print "Detect a download command from \$PATH\n"
      unless $config{quiet};

    my %h = (
      curl => "-L -C - -o %f %u --user-agent Mozilla/5.0",

      # Add new ones below.
            );

    for my $k (keys %h)
    {
      my $s = detect_cmd($k);
      if ($s)
      {
        $config{get_with} = "$k $h{$k}";
        last;
      }
    }

    croak "error: specify path to a download command with --get-with\n"
      unless $config{get_with};
  }

  # Check --regexp.

  apply_regexp();

  # Process input.

  if (scalar @ARGV == 0)
  {
    append_queue($_) while <STDIN>;
  }
  else
  {
    foreach (@ARGV)
    {
      if (!is_url($_))
      {
        open my $fh, "<", $_
          or print STDERR "$_: $!\n" and next;
        append_queue($_) while <$fh>;
        close $fh;
      }
      else
      {
        append_queue($_);
      }
    }
  }

  @queue = uniq2(@queue);    # Remove duplicate URLs.

  print STDERR "error: no input urls\n" and exit 0x3    # QUVI_INVARG
    unless scalar @queue;

  select STDOUT;
  $| = 1;                                               # Go unbuffered.
}

sub detect_cmd
{
  my ($cmd) = @_;

  print "  Check for $cmd ..." unless $config{quiet};
  my $o = join '', qx|$cmd --version 2>/dev/null|;

  if ($? >> 8 == 0)
  {

    # TODO: Use more a elegant regexp combining all three.
    my @a =
      (qr|(\d+.\d+.\d+-\w+-\w+)|, qr|(\d+.\d+.\d+)|, qr|(\d+.\d+)|);
    foreach (@a)
    {
      if ($o =~ /$_/)
      {
        print "$1\n" unless $config{quiet};
        return $1;
      }
    }
  }
  else
  {
    print "no\n" unless $config{quiet};
  }
  undef;
}

sub is_url
{
  return $_ =~ /^\w+\:\/\//;
}

sub append_queue
{
  my $ln = trim(shift);
  chomp $ln;

  return if $ln =~ /^$/;
  return if $ln =~ /^#/;

  push @queue, $ln;
}

sub uniq2
{    # http://is.gd/g8jQU
  my %seen = ();
  my @r    = ();
  foreach my $a (@_)
  {
    unless ($seen{$a})
    {
      push @r, $a;
      $seen{$a} = 1;
    }
  }
  @r;
}

sub process_queue
{
  require JSON::XS;

  my $n = scalar @queue;
  my $i = 0;
  my $r = 0;
  my $fpath;

  foreach (@queue)
  {
    print_checking(++$i, $n);

    my $q = $config{quvi};
    $q =~ s/%u/"$_"/;
    $q .= " $quvi_quiet_switch"
      if $q !~ /$quvi_quiet_switch/;    # Force quiet.
    $q .= " -f $config{format}";
    $q .= " -F" if $config{query_formats};

    my $o = join '', qx/$q/;
    $r = $? >> 8;

    next unless $r == 0;

    print "done.\n" unless $config{quiet};
    print $o and next if $config{query_formats};

    $media = JSON::XS::decode_json($o);
    ($r, $fpath) = get_media();
    if ($r == 0)
    {
      $r = invoke_exec($fpath) if $config{exec};
    }
  }
  $r;
}

sub print_checking
{
  return if $config{quiet};

  my ($i, $n) = @_;

  print "($i of $n) " if $n > 1;
  print "Checking ...";
}

sub get_media
{
  require File::Basename;

  my $fpath = get_filename();
  my $fname = File::Basename::basename($fpath);

  if ($config{no_download}) {print_media($fname); return 0;}

  write_media($fpath, $fname);
}

sub invoke_exec
{
  my $fpath = shift;

  my $e = $config{exec};
  $e =~ s/%f/"$fpath"/g;

  qx/$e/;

  $? >> 8;
}

sub to_mb {(shift) / (1024 * 1024);}

sub print_media
{
  printf "%s  %.2fM  [%s]\n",
    shift,
    to_mb($media->{link}[0]->{length_bytes}),
    $media->{link}[0]->{content_type};
}

sub write_media
{
  my ($fpath, $fname) = @_;

  my $g = $config{get_with};
  $g =~ s/%u/"$media->{link}[0]->{url}"/g;
  $g =~ s/%f/"$fpath"/g;
  $g =~ s/%n/"$fname"/g;

  qx/$g/;

  ($? >> 8, $fpath);
}

sub get_filename
{
  my $fpath;

  if ($config{output_file}) {$fpath = $config{output_file};}
  else {$fpath = apply_output_path(apply_filename_format());}

  $fpath;
}

sub apply_output_path
{
  require Cwd;

  # Do not touch.
  my $cwd   = decode_utf8(Cwd::getcwd);
  my $fname = shift;

  require File::Spec::Functions;

  File::Spec::Functions::catfile($cwd, $fname);
}

sub apply_filename_format
{
  return $config{output_filename}
    if $config{output_filename};

  my $title = trim(apply_regexp($media->{page_title}));
  my $fname = $config{filename_format};

  $fname =~ s/%s/$media->{link}[0]->{file_suffix}/g;
  $fname =~ s/%h/$media->{host}/g if $media->{host};    # quvi 0.2.8+
  $fname =~ s/%i/$media->{id}/g;
  $fname =~ s/%t/$title/g;

  $fname;
}

sub trim
{
  my $s = shift;
  $s =~ s{^[\s]+}//;
  $s =~ s{\s+$}//;
  $s =~ s{\s\s+}/ /g;
  $s;
}

sub apply_regexp
{
  my ($title, $rq) = (shift, qr|^/(.*)/(.*)$|);

  if ($config{regexp} =~ /$rq/)
  {
    return unless $title;    # Must be a syntax check.

    $title = decode_utf8($title);    # Do not touch.

    my ($pat, $flags, $g, $i) = ($1, $2);

    if ($flags)
    {
      $g = ($flags =~ /g/);
      $i = ($flags =~ /i/);
    }

    $rq = $i ? qr|$pat|i : qr|$pat|;

    return $g
      ? join '', $title =~ /$rq/g
      : join '', $title =~ /$rq/;
  }

  croak "error: --regexp: expects "
    . "`/pattern/flags', for example: `/(\\w)/g'\n";
}

sub detect_quvi_version
{
  my $q = (split /\s+/, $config{quvi})[0];    # Improve this.
  my $o = qx|$q --version|;
  if ($? >> 8 == 0)
  {
    return ($1, $2, $3) if (split /\n/, $o)[0] =~ /(\d+).(\d+).(\d+)/;
  }
  print "warning: unable to detect quvi version\n"
    unless $config{quiet};
  -1;
}

sub check_quvi
{
  my @v = detect_quvi_version();
  $quvi_quiet_switch = '-vq' if $v[0] >= 0 && $v[1] >= 4 && $v[2] >= 1;
}

sub check_format
{
  if ($config{format} eq "help")
  {
    printf "Usage:
     --format arg                get format arg of media
     --format list               print domains with formats
     --format list arg           match arg to supported domain names
Examples:
     --format list youtube       print youtube formats
     --format fmt34_360p         get format fmt34_360p of media
%s\n", $depr_msg;
    exit 0;
  }

  elsif ($config{format} eq "list")
  {
    my $q = (split /\s+/, $config{quvi})[0];    # Improve this.

    my %h;
    foreach (qx/$q --support/)
    {
      my ($k, $v) = split /\s+/, $_;
      $h{$k} = $v;
    }

    # -f list <pattern>
    if (scalar @ARGV > 0)
    {

      foreach (sort keys %h)
      {
        print "$_:\n  $h{$_}\n" if $_ =~ /$ARGV[0]/;
      }
    }

    # -f list
    else
    {
      print "$_:\n  $h{$_}\n\n" foreach sort keys %h;
    }

    printf "%s\n", $depr_msg;

    exit 0;
  }
}

sub print_help
{
  require Pod::Usage;
  Pod::Usage::pod2usage(-exitstatus => 0, -verbose => 1);
}

sub print_license
{
  print "# clive
# Copyright (C) 2010-2011  Toni Gundogdu <legatvs\@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
";
  exit 0;
}

__END__

=head1 SYNOPSIS

clive [-F] [-n] [--format=E<lt>value<gt>] [--output-file=E<lt>value<gt>]
      [--filename-format=E<lt>valueE<gt>] [--config-file=E<lt>value<gt>]
      [--quvi=E<lt>valueE<gt>] [--get-with=E<lt>valueE<gt>]
      [--regexp=E<lt>valueE<gt>] [--exec=E<lt>valueE<gt>]
      [--help] [--version] [--license] [--quiet]
      [E<lt>urlE<gt> | E<lt>fileE<gt>]

=head2 OPTIONS

      --help                      Print help and exit
      --version                   Print version and exit
      --license                   Print license and exit
      --quiet                     Turn off all output excl. errors
  -F, --query-formats             Query available formats to URL
  -f, --format arg (=default)     Download media format
  -O, --output-file arg           Write media to arg
  -n, --no-download               Do not download media, print details
      --config-file arg           File to read clive arguments from
Configuration:
  --quvi arg                      Path to quvi(1) with additional args
  --get-with arg                  Path to download command with args
  --filename-format arg (=%t.%s)  Downloaded media filename format
  --regexp arg (=/(\w|\s)/g)      Regexp to cleanup media title
  --exec arg                      Invoke arg after each finished download

=cut

# vim: set ts=2 sw=2 tw=72 expandtab:
