#!/usr/bin/perl

use strict;

use Net::SCP qw(scp);
use Net::SMTP;

use lib 'c:/cygwin/app/tools/distribution/bin/bulk_injest';
use RSWS::ClientInfo;
use RSWS::AssetMgmt;

use Crypt::SSLeay;

# Program Directories
use constant LOCAL_IMAGE_DUMP_WIN => 'c:/image_import';
use constant LOCAL_AUDIO_DUMP_WIN => 'c:/audio_import';

use constant TEMP_DIR_WIN => 'c:/temp/' . $$;
use constant TEMP_DIR_CYG => '/cygdrive/c/temp/' . $$;

# Program required executables
use constant IM_IDENTIFY => 'identify';
use constant IM_CONVERT  => 'convert';
use constant FLAC        => 'flac';
use constant METAFLAC    => 'metaflac';

my @status_log;

$| = 1;    # Unbuffer output

print "RoyaltyShare Content Management bulk asset uploader\n";

my $upload_type;
do {
    print "\nUpload Audio or Images? [A/I]: ";
    $upload_type = lc <STDIN>;
    chomp $upload_type;
    unless ( $upload_type =~ m/^[ai]$/i ) {
        print "Invalid choice!\n";
        $upload_type = undef;
    }
} while ( !$upload_type );

my ( $client_id, $client_name, $client_host ) = &get_client_id();

if ( $upload_type eq 'a' ) {
    &audio_importer( $client_id, \@status_log, $client_host );
} elsif ( $upload_type eq 'i' ) {
    &image_importer( $client_id, \@status_log, $client_host );
}

&send_email_report( \@status_log, $client_name, $upload_type );

print "\nPress the enter key to exit...";
my $in = <>;
exit;

### MAIN SUBROUTINES ###

sub audio_importer ($$$) {
    my $client_id  = shift;
    my $status_log = shift;
    my $host_id    = shift;
    my @audiodir_success;

    chdir LOCAL_AUDIO_DUMP_WIN or die "can't access " . LOCAL_AUDIO_DUMP_WIN . ": $!";
    unless ( -d "old" ) {
        mkdir "old" or die "can't create " . LOCAL_AUDIO_DUMP_WIN . "/old: $!";
    }
    unless ( -d "err" ) {
        mkdir "err" or die "can't create " . LOCAL_AUDIO_DUMP_WIN . "/err: $!";
    }

    my @audiodirs = &get_audiodirs();

    unless (@audiodirs) {
        print "no audio to process!\n";
        return;
    }

    my @verified_audio;

    foreach my $audiodir (@audiodirs) {
        next unless ($audiodir);
        print "\nprocessing $audiodir...\n";
        my @errors;
        my $valid = &validate_audio( $audiodir, \@errors );
        if ($valid) {
            push @verified_audio, $audiodir;
        } else {
            print join "\n", @errors;
            print "\n";
            push @$status_log, @errors;
            next;
        }

        chdir LOCAL_AUDIO_DUMP_WIN . "/$audiodir" or die "can't access " . LOCAL_AUDIO_DUMP_WIN . "/$audiodir: $!";
        my @files = <*.flac>;
        my $asset = RSWS::AssetMgmt->new( client_id => $client_id, action => 'save', media_type => 'audio', host_id => $host_id );
        $asset->session_id( time() );
        foreach my $file (@files) {
            $asset->file_name($file);
        }
        my $assetInfo = $asset->fetch() || print $asset->error;

        if ( $asset->response_error_count() ) {
            warn "content from $audiodir is already in the system!\n";
            push @$status_log, "content from $audiodir is already in the system!";
            next;
        }

        my $errors = 0;
        foreach my $file (@files) {
            die "didn't get back info for $file" unless ( exists $assetInfo->{$file} );    ## cleaner way?
            if ( &upload_file( $file, $assetInfo->{$file}->{HostName}, $assetInfo->{$file}->{AbsPath} ) ) {
                my $asset = RSWS::AssetMgmt->new( client_id => $client_id, action => 'status', media_type => 'audio', host_id => $host_id );
                $asset->file_id( $assetInfo->{$file}->{MediaFileID} );
                my $assetInfo = $asset->fetch() || print $asset->error;
                if ( $asset->response_error_count() ) {
                    warn "status update on $file failed!\n";
                    push @$status_log, "status update on $file failed!";
                    $errors++;
                }
            } else {
                warn "upload failed on $file!\n";
                push @$status_log, "upload failed on $file!";
                $errors++;
            }
        }
        push @audiodir_success, $audiodir unless ( $errors > 0 );
        chdir LOCAL_AUDIO_DUMP_WIN or die "can't access " . LOCAL_AUDIO_DUMP_WIN . ": $!";
        rename $audiodir, "old/$audiodir" or warn "can't move $audiodir: $!";
    }
    my @errfiles = <*>;
    foreach my $errfile (@errfiles) {
        if ( $errfile ne "old" && $errfile ne "err" ) {
            rename $errfile, "err/$errfile" or warn "can't move $errfile: $!";
        }
    }
    push @$status_log, "\n\nAll audio imported successfully for the following:";
    push @$status_log, @audiodir_success;
}

