#!/usr/bin/perl

use strict;
use warnings;

use threads;
use IO::Socket::UNIX;
use File::Copy 'cp';
use IO::All;
use Archive::Zip;

use Email::MIME::Creator;
use Email::Sender::Simple 'sendmail';

use File::Basename;

use constant MAX_RUNNING => 10;
use constant SOCKET_FILE => '/tmp/cgiauto.socket';
use constant EMAIL_FROM  => 'autocgi@there.com';

$ENV{PATH}      = $ENV{PATH}.":/usr/local/bin";

my $peuga = IO::Socket::UNIX->new(Type  => SOCK_DGRAM,
                                  Local => SOCKET_FILE);

die ( "Error in socket creation:$!$@\n") unless $peuga;
my $TASKID = 0;

## HACK TEMPORARIO
## `chmod a+rw /tmp/cgiauto.socket`; # infelizmente as constantes nao sao interpolaveis
chmod 0666 , SOCKET_FILE;

$SIG{INT} = sub{
    print STDERR "Shutting Down...\n";

    # Close socket and remove its file under /tmp
    $peuga->close;
    unlink SOCKET_FILE;

    # detach running threads so that they end silently
    $_->detach for threads->list(threads::running);
    # join finished threads
    $_->join   for threads->list(threads::joinable);

    exit;
};

while (1) {
    my $running = scalar(threads->list());
    my $buffer;

    if ($running < MAX_RUNNING) {
        $peuga->recv($buffer, 1024);
        if ($buffer) {
            my $hash = eval("my ". $buffer) // {};
            threads->create( \&process, $hash, ++$TASKID );
        }
    }

    $_->join for threads->list(threads::joinable);
}


sub process {
    my ($struct, $taskid) = @_;

    ######## STRUCT SPECS ##################################################
    #-----=----------------------------------------------------------------#
    #     FIELD    | OBRIG | DESCRIPTION                                   #
    #----=-----------------------------------------------------------------#
    # command      |  SIM  | Comando a ser executado                       #
    # output_file  |  SIM  | String ou ArrayRef com ficheiros              #
    #              |       | Se ArrayRef, zipar e meter zip para download  #
    # local_publish|  NAO  | Onde colocar os ficheiros para download       #
    # web_publish  |  NAO  | Path web onde os ficheiros ficam disponiveis  #
    # work_dir     |  NAO  | Directoria de trabalaho, /tmp by default      #
    # email_to     |  SIM  | Para onde enviar resultado                    #
    # email_from   |  NAO  | Email de origem dos emails                    #
    # email_subject|  NAO  | Subject a colocar no email                    #
    # email_body   |  NAO  | Template para o email                         #
    #              |       | [% web_publish %] e todas outras as variáveis #
    #              |       | definidas nesta tabela são interpoladas       #
    # email_attach |  NAO  | Boolean. Defaults to false.                   #
    # content_type |  NAO  | defaults to 'text/plain' or zip...            #
    #----------------------------------------------------------------------#
    ########################################################################

    print STDERR "[$taskid] Invalid task...\n" and return unless exists $struct->{command} and
      exists $struct->{output_file} and exists $struct->{email_to};

    print STDERR "[$taskid] Starting task [command: $struct->{command}]\n";

    $struct->{work_dir}      ||= '/tmp/';
    $struct->{email_from}    ||= EMAIL_FROM;
    $struct->{email_subject} ||= "Your batch job is finished!";
    $struct->{content_type}  ||= "text/plain";

    print STDERR "[$taskid] chdir $struct->{work_dir}\n";
    chdir $struct->{work_dir};

    # /!\ TAKE CARE! /!\
    print STDERR "[$taskid] executing command\n";
    system $struct->{command};

    if (ref($struct->{output_file}) eq "ARRAY") {
        print STDERR "[$taskid] Requested a zip as output. Creating it...\n";
        my $zip = Archive::Zip->new();
        for my $file (@{$struct->{output_file}}) {
            next unless -f $file;
            my ($newfile) = fileparse($file);
            $zip->addFile($file, $newfile);
        }
        my ($fh, $name) = Archive::Zip::tempFile($struct->{work_dir});
        $zip->writeToFileHandle($fh);
        $struct->{output_filename} = fileparse($name);
        $struct->{output_file} = "$struct->{work_dir}/$struct->{output_filename}";
        $struct->{content_type} = "application/zip";
    } else {
        $struct->{output_filename} = $struct->{output_file};
        $struct->{output_filename} =~ s!.*/!!;
    }

    if ($struct->{web_publish}) {
        print STDERR "[$taskid] Requested web publish. Moving output file...\n";
        cp($struct->{output_file},
           "$struct->{local_publish}/$struct->{output_filename}") 
           or warn("Error: $!\n");
    }


    # Create the basic Email
    my $email = Email::MIME->create(header => [
                                               To      => $struct->{email_to},
                                               From    => $struct->{email_from},
                                               Subject => $struct->{email_subject},
                                              ]
                                   );

    # if we should prepare an attach
    if ($struct->{email_attach}) {
        print STDERR "[$taskid] Email with attachment requested...\n";

        # Create the attach MIME part
        my $aux = $struct->{output_file};
        $aux = "$struct->{work_dir}/$aux" if($aux !~ m!^/!);
        my $attach = Email::MIME->create
          (
           attributes => {
                          filename     => $struct->{output_file},
                          disposition  => "attachment",
                          content_type => $struct->{content_type},
                          encoding     => 'base64',
                          name         => $struct->{output_filename},
                         },
           body => io($aux)->all,
          );

        # Check what kind of message to present
        if ($struct->{web_publish}) {
            $email->body_str_set(email_download_and_attach_text($struct));
            $email->parts_add( [ $attach ]);
        } else {
            $email->body_str_set(email_attach_text($struct));
            $email->parts_add( [ $attach ]);
        }
    } else {
        $email->body_str_set(email_download_text($struct));
    }

    print STDERR "[$taskid] Sending email...\n";
    sendmail($email);
    print STDERR "[$taskid] Ending task\n";
}


sub email_download_text {
    my $x = shift;

    $x->{email_body} ||= <<"EOMAIL";
Dear user,

Your batch job finished. You can download it from
[% web_publish %]/[% output_filename %].
Note that it might get removed if you do not download it for
seven days.
EOMAIL

    $x->{email_body} =~ s/\[%\s*([^ %]+)\s*%\]/$x->{$1} || ""/ge;
    return $x->{email_body};
}


sub email_download_and_attach_text {
    my $x = shift;

    $x->{email_body} ||= <<"EOMAIL";
Dear user,

Your batch job finished. You can download it from
[% web_publish %]/[% output_filename %].
Note that it might get removed if you do not download it for
seven days.
You can find this same file in attach.
EOMAIL

    $x->{email_body} =~ s/\[%\s*([^ %]+)\s*%\]/$x->{$1} || ""/ge;
    return $x->{email_body};
}


sub email_attach_text {
    my $x = shift;

    $x->{email_body} ||= <<"EOMAIL";
Dear user,

Your batch job finished. You can check it in attach.
EOMAIL

    $x->{email_body} =~ s/\[%\s*([^ %]+)\s*%\]/$x->{$1} || ""/ge;
    return $x->{email_body};
}
