#!/usr/bin/perl

$|=1;

use strict;
use vars qw(%ENV %ARGV);
use CGI qw(:cgi standart);
use Data::Dump qw(dump);
use POSIX ":sys_wait_h";
use File::Basename;
use Digest::MD5;
use MIME::Base64 qw(encode_base64 decode_base64);
use JSON;

#use lib dirname(__FILE__) . '/items';

my $now_cached;
my $now_tc_cached;
sub replace_keywords
{
    my $src = shift;

    {
        my $s;
        my $r;

        $s = "<NOW/>";
        $now_cached = sprintf("%04d%02d%02d_%02d%02d%02d",
            sub {($_[5]+1900, $_[4]+1, $_[3], $_[2], $_[1], $_[0])}->(localtime))
            unless defined($now_cached);
        $r = $now_cached;

        $src =~ s/$s/$r/g;

        $s = "<NOW_TC/>";
        $now_tc_cached = sprintf("%02d:%02d:%02d:00",
            sub {($_[2], $_[1], $_[0])}->(localtime))
            unless defined($now_tc_cached);
        $r = $now_tc_cached;

        $src =~ s/$s/$r/g;

    };


    return $src;
};

sub path_recreate
{
    my $src = shift;

    my $i = rindex($src, '/');

    if(-1 != $i)
    {
        my $p = substr($src, 0, $i);
        `mkdir -p -m 777 $p`;
    };

    return $src;
};

