package Mojolicious::Plugin::WebSocketProxy;

use strict;
use warnings;

use Mojo::Base 'Mojolicious::Plugin';
use Mojo::WebSocketProxy::Backend;
use Mojo::WebSocketProxy::Config;
use Mojo::WebSocketProxy::Dispatcher;

use Log::Any qw($log);

# Other backend types may be available; we default to 'jsonrpc' in the code below
use Mojo::WebSocketProxy::Backend::JSONRPC;

our $VERSION = '0.14';    ## VERSION

sub register {
    my ($self, $app, $config) = @_;

    die 'No base path found!' unless $config->{base_path};

    my $url_setter;
    $url_setter = delete $config->{url} if $config->{url} and ref($config->{url}) eq 'CODE';
    $app->helper(
        call_rpc => sub {
            my ($c, $req_storage) = @_;
            $url_setter->($c, $req_storage) if $url_setter && !$req_storage->{url};
            return $c->forward($req_storage);
        });
    $app->helper(
        wsp_error => sub {
            shift;    # $c
            my ($msg_type, $code, $message, $details) = @_;

            my $error = {
                code    => $code,
                message => $message
            };
            $error->{details} = $details if ref($details) eq 'HASH' && keys %$details;

            if ($details && ref($details) ne 'HASH') {
                $log->debugf("Details in a websocket error must be a hash reference instead of a %s", ref($details));
            }

            return {
                msg_type => $msg_type,
                error    => $error,
            };
        });

    my $r = $app->routes;
    for ($r->under($config->{base_path})) {
        $_->to('Dispatcher#ok', namespace => 'Mojo::WebSocketProxy');
        $_->websocket('/')->to('Dispatcher#open_connection', namespace => 'Mojo::WebSocketProxy');
    }

    my $actions           = delete $config->{actions};
    my $dispatcher_config = Mojo::WebSocketProxy::Config->new;
    $dispatcher_config->init($config);

    if (ref $actions eq 'ARRAY') {
        for (my $i = 0; $i < @$actions; $i++) {
            $dispatcher_config->add_action($actions->[$i], $i);
        }
    } else {
        die 'No actions found!';
    }

    my $default_backend = delete $config->{default_backend} // '';
    if (my $backend_configs = delete $config->{backends}) {
        foreach my $name (keys %$backend_configs) {
            my %args = %{$backend_configs->{$name}};
            my $type = delete($args{type}) // 'jsonrpc';
            my $key  = $default_backend eq $name ? 'default' : $name;
            $dispatcher_config->add_backend($key => Mojo::WebSocketProxy::Backend->backend_instance($type => %args));
        }
    }

    # For backwards compatibility, we always want to add a plain JSON::RPC backend
    my $jsonrpc_backend_key = exists $dispatcher_config->{backends}->{default} ? 'http' : 'default';
    $dispatcher_config->add_backend(
        $jsonrpc_backend_key => Mojo::WebSocketProxy::Backend->backend_instance(
            jsonrpc => url => delete $config->{url},
        ));

    $app->helper(
        wsp_config => sub {
            my $c = shift;
            return $dispatcher_config;
        });

    # Subscribe on notification about termination of worker.
    Mojo::IOLoop->singleton->once(finish => $config->{before_shutdown}) if $config->{before_shutdown};

    return;
}

1;

__END__

=head1 NAME

Mojolicious::Plugin::WebSocketProxy

=head1 SYNOPSYS

    # lib/your-application.pm

    use base 'Mojolicious';

    sub startup {
        my $self = shift;
        $self->plugin(
            'web_socket_proxy' => {
                actions => [
                    ['json_key', {some_param => 'some_value'}]
                ],
                base_path => '/api',
                url => 'http://rpc-host.com:8080/',
            }
        );
   }

Or to manually call RPC server:

    # lib/your-application.pm

    use base 'Mojolicious';

    sub startup {
        my $self = shift;
        $self->plugin(
            'web_socket_proxy' => {
                actions => [
                    [
                        'json_key',
                        {
                            instead_of_forward => sub {
                                shift->call_rpc({
                                    args => $args,
                                    method => $rpc_method, # it'll call 'http://rpc-host.com:8080/rpc_method'
                                    rpc_response_cb => sub {...}
                                });
                            }
                        }
                    ]
                ],
                base_path => '/api',
                url => 'http://rpc-host.com:8080/',
            }
        );
   }

=head1 DESCRIPTION

Using this module you can forward websocket JSON-RPC 2.0 requests to RPC server.
See L<Mojo::WebSocketProxy> for details on how to use hooks and parameters.

=head1 SEE ALSO

L<Mojolicious::Plugin::WebSocketProxy>,
L<Mojo::WebSocketProxy>
L<Mojo::WebSocketProxy::Backend>,
L<Mojo::WebSocketProxy::Dispatcher>
L<Mojo::WebSocketProxy::Config>
L<Mojo::WebSocketProxy::Parser>

=cut