sub image_importer ($$) {
    my $client_id  = shift;
    my $status_log = shift;
    my $host_id    = shift;
    my @images_success;

    chdir LOCAL_IMAGE_DUMP_WIN or die "can't access " . LOCAL_IMAGE_DUMP_WIN . ": $!";
    unless ( -d "old" ) {
        mkdir "old" or die "can't create " . LOCAL_IMAGE_DUMP_WIN . "/old: $!";
    }
    unless ( -d "err" ) {
        mkdir "err" or die "can't create " . LOCAL_IMAGE_DUMP_WIN . "/err: $!";
    }

    my @images = &get_images();

    unless (@images) {
        print "no images to process!\n";
        return;
    }

    mkdir TEMP_DIR_WIN or die "can't create " . TEMP_DIR_WIN . ": $!";

    foreach my $imagefile (@images) {
        next unless ($imagefile);
        print "\nprocessing $imagefile...\n";
        my @errors;
        my $valid = &validate_image( $imagefile, \@errors );
        unless ($valid) {
            print join "\n", @errors;
            print "\n";
            push @$status_log, @errors;    ### TODO <<-- did this not work?
            next;
        }

        my ( $orig, $tiff, $jpeg1, $jpeg2 ) = &convert_image($imagefile);
        if ( $orig && $tiff && $jpeg1 && $jpeg2 ) {
            rename $imagefile, "old/$imagefile" or warn "can't move $imagefile: $!";
        } else {
            print "image conversion on $imagefile failed! skipping...\n";
            next;
        }

        chdir TEMP_DIR_WIN or die "can't access " . TEMP_DIR_WIN . ": $!";
        foreach my $size (qw(orig dist med th)) {
            my $image = ( $size eq "orig" ) ? $orig : ( $size eq "dist" ) ? $tiff : ( $size eq "med" ) ? $jpeg1 : $jpeg2;
            my $type =
                ( $size eq "orig" ) ? RSWS::AssetMgmt::IMAGE_ORIG
              : ( $size eq "dist" ) ? RSWS::AssetMgmt::IMAGE_DIST
              : ( $size eq "med" )  ? RSWS::AssetMgmt::IMAGE_MED
              :                       RSWS::AssetMgmt::IMAGE_TH;

            my $asset = RSWS::AssetMgmt->new( client_id => $client_id, action => 'save', media_type => 'image', host_id => $host_id );
            $asset->file_name( $image, $type );
            my $assetInfo = $asset->fetch() || print $asset->error;
            if ( $asset->response_error_count() ) {
                print "$image is already in the system!\n";
                push @$status_log, "$image is already in the system!";
                next;
            }

            if ( &upload_file( $image, $assetInfo->{HostName}, $assetInfo->{AbsPath} ) ) {
                push @images_success, $image if ( $size eq "orig" );
            } else {
                warn "upload failed on $image!\n";
            }
        }
        chdir LOCAL_IMAGE_DUMP_WIN or die "can't access " . LOCAL_IMAGE_DUMP_WIN . ": $!";
    }
    chdir TEMP_DIR_WIN or die "can't access " . TEMP_DIR_WIN . ": $!";
    my @to_delete = <*>;
    foreach my $file (@to_delete) {
        unlink $file or warn "can't delete $file: $!";
    }
    chdir LOCAL_IMAGE_DUMP_WIN or die "can't access " . LOCAL_IMAGE_DUMP_WIN . ": $!";
    rmdir TEMP_DIR_WIN or warn "can't delete " . TEMP_DIR_WIN . ": $!";
    my @errfiles = <*>;
    foreach my $errfile (@errfiles) {
        if ( $errfile ne "old" && $errfile ne "err" ) {
            rename $errfile, "err/$errfile" or warn "can't move $errfile: $!";
        }
    }

    push @$status_log, "\n\nAll images imported successfully for the following:";
    push @$status_log, @images_success;
}

sub get_client_id {
    my $cli = RSWS::ClientInfo->new();
    $cli->clientType(4);    # 1=DA, 2=RPS, 3=Label, 4=Dist, don't call at all to get ALL clients
    my $clients = $cli->fetch() || print $cli->error;

    foreach my $client_id ( sort { $a <=> $b } keys %$clients ) {
        print "$client_id  $clients->{$client_id}->{name}\n";
    }

    my $client_id;
    do {
        print "\nClient ID: ";
        $client_id = <>;
        chomp $client_id;
        unless ( $client_id =~ m/^\d+$/ && exists $clients->{$client_id} ) {
            print "Invalid client ID!\n";
            $client_id = undef;
        }
    } while ( !$client_id );

    return ( $client_id, $clients->{$client_id}->{name}, $clients->{$client_id}->{host} );
}

