package Pcore::App::Nginx;

use Pcore -class;
use Pcore::Nginx;

extends qw[Pcore::App::Alien];

has '+name' => ( default => 'nginx' );
has '+ns'   => ( default => 'Pcore::Nginx' );

has nginx_ver => ( is => 'lazy', isa => Str, init_arg => undef );

our $CFG = {
    NGINX => {    #
        NGINX_VER => undef,
    },
};

# CLI
around CLI => sub ( $orig, $self ) {
    my $cli = $self->$orig;

    $cli->{name} = 'nginx';

    return $cli;
};

sub _build_nginx_ver ($self) {
    return $self->cfg->{NGINX}->{NGINX_VER} || $ENV->dist('Pcore-Nginx')->cfg->{NGINX}->{NGINX_VER};
}

# APP
around _build_cfg => sub ( $orig, $self ) {
    return P->hash->merge( $self->$orig, $CFG );
};

around _create_local_cfg => sub ( $orig, $self ) {
    my $local_cfg = {};

    return P->hash->merge( $self->$orig, $CFG, $local_cfg );
};

sub _build_alien_dir ($self) {
    return $self->app_dir . 'nginx-' . $self->nginx_ver . q[/];
}

sub _build_alien_bin_path ($self) {
    return $self->alien_dir . 'sbin/nginx';
}

sub _build_alien_cfg_path ($self) {
    return $self->app_dir . 'nginx.conf';
}

