The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package HTTP::Router;

use 5.008_001;
use strict;
use warnings;
use Hash::AsObject;
use List::MoreUtils 'part';
use Scalar::Util ();
use HTTP::Router::Route;

our $VERSION = '0.05';

sub new {
    my $class = shift;
    return bless { routes => [], matcher => undef }, $class;
}

sub routes {
    my $self = shift;
    @{ $self->{routes} };
}

sub add_route {
    my ($self, $route, @args) = @_;

    unless (Scalar::Util::blessed($route)) {
        $route = HTTP::Router::Route->new(path => $route, @args);
    }

    push @{ $self->{routes} }, $route;
}

sub reset {
    my $self = shift;
    $self->thaw->{routes}  = [];
    $self;
}

sub freeze {
    my $self = shift;
    $self->{matcher} = $self->_build_matcher;
    $self;
}

sub thaw {
    my $self = shift;
    $self->{matcher} = undef;
    $self;
}

sub is_frozen {
    my $self = shift;
    defined $self->{matcher};
}

sub _build_matcher {
    my $self = shift;

    my ($path_routes, $capture_routes) =
        part { scalar $_->templates->expansions > 0 } $self->routes;

    return sub {
        my $req   = shift;
        my $parts = $req->path =~ tr!/!/!;

        # path
        for my $route (grep { $_->parts == $parts } @$path_routes) {
            my $match = $route->match($req) or next;
            return $match; # return if found path route
        }

        # capture
        for my $route (grep { $_->parts <= $parts } @$capture_routes) {
            my $match = $route->match($req) or next;
            return $match;
        }

        return;
    };
}

sub match {
    my $self = shift;

    my $req = Scalar::Util::blessed($_[0])
        ? $_[0]
        : Hash::AsObject->new(path => $_[0], %{ $_[1] || {} });

    if ($self->is_frozen) {
        return $self->{matcher}->($req);
    }
    else {
        for my $route ($self->routes) {
            my $match = $route->match($req) or next;
            return $match;
        }

        return;
    }
}

sub route_for {
    my $self = shift;

    if (my $match = $self->match(@_)) {
        return $match->route;
    }

    return;
}

1;

=for stopwords inline

=head1 NAME

HTTP::Router - Yet Another Path Router for HTTP

=head1 SYNOPSIS

  use HTTP::Router;

  my $router = HTTP::Router->new;

  my $route = HTTP::Router::Route->new(
      path       => '/',
      conditions => { method => 'GET' },
      params     => { controller => 'Root', action => 'index' },
  );
  $router->add_route($route);
  # or
  $router->add_route('/' => (
      conditions => { method => 'GET' },
      params     => { controller => 'Root', action => 'index' },
  ));

  # GET /
  my $match = $router->match($req);
  $match->params;  # { controller => 'Root', action => 'index' }
  $match->uri_for; # '/'

=head1 DESCRIPTION

HTTP::Router provides a way of constructing routing tables.

If you are interested in a Merb-like constructing way,
please check L<HTTP::Router::Declare>.

=head1 METHODS

=head2 new

Returns a HTTP::Router object.

=head2 add_route($route)

=head2 add_route($path, %args)

Adds a new route.
You can specify L<HTTP::Router::Route> object,
or path string and options pair.

example:

  my $route = HTTP::Router::Route->new(
      path       => '/',
      conditions => { method => 'GET' },
      params     => { controller => 'Root', action => 'index' },
  );

  $router->add_route($route);

equals to:

  $router->add_route('/' => (
      conditions => { method => 'GET' },
      params     => { controller => 'Root', action => 'index' },
  ));

=head2 routes

Returns registered routes.

=head2 reset

Clears registered routes.

=head2 freeze

Creates inline matcher using registered routes.

=head2 thaw

Clears inline matcher.

=head2 is_frozen

Returns true if inline matcher is defined.

=head2 match($req)

Returns a L<HTTP::Router::Match> object that matches a given request.
If no routes match, it returns C<undef>.

=head2 route_for($req)

Returns a L<HTTP::Router::Route> object that matches a given request.
If no routes match, it returns C<undef>.

=head1 AUTHOR

NAKAGAWA Masaki E<lt>masaki@cpan.orgE<gt>

Takatoshi Kitano E<lt>kitano.tk@gmail.comE<gt>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 SEE ALSO

L<HTTP::Router::Declare>, L<HTTP::Router::Route>, L<HTTP::Router::Match>,

L<MojoX::Routes>, L<http://merbivore.com/>,
L<HTTPx::Dispatcher>, L<Path::Router>, L<Path::Dispatcher>

=cut