sub process_path
{
    my $src = shift;

    # file protocol
    if($src =~ m/^(file|foo):\/\//)
    {
        $src  =~ s/^(file|foo):\/\///g;
        $src = replace_keywords($src);
        path_recreate($src);
        return $src;
    };

    return $src
        if ($src =~ m/^(udp|ftp|http|https|rtmp|rtmps|file|icecast|srt|rtp):\/\//);

    $src = '/TheCore/data/' . replace_keywords($src);
    path_recreate($src);

    return $src;
};

sub args4ffmpeg
{
    my ($args, $body, $overrideurl) = @_;

    my $res = decode_json($body);

#    push(@{$args}, "-v", "verbose");
#    push(@{$args}, "-v", "debug");

    return undef unless defined($res->{'input'});

    # INPUT
    if(defined($res->{'input'}->{'url'}))
    {
        push(@{$args}, "-i", defined($overrideurl) ? $overrideurl : $res->{'input'}->{'url'});
    }
    elsif(defined($res->{'input'}->{'local'}))
    {
        # build
        my $local = '/TheCore/data/' . $res->{'input'}->{'local'};

        # check if it directory
        if(-d $local)
        {
            # read directory content
            my @files = ();
            opendir my $dir, $local or die "Cannot open directory: $!";
            while (my $file = readdir($dir))
            {
                next if ($file =~ m/^\./);

                my $file_full = $local . '/' . $file;

                next unless (-f $file_full);

                push(@files, $file_full);
            };
            closedir $dir;

            # create concat file
            my $concat = "/tmp/TheCoreBridge-$$.concat";
            open(DATA, ">", $concat);
            map { print DATA sprintf("file '%s'\n", $_); } sort @files;
            close(DATA);

            # push arguments
            push(@{$args}, "-f", "concat");
            push(@{$args}, "-safe", "0");
            push(@{$args}, "-i", $concat);
        }
        else
        {
            push(@{$args}, "-i", $local);
        };
    }
    elsif(defined($res->{'input'}->{'thecoreelements'}))
    {
        push(@{$args}, "-f", "thecoreelements");
        push(@{$args}, "-nv12", "1")
            if(defined($res->{'input'}->{'nv12'}));
        push(@{$args}, "-channels_cnt", "8")
            if(defined($res->{'input'}->{'channels_cnt_8'}));
        push(@{$args}, "-deint", "1")
            if(defined($res->{'input'}->{'deint'}));
        push(@{$args}, "-i", '/TheCore/runtime/matrix0@' . $res->{'input'}->{'thecoreelements'});
    }
    elsif(defined($res->{'input'}->{'ndi'}))
    {
        push(@{$args}, "-f", "libndi_newtek", "-i", $res->{'input'}->{'ndi'});
    }
    elsif(defined($res->{'input'}->{'decklink'}))
    {
        push(@{$args}, "-f", "decklink");
        push(@{$args}, "-format_code", $res->{'input'}->{'mode'});
        push(@{$args}, "-channels", $res->{'input'}->{'decklink_audio_channels'})
            if(defined($res->{'input'}->{'decklink_audio_channels'}) && $res->{'input'}->{'decklink_audio_channels'} ne '');
        push(@{$args}, "-i", $res->{'input'}->{'decklink'});
#        push(@{$args}, "-f", "decklink", "-i", $res->{'input'}->{'decklink'} . '@' . $res->{'input'}->{'mode'});
    }
    else
    {
        return undef;
    };

    # FILTER COMPLEX
    if(defined($res->{'filters_complex'}))
    {
        foreach my $filter_complex (@{$res->{'filters_complex'}})
        {
            push(@{$args}, "-filter_complex", $filter_complex);
        };
    };

    # OUTPUTS
    if(defined($res->{'outputs'}))
    {
        foreach my $output (@{$res->{'outputs'}})
        {
            if(defined($output->{'video'}))
            {
                my $s = $output->{'video'};
                my $c = $s->{'codec'};
                my $f_global_header = 0;

                push(@{$args}, "-map", $s->{'map'})
                    if(defined($s->{'map'}) && ('' ne $s->{'map'}));

                if('none' eq $c)
                {
                    push(@{$args}, "-vn");
                }
                elsif('copy' eq $c)
                {
                    push(@{$args}, "-vcodec", "copy");

                    push(@{$args}, "-bsf:v", $s->{'bsf:v'})
                        if(defined($s->{'bsf:v'}) && ('' ne $s->{'bsf:v'}));
                }
                else
                {
                    push(@{$args}, "-vf", $s->{'vf'})
                        if(defined($s->{'vf'}) && ('' ne $s->{'vf'}));

                    if('empty' eq $c)
                    {
                        push(@{$args}, "-vcodec", "wrapped_avframe");
                    }
                    else
                    {
                        push(@{$args}, "-vcodec", $c);
                    };

                    foreach my $f (keys %{$s})
                    {
                        # stop list
                        next if ($f eq 'vf' || $f eq 'codec' || $f eq 'bsf:v' || $f eq 'map');

                        # ignore empty
                        next if ('' eq $s->{$f});

                        # save value
                        if ($f eq 'flags:v')
                        {
                            push(@{$args}, "-" . $f, $s->{$f} . '+global_header');
                            $f_global_header++;
                        }
                        else
                        {
                            push(@{$args}, "-" . $f, $s->{$f});
                        };
                    };
                };

                push(@{$args}, '-flags:v', '+global_header')
                    if($f_global_header == 0);
            };

            if(defined($output->{'audio'}))
            {
                my $s = $output->{'audio'};
                my $c = $s->{'codec'};

                push(@{$args}, "-map", $s->{'map'})
                    if(defined($s->{'map'}) && ('' ne $s->{'map'}));

                if('none' eq $c)
                {
                    push(@{$args}, "-an");
                }
                elsif('empty' eq $c)
                {
                    push(@{$args}, "-af", $s->{'af'})
                        if(defined($s->{'af'}) && ('' ne $s->{'af'}));
                }
                elsif('copy' eq $c)
                {
                    push(@{$args}, "-acodec", "copy");

                    push(@{$args}, "-bsf:a", $s->{'bsf:a'})
                        if(defined($s->{'bsf:a'}) && ('' ne $s->{'bsf:a'}));
                }
                else
                {
                    push(@{$args}, "-af", $s->{'af'})
                        if(defined($s->{'af'}) && ('' ne $s->{'af'}));

                    push(@{$args}, "-acodec", $c);

                    my $flds = ["ab", "ac", "ar"];

                    foreach my $f (@{$flds})
                    {
                        push(@{$args}, "-" . $f, $s->{$f})
                            if(defined($s->{$f}) && ('' ne $s->{$f}));
                    };
                };
            };

            if(defined($output->{'audios'}))
            {
                my $dst_stream = 0;

                foreach my $s (@{$output->{'audios'}})
                {
                    my $ax = ":a:" . $dst_stream;

                    my $c = $s->{'codec'};

                    push(@{$args}, "-map", $s->{'map'})
                        if(defined($s->{'map'}) && ('' ne $s->{'map'}));

                    if('none' eq $c)
                    {
                        push(@{$args}, "-an");
                    }
                    elsif('empty' eq $c)
                    {
                        push(@{$args}, "-filter$ax", $s->{'af'})
                            if(defined($s->{'af'}) && ('' ne $s->{'af'}));
                    }
                    elsif('copy' eq $c)
                    {
                        push(@{$args}, "-c$ax", "copy");

                        push(@{$args}, "-bsf$ax", $s->{'bsf:a'})
                            if(defined($s->{'bsf:a'}) && ('' ne $s->{'bsf:a'}));
                    }
                    else
                    {
                        push(@{$args}, "-filter$ax", $s->{'af'})
                            if(defined($s->{'af'}) && ('' ne $s->{'af'}));

                        push(@{$args}, "-c$ax", $c);

                        my $flds = ["ab", "ac", "ar"];

                        foreach my $f (@{$flds})
                        {
                            push(@{$args}, "-" . $f . "$ax", $s->{$f})
                                if(defined($s->{$f}) && ('' ne $s->{$f}));
                        };
                    };

                    $dst_stream++;
                };

            };

#            push(@{$args}, "-flags", "+global_header");

            if(defined($output->{'formats'}))
            {
                my @F = ();
                my $c = 0;

                foreach my $f (@{$output->{'formats'}})
                {
                    if('thecoreelements' eq $f->{'f'})
                    {
                        $f->{'y'} = '/TheCore/runtime/matrix0@' . $f->{'y'};
                    }
                    elsif(($f->{'f'} ne 'decklink') && ($f->{'f'} ne 'libndi_newtek'))
                    {
                        # append path
                        $f->{'y'} = process_path($f->{'y'});

                        # override hls_segment_filename if used
                        $f->{'hls_segment_filename'} = process_path($f->{'hls_segment_filename'})
                            if(defined($f->{'hls_segment_filename'}) && $f->{'hls_segment_filename'} ne '');

                        # override hls_segment_filename if used
                        $f->{'segment_list'} = process_path($f->{'segment_list'})
                            if(defined($f->{'segment_list'}) && $f->{'segment_list'} ne '');

                        # override timecode if used
                        if(defined($f->{'timecode'}) && $f->{'timecode'} ne '')
                        {
                            # replace a key
                            $f->{'timecode'} = replace_keywords($f->{'timecode'});
                        }

                    };

                    my $ff =
                    {
                        f => $f->{'f'},
                        y => $f->{'y'},
                        a => {}
                    };

                    foreach my $k (keys %{$f})
                    {
                        next if (('f' eq $k) || ('y' eq $k) || ('protocol_params' eq $k));
                        next if ('' eq $f->{$k});
                        $ff->{'a'}->{$k} = $f->{$k};
                    };

                    if(defined($f->{'protocol_params'}))
                    {
                        foreach my $k (keys %{$f->{'protocol_params'}})
                        {
                            my $v = $f->{'protocol_params'}->{$k};
                            next if ('' eq $v);
                            $ff->{'a'}->{$k} = $v;
                        };
                    };

                    push(@F, $ff);

                    $c++;
                };

                if($c > 1)
                {
                    my @tee = ();

                    for(my $i = 0; $i < $c; $i++)
                    {
                        my @a = ();

                        push(@a, "f=" . $F[$i]->{'f'});

                        foreach my $k (keys %{$F[$i]->{'a'}})
                        {
                            push(@a, $k . "=" . $F[$i]->{'a'}->{$k});
                        };

                        push(@a, "bsfs/a=aac_adtstoasc")
                            if(($F[$i]->{'f'} eq 'flv') || ($F[$i]->{'f'} eq 'mp4'));

                        push(@tee, "[" . join(':', @a) . "]" . $F[$i]->{'y'});
                    };

                    push(@{$args}, "-f", "tee", join('|', @tee));
                }
                else
                {
                    my $f = shift (@{$output->{'formats'}});

                    push(@{$args}, "-f", $f->{'f'});

                    foreach my $k (keys %{$f})
                    {
                        next if (('f' eq $k) || ('y' eq $k) || ('protocol_params' eq $k));
                        next if ('' eq $f->{$k});

                        if(rindex($k, '@') < 0)
                        {
                            push(@{$args}, "-" . $k, $f->{$k});
                        }
                        else
                        {
                            my @K = split('@', $k);

                            push(@{$args}, "-" . $K[0], $K[1] . '=' . $f->{$k} . '');
                        };
                    };

                    push(@{$args}, "-muxdelay", "0")
                        if ($f->{'f'} eq 'thecoreelements');
#                    push(@{$args}, "-shortest");
#                    push(@{$args}, "-muxpreload", "12");

                    if(defined($f->{'protocol_params'}))
                    {
                        foreach my $k (keys %{$f->{'protocol_params'}})
                        {
                            my $v = $f->{'protocol_params'}->{$k};
                            next if ('' eq $v);
                            push(@{$args}, "-" . $k, $v);
                        };
                    };

                    push(@{$args}, "-y", $f->{'y'});
                };
            }
        };
    };

    return $args;
};

my @args;
my $pid;
my $body;

BEGIN
{
    if($ENV{DOCUMENT_ROOT})
    {
        $SIG{PIPE} = sub
        {
            kill 15, $pid;
            sleep 5;
            kill 9, $pid;
            exit 0;
        };

        $SIG{TERM} = sub
        {
            kill 15, $pid;
            sleep 5;
            kill 9, $pid;
            exit 0;
        };

        $SIG{INT} = sub
        {
            kill 15, $pid;
            sleep 5;
            kill 9, $pid;
            exit 0;
        };
    };
};

if($ENV{DOCUMENT_ROOT})
{
    my $cgi = new CGI;
    $body = $cgi->param('body');

    print $cgi->header
    (
        -access_control_allow_origin => "*",
        -charset        =>      "utf-8",
        -type           =>      "text/plain",
        -expires        =>      "-1d"
    );
}
else
{
    # load from file
    my $filename = ($ARGV[0] eq "--nodemonize") ? $ARGV[1] : $ARGV[0];

    if('/' eq substr($filename, 0, 1))
    {
        $body = do
        {
            open(my $json_fh, "<:encoding(UTF-8)", "$filename") or die("Can't open \"$filename\": $!\n");
            local $/;
            <$json_fh>
        };
    }
    else
    {
        $body = decode_base64($filename);
    };
};

# try to detect if it streamlink
my $res = decode_json($body);
if(defined($res) && defined($res->{'input'}) && defined($res->{'input'}->{'streamlink'}))
{
    # save link
    my $streamlink = $res->{'input'}->{'streamlink'};

    # overrride
    $res->{'input'}->{'streamlink'} = undef;
    $res->{'input'}->{'url'} = '+';

    # create new config
    $body = encode_base64(encode_json($res), '');

    # create streamlink arguments list
    push(@args, '/usr/local/bin/streamlink');
###    if(defined($res->{'input'}->{'streamlink_twitch-oauth-token'}))
###    {
###        push(@args, '--twitch-oauth-token');
###        push(@args, $res->{'input'}->{'streamlink_twitch-oauth-token'});
###    };
    push(@args, '--ringbuffer-size');
    push(@args, '128M');
    push(@args, '--stream-segment-threads');
    push(@args, '8');
    push(@args, '--player-fifo');
#    push(@args, '--player-passthrough');
#    push(@args, 'hls,http,rtmp');
    push(@args, '--http-no-ssl-verify');
    push(@args, '--ffmpeg-ffmpeg');
    push(@args, '/usr/local/enctools/bin/ffmpeg');
    push(@args, '--loglevel');
    push(@args, 'debug');
    push(@args, '-p');
    push(@args, __FILE__);
    push(@args, '-a');
    push(@args, "$body --overrideurl");
    push(@args, $streamlink);
    push(@args, 'best');
}
elsif(defined($res) && defined($res->{'input'}) && defined($res->{'input'}->{'dvblink'}))
{
    # save link
    my $dvblink = $res->{'input'}->{'dvblink'};

    # overrride
    $res->{'input'}->{'dvblink'} = undef;
    $res->{'input'}->{'url'} = '+';

    # create new config
    $body = encode_base64(encode_json($res), '');

    # create channel file
    my $channel = "/tmp/TheCoreBridge-$$.channel";
    my $channel_dev_idx;
    open(DATA, ">", $channel);
    print DATA "[DEMO]\n";
    foreach my $key (keys %{$dvblink})
    {
        # save device index
        $channel_dev_idx = $dvblink->{$key} if($key eq 'DEV_IDX');

        # skip it - use default channel name
        next if ($key eq 'name' || $key eq 'DEV_IDX');

        if($key eq 'FREQUENCY' or $key eq 'SYMBOL_RATE')
        {
            $dvblink->{$key} = int($dvblink->{$key}) * 1000;
        };

        print DATA sprintf("\t%s = %s\n", $key, $dvblink->{$key});
    };
    close(DATA);

    # executable
    push(@args, '/opt/TheCore/bin/TheCoreBridge.dvblink');

    # create dvblink arguments list
    push(@args, '-d');
    push(@args, $channel_dev_idx);

    push(@args, '-c');
    push(@args, $channel);

    push(@args, '-z');
    push(@args, '/usr/local/v4l-utils/bin/dvbv5-zap');

    push(@args, '-p');
    push(@args, __FILE__);

    push(@args, '-a');
    push(@args, $body);
}
else
{
    my $overrideurl;

    # manually find arg
    $overrideurl = $ARGV[1] if(defined($ARGV[1]) && defined($ARGV[0]) && ('--overrideurl' eq $ARGV[0]));
    $overrideurl = $ARGV[2] if(defined($ARGV[2]) && defined($ARGV[1]) && ('--overrideurl' eq $ARGV[1]));
    $overrideurl = $ARGV[3] if(defined($ARGV[3]) && defined($ARGV[2]) && ('--overrideurl' eq $ARGV[2]));
    $overrideurl = $ARGV[4] if(defined($ARGV[4]) && defined($ARGV[3]) && ('--overrideurl' eq $ARGV[3]));

    # parse
    push(@args, '/usr/local/enctools/bin/ffmpeg');
    args4ffmpeg(\@args, $body, $overrideurl);
};

if($ENV{DOCUMENT_ROOT})
{
    delete $ENV{DOCUMENT_ROOT};

    if (!defined($pid = fork))
    {
        die "Could not fork! $!";
    }
    elsif ($pid)
    {
        # parent - clean up when child exits
        while(waitpid($pid, WNOHANG) != -1)
        {
            sleep(1);
            print "\0";
        };
        print "\n\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n";

        exit(0);
    }
    else
    {
        open STDERR, '>&STDOUT';

        print "-------------------- source arguments ----------------------\n\n";
        print "$body\n\n";

        print "-------------------- application argument ------------------\n\n";
        print dump(\@args) . "\n\n";

        print "-------------------- application output --------------------\n\n";
        exec { $args[0] } @args;

        # child - does the processing
        print "Processing...\n\n";
        sleep(5);
        print "Finished...\n\n";
        exit(0);
    }
}
else
{
    exec { $args[0] } @args;
};