sub upload_file ($$$) {
    my $file = shift;
    my $host = shift;
    my $path = shift;

    my $scp = Net::SCP->new( { "host" => $host, "user" => "web" } );

    my ($path_mod) = $path =~ m/^\/?(.*)\/?$/;
    my @path = split "/", $path_mod;
    my $destfile = pop @path;

    # scp mkdir uses -p
    my $path_create = "/";
    foreach my $dir (@path) {
        $path_create .= "$dir/";
        $scp->mkdir($path_create);    ## TODO: or problem!
    }

    return 1 if ( $scp->put( $file, $path ) );
    print $scp->{errstr};
    return 0;
}

sub send_email_report ($$) {
    my $report = shift;
    my $client = shift;
    my $type   = shift;
    $type = ( $type eq 'a' ) ? "audio" : "image";
    my $text = join "\n", @$report;
    print "Results for $client\n\n$text\n";
    my $smtp = Net::SMTP->new("royaltyshare.com.s5a1.psmtp.com");
    $smtp->mail('cm_update@royaltyshare.com');
    $smtp->to('cm_update@royaltyshare.com');
    $smtp->data();
    $smtp->datasend("To: CM Info List <cm_update\@royaltyshare.com>\n");
    $smtp->datasend("From: CM Bulk Ingestion <cm_update\@royaltyshare.com>\n");
    $smtp->datasend("Subject: $client bulk $type import report\n");
    $smtp->datasend("\n");
    $smtp->datasend($text);
    $smtp->dataend();
    $smtp->quit;
}

### HELPER SUBROUTINES FOR AUDIO ###

