package Plack::Middleware;
use strict;
use warnings;
use parent qw/Class::Accessor::Fast/;
use Carp ();
use Plack::Util;
use overload '&{}' => sub { shift->to_app(@_) }, fallback => 1;

__PACKAGE__->mk_accessors(qw/app/);

sub import {
    my $class = shift;
    if (@_) {
        Carp::carp("use Plack::Middleware qw(Foo) is deprecated. See perldoc Plack::Builder");
    }
}

sub wrap {
    my($self, $app, @args) = @_;
    if (ref $self) {
        $self->{app} = $app;
    } else {
        $self = $self->new({ app => $app, @args });
    }
    return $self->to_app;
}

sub to_app {
    my $self = shift;
    return sub { $self->call(@_) };
}

sub response_cb {
    my($self, $res, $cb) = @_;

    my $body_filter = sub {
        my($cb, $res) = @_;
        my $filter_cb = $cb->($res);
        # If response_cb returns a callback, treat it as a $body filter
        if (defined $filter_cb && ref $filter_cb eq 'CODE') {
            if (defined $res->[2]) {
                my $body    = $res->[2];
                my $getline = ref $body eq 'ARRAY' ? sub { shift @$body } : sub { $body->getline };
                $res->[2] = Plack::Util::inline_object
                    getline => sub { $filter_cb->($getline->()) },
                    close => sub { $body->close if ref $body ne 'ARRAY' };
            } else {
                return $filter_cb;
            }
        }
    };

    if (ref $res eq 'ARRAY') {
        $body_filter->($cb, $res);
        return $res;
    } elsif (ref $res eq 'CODE') {
        return sub {
            my $respond = shift;
            $res->(sub {
                my $res = shift;
                my $filter_cb = $body_filter->($cb, $res);
                if ($filter_cb) {
                    my $writer = $respond->($res);
                    if ($writer) {
                        return Plack::Util::inline_object
                            write => sub { $writer->write($filter_cb->(@_)) },
                            close => sub { $writer->write($filter_cb->(undef)); $writer->close };
                    }
                } else {
                    return $respond->($res);
                }
            });
        };
    }

    return $res;
}

1;

__END__

=head1 NAME

Plack::Middleware - Base class for easy-to-use PSGI middleware

=head1 SYNOPSIS

  package Plack::Middleware::Foo;
  use parent qw( Plack::Middleware );

  sub call {
      my($self, $env) = @_;
      # Do something with $env

      # $self->app is the original app
      my $res = $self->app->($env);

      # Do something with $res
      return $res;
  }

  # then in app.psgi
  use Plack::Builder;

  my $app = sub { ... } # as usual

  builder {
      enable "Plack::Middleware::Foo";
      enable "Plack::Middleware::Bar", %options;
      $app;
  };

=head1 DESCRIPTION

Plack::Middleware is an utility base class to write PSGI
middleware. All you have to do is to inherit from Plack::Middleware
and then implement the callback C<call> method (or C<to_app> method
that would return the PSGI code reference) to do the actual work. You
can use C<< $self->app >> to call the original (wrapped) application.

See L<Plack::Builder> how to actually enable middlewares in your
I<.psgi> application file using the DSL. If you do not like our
builder DSL, you can also use C<wrap> method to wrap your application
with a middleware:

  use Plack::Middleware::Foo;

  my $app = sub { ... };
  $app = Plack::Middleware::Foo->wrap($app, %options);
  $app = Plack::Middleware::Bar->wrap($app, %options);

Note also Plack::Middleware object is callable, by overloading the
code dereference, so:

  package Plack::Middleware::Foo;
  use parent qw(Plack::Middleware);

  sub call { ... }

  my $app = Plack::Middleware::Foo->new;

C<$app> here is an object but can be used as a PSGI application as
well, since C<< $app->($env) >> just calls C<< $app->call($env) >>
internally.

=head1 SEE ALSO

L<Plack> L<Plack::Builder>

=cut