around app_build => sub ( $orig, $self ) {
    $self->$orig;

    if ( -d $self->alien_dir ) {
        $self->_appx_report_warn( q["] . $self->alien_dir . q[" already exists. Remove it manually to rebuild] );
    }
    else {
        state $init = !!require Archive::Tar;

        my $module = {
            nginx_http_auth_request_module => {
                enabled => 0,
                url     => 'http://mdounin.ru/hg/ngx_http_auth_request_module/archive/tip.tar.gz',
            },
            nginx_echo_module => {
                enabled => 0,
                url     => 'https://github.com/agentzh/echo-nginx-module/archive/master.tar.gz',
            },
            nginx_memc_module => {
                enabled => 0,
                url     => 'https://github.com/agentzh/memc-nginx-module/archive/master.tar.gz',
            },
            nginx_headers_more_module => {
                enabled => 0,
                url     => 'https://github.com/agentzh/headers-more-nginx-module/archive/master.tar.gz',
            },
            nginx_substitutions_filter_module => {
                enabled => 0,
                url     => 'https://github.com/yaoweibin/ngx_http_substitutions_filter_module/archive/master.tar.gz',
            },
            nginx_upload_progress_module => {
                enabled => 0,
                url     => 'https://github.com/masterzen/nginx-upload-progress-module/archive/master.tar.gz',
            },
            nginx_push_stream_module => {
                enabled => 0,
                url     => 'https://github.com/wandenberg/nginx-push-stream-module/archive/master.tar.gz',
            },
            nginx_psgi_module => {
                enabled => 0,
                url     => 'https://github.com/yko/ngx_mod_psgi/archive/master.tar.gz',
            },
            nginx_upload_module => {
                enabled => 0,
                url     => 'https://github.com/vkholodkov/nginx-upload-module/archive/master.tar.gz',
            },
        };

        my $build_dir = P->file->tempdir;

        my $chdir_guard = P->file->chdir($build_dir) or die;

        my $cv = AE::cv;

        # nginx
        P->http->get(
            'http://nginx.org/download/nginx-' . $self->nginx_ver . '.tar.gz',
            buf_size    => 1,
            cv          => $cv,
            on_progress => 1,
            on_finish   => sub ($res) {
                Archive::Tar->new( $res->body->path, 1, { extract => 1 } );

                return;
            }
        );

        # pcre
        P->http->get(
            qq[http://netcologne.dl.sourceforge.net/project/pcre/pcre/@{[$ENV->dist('Pcore-Nginx')->cfg->{NGINX}->{PCRE_VER}]}/pcre-@{[$ENV->dist('Pcore-Nginx')->cfg->{NGINX}->{PCRE_VER}]}.tar.gz],
            buf_size    => 1,
            cv          => $cv,
            on_progress => 1,
            on_finish   => sub ($res) {
                Archive::Tar->new( $res->body->path, 1, { extract => 1 } );

                return;
            }
        );

        # third party modules
        for my $module_name ( grep { $module->{$_}->{enabled} } keys $module->%* ) {
            P->http->get(
                $module->{$module_name}->{url},
                buf_size    => 1,
                cv          => $cv,
                on_progress => 1,
                on_finish   => sub ($res) {
                    my $tar = Archive::Tar->new( $res->body->path );

                    $module->{$module_name}->{path} = substr [ $tar->list_files ]->[0], 0, index [ $tar->list_files ]->[0], q[/];

                    $tar->extract;

                    return;
                }
            );
        }

        $cv->recv;

        # build
        my $res = try {
            P->file->chdir( $build_dir . 'nginx-' . $self->nginx_ver );

            P->pm->run_check(
                join q[ ],    #
                './configure',
                '--prefix=' . $self->alien_dir,
                '--with-ld-opt="-Wl,-rpath,/usr/local/lib64 -L /usr/local/lib64"',
                '--with-http_ssl_module',
                '--with-http_v2_module',    # starting from nginx v1.9.5, for older versions use --with-http_spdy_module
                '--with-http_geoip_module',
                '--with-http_realip_module',
                '--with-http_addition_module',
                '--with-http_sub_module',
                '--with-http_gzip_static_module',
                '--with-http_perl_module',
                '--with-pcre=' . $build_dir . 'pcre-' . $ENV->dist('Pcore-Nginx')->cfg->{NGINX}->{PCRE_VER},
                '--with-pcre-jit',
                map { '--add-module=' . $build_dir . $module->{$_}->{path} } grep { $module->{$_}->{enabled} } keys $module->%*,
            );

            P->pm->run_check( 'make', q[-j] . P->sys->cpus_num );

            P->pm->run_check(qw[make install]);

            return 1;
        }
        catch {
            my $e = shift;

            return 0;
        };

        unless ($res) {
            P->file->rmtree( $self->alien_dir );

            $self->_appx_report_fatal(qq[Error building application "$@"]);
        }
    }

    P->file->mkpath( $self->app_dir . 'vhosts' );    # create vhosts directory

    return;
};

around app_deploy => sub ( $orig, $self ) {
    $self->$orig;

    return;
};

around app_test => sub ( $orig, $self ) {
    $self->$orig;

    say 'Config test';

    $self->generate_alien_cfg;

    my $out = P->pm->run_capture( $self->alien_bin_path, '-t', '-c', $self->alien_cfg_path );

    say $LF, $out;

    if ( $out =~ /test\hfailed/sm ) {
        say 'FAILED';

        exit 1;
    }
    else {
        say 'OK';
    }

    return;
};

sub generate_alien_cfg ($self) {
    my $cfg;

    # TODO PCORE-19

    my $params = {
        app_name           => $self->name,
        app_dir            => $self->app_dir,
        alien_dir          => $self->alien_dir,
        log_dir            => $ENV->{LOG_DIR},
        vhosts_dir         => $self->app_dir . 'vhosts',
        geoip_country_path => $ENV->share->get('/data/geoip_country.dat'),
        geoip_city_path    => $ENV->share->get('/data/geoip_city.dat'),
        ssl                => 0,                                             # $self->openssl->is_enabled, # TODO
    };

    $cfg = P->tmpl( type => 'text' )->render( 'nginx/server.nginx', $params );

    $self->store_alien_cfg($cfg);

    return;
}

sub master_proc ($self) {

    # плавное завершение
    $SIG->{QUIT} = AE::signal QUIT => sub {
        P->log->debug('SIGQUIT received');

        $self->term_state(1);

        kill 'QUIT', $self->alien_pid;    ## no critic qw[InputOutput::RequireCheckedSyscalls]
    };

    # изменение конфигурации, обновление изменившейся временной зоны (только для FreeBSD и Linux), запуск новых рабочих процессов с новой конфигурацией, плавное завершение старых рабочих процессов
    $SIG->{HUP} = AE::signal HUP => sub {
        P->log->debug('SIGHUP received');

        kill 'HUP', $self->alien_pid;     ## no critic qw[InputOutput::RequireCheckedSyscalls]
    };

    # переоткрытие лог-файлов
    $SIG->{USR1} = AE::signal USR1 => sub {
        P->log->debug('SIGUSR1 received');

        kill 'USR1', $self->alien_pid;    ## no critic qw[InputOutput::RequireCheckedSyscalls]
    };

    # обновление исполняемого файла
    $SIG->{USR2} = AE::signal USR2 => sub {
        P->log->debug('SIGUSR2 received ');

        kill ' USR2 ', $self->alien_pid;    ## no critic qw[InputOutput::RequireCheckedSyscalls]
    };

    # плавное завершение рабочих процессов
    $SIG->{WINCH} = AE::signal WINCH => sub {
        P->log->debug(' SIGWINCH received ');

        kill ' WINCH ', $self->alien_pid;    ## no critic qw[InputOutput::RequireCheckedSyscalls]
    };

    return;
}

sub alien_proc ($self) {
    $self->generate_alien_cfg;

    exec $self->alien_bin_path, q[-c], $self->alien_cfg_path or die;
}

1;
## -----SOURCE FILTER LOG BEGIN-----
##
## PerlCritic profile "pcore-script" policy violations:
## ┌──────┬──────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
## │ Sev. │ Lines                │ Policy                                                                                                         │
## ╞══════╪══════════════════════╪════════════════════════════════════════════════════════════════════════════════════════════════════════════════╡
## │    3 │ 136, 175             │ References::ProhibitDoubleSigils - Double-sigil dereference                                                    │
## └──────┴──────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
##
## -----SOURCE FILTER LOG END-----
__END__
=pod

=encoding utf8

=head1 NAME

Pcore::App::Nginx - Pcore nginx application

=head1 SYNOPSIS

    use Pcore::App::Nginx;

    Pcore::App::Nginx->new->run;

=head1 DESCRIPTION

Additional signals supported:

    QUIT  - graceful shutdown (SIGQUIT)
    HUP   - graceful reloading worker processes, reload config (SIGHUP)
    USR1  - re-opening log files (SIGUSR1)
    USR2  - upgrading an executable file (SIGUSR2)
    WINCH - graceful shutdown of worker processes (SIGWINCH)

=cut