sub get_audiodirs {
    my @files = <*>;
    my @audiodirs;
    foreach my $fname (@files) {
        if ( -d $fname && $fname =~ m/^\d{12,13}$/ ) {
            ## TODO: or do we want all directories in there, besides 'old'?
            my @flacs = <$fname/*.flac>;
            push @audiodirs, $fname if (@flacs);
        }
    }
    return @audiodirs;
}

sub validate_audio ($$) {
    my $dirname  = shift;
    my $errors   = shift;
    my $errcount = 0;

    my @contents   = sort <$dirname/*>;
    my $last_disc  = 0;
    my $last_track = 0;
    foreach my $file (@contents) {
        my $clean_name = lc $file;
        rename $file, $clean_name;
        $file = $clean_name;
        unless ( $file =~ m/^${dirname}\/${dirname}_\d\d_\d\d\d\.flac$/ ) {
            ## TODO: but I really only want to print this error once...
            push @$errors, "$dirname contains improperly named flacs or non-flac files";
            $errcount++;
            next;
        }

        $file =~ m/_(\d\d)_(\d\d\d)\.flac$/;
        my $disc  = $1;
        my $track = $2;

        if ( $disc == $last_disc ) {
            unless ( $track == $last_track + 1 ) {
                push @$errors, "$dirname contains out of order track numbers";
                $errcount++;
                next;
            }
        } elsif ( $disc == $last_disc + 1 ) {
            unless ( $track == 1 ) {
                push @$errors, "$dirname contains out of order track numbers";
                $errcount++;
                next;
            }
        } else {
            push @$errors, "$dirname contains out of order disc numbers";
            $errcount++;
            next;
        }

        $last_disc  = $disc;
        $last_track = $track;

        my @errlist;
        my $valid = &validate_audio_file( $file, \@errlist );
        unless ($valid) {
            push @$errors, @errlist;
            $errcount++;
        }
    }
    return 0 if ( $errcount > 0 );
    return 1;
}

sub validate_audio_file {
    my $file   = shift;
    my $errors = shift;

    my $errcount = 0;

    my $command = FLAC . " -t --totally-silent $file";
    my $status  = system($command);
    if ( $status != 0 ) {
        push @$errors, "$file is an invalid flac";
        return 0;
    }

    $command = METAFLAC . " --remove-all-tags $file";
    $status  = `$command`;                              ## afaik, it doesn't actually return anything...

    $command = METAFLAC . " --show-channels --show-bps --show-total-samples --show-sample-rate $file";
    $status  = `$command`;
    my ( $channels, $bps, $samples, $samplerate ) = split "\n", $status;

    unless ( $channels == 2 ) {
        push @$errors, "Non-stereo audio ($channels channels) on $file";
        $errcount++;
    }
    unless ( $bps == 16 ) {
        push @$errors, "Unsupported bits per sample ($bps BPS) on $file: must be 16";
        $errcount++;
    }
    unless ( $samples > 0 ) {
        push @$errors, "Empty audio file on $file";
        $errcount++;
    }
    unless ( $samplerate == 44100 ) {
        push @$errors, "Unsupported sample rate ($samplerate Hz) on $file: must be 44100 Hz";
        $errcount++;
    }

    return 0 if ( $errcount > 0 );
    return 1;
}

### HELPER SUBROUTINES FOR IMAGES ###

sub get_images {
    my @files = <*>;
    my @images;
    foreach my $fname (@files) {
        if ( $fname =~ m/^\d{12,13}\.(jpe?g|tiff?)$/i ) {
            push @images, $fname;
        } elsif ( $fname ne "old" && $fname ne "err" ) {
            ## return as part of errors?
            print "warning: $fname is either not an image or not properly named\n";
        }
    }
    return @images;
}

sub validate_image ($$) {
    my $filename = shift;
    my $errors   = shift;
    my $errcount = 0;

    ## prep it, forcing to PixelsPerInch
    my $force_ppinch = IM_CONVERT . " $filename -units PixelsPerInch $filename";
    `$force_ppinch`;

    my $command    = IM_IDENTIFY . " -verbose $filename";
    my $imagestats = `$command`;

    my ($format) = $imagestats =~ m/  Format: (.*)\s/;
    my ( $geometry,   $width ) = $imagestats =~ m/  Geometry: ((\d+)x\d+(\+\d+\+\d+)?)\s/;
    my ( $resolution, $dpi )   = $imagestats =~ m/  Resolution: ((\d+)x\d+)\s/;
    my ($resunits)   = $imagestats =~ m/  Units: (.*)\s/;
    my ($colorspace) = $imagestats =~ m/  Colorspace: (.*)\s/;

    unless ( $format =~ m/^(TIFF|JPEG) / ) {
        push @$errors, "Invalid internal format ($format) on $filename: must be TIFF or JPEG";
        $errcount++;
    }
    unless ( $resunits eq "PixelsPerInch" ) {
        push @$errors, "Invalid resolution units ($resunits) on $filename: must be Pixels per Inch";
        $errcount++;
    }
    unless ( $colorspace eq "RGB" || $colorspace eq "Gray" ) {
        ## allowing gray since imagemagick can't tell the difference between grey rgb images and greyscale ones
        push @$errors, "Invalid colorspace ($colorspace) on $filename: must be RGB";
        $errcount++;
    }
    unless ( $resolution =~ m/^(\d+)x(\1)$/ ) {
        push @$errors, "Inconsistent resolution ($resolution) on $filename: must be equal";
        $errcount++;
    }
    unless ( $dpi >= 72 ) {
        push @$errors, "Unsupported resolution ($dpi dpi) on $filename: must be at least 72 dpi";
        $errcount++;
    }
    unless ( $geometry =~ m/^(\d+)x(\1)(\+0\+0)?$/ ) {
        push @$errors, "Inconsistent dimensions ($geometry) on $filename: must be square";
        $errcount++;
    }
    unless ( $width >= 600 ) {
        push @$errors, "Unsupported width ($width) on $filename: must be at least 1425";
        $errcount++;
    }

    return 0 if ( $errcount > 0 );
    return 1;
}

sub convert_image ($) {
    my $orig = shift;
    my ( $tiff, $jpeg1, $jpeg2 ) = &get_new_image_names($orig);

    return 0 if ( system( "cp $orig " . TEMP_DIR_CYG . "/$orig" ) );

    my $command = IM_CONVERT . " -compress none $orig -density 300 -resample 300x300 -resize 1425 " . TEMP_DIR_CYG . "/$tiff";
    my $status  = `$command`;

    my $command1 =
      IM_CONVERT . " -compress none " . TEMP_DIR_CYG . "/$orig -resize 600 -density 72 -quality 100 " . TEMP_DIR_CYG . "/$jpeg1";
    my $status1 = `$command1`;
    my $command2 =
      IM_CONVERT . " -compress none " . TEMP_DIR_CYG . "/$orig -resize 100 -density 72 -quality 100 " . TEMP_DIR_CYG . "/$jpeg2";
    my $status2 = `$command2`;

    ## TODO: check for errors

    return ( $orig, $tiff, $jpeg1, $jpeg2 );
}

sub get_new_image_names ($) {
    my $original_image = shift;
    my $base           = lc $original_image;
    $base =~ s/\.[a-z]{3,4}$//;
    $base =~ s/ /_/g;
    $base =~ s/\W//g;

    my $tiff = $base;
    $tiff .= "-D.tiff";
    my $jpeg1 = $base;
    $jpeg1 .= "-L.jpeg";
    my $jpeg2 = $base;
    $jpeg2 .= "-Th.jpeg";

    return ( $tiff, $jpeg1, $jpeg2 );
}

__END__
