package HTTP::Engine::Middleware::Static;
use HTTP::Engine::Middleware;
use HTTP::Engine::Response;

use MIME::Types;
use Path::Class;
use Cwd;
use Any::Moose 'X::Types::Path::Class';
use Any::Moose '::Util::TypeConstraints';
use File::Spec::Unix;
use HTTP::Date ();

# corece of Regexp
subtype 'HTTP::Engine::Middleware::Static::Regexp'
    => as 'RegexpRef';
coerce 'HTTP::Engine::Middleware::Static::Regexp'
    => from 'Str' => via { qr/$_/ };

has 'regexp' => (
    is       => 'ro',
    isa      => 'HTTP::Engine::Middleware::Static::Regexp',
    coerce   => 1,
    required => 1,
);

has 'docroot' => (
    is       => 'ro',
    isa      => 'Path::Class::Dir',
    coerce   => 1,
    required => 1,
);

has directory_index => (
    is  => 'ro',
    isa => 'Str|Undef',
);

has 'mime_types' => (
    is  => 'ro',
    isa => 'MIME::Types',
    lazy => 1,
    default => sub {
        my $mime_types = MIME::Types->new(only_complete => 1);
        $mime_types->create_type_index;
        $mime_types;
    },
);

has 'is_404_handler' => (
    is      => 'ro',
    isa     => 'Bool',
    default => 1,
);

before_handle {
    my ( $c, $self, $req ) = @_;

    my $re   = $self->regexp;
    my $uri_path = $req->uri->path;
    return $req unless $uri_path && $uri_path =~ /^(?:$re)$/;

    my $docroot = dir($self->docroot)->absolute;
    my $file = do {
        if ($uri_path =~ m{/$} && $self->directory_index) {
            $docroot->file(
                File::Spec::Unix->splitpath($uri_path),
                $self->directory_index
            );
        } else {
            $docroot->file(
                File::Spec::Unix->splitpath($uri_path)
            )
        }
    };

    my $realpath = Cwd::realpath($file->absolute->stringify);
    if ($realpath) {
        # check directory traversal if realpath found
        return HTTP::Engine::Response->new( status => 403, body => 'forbidden') unless $docroot->subsumes($realpath);
    }

    unless ($realpath && -e $file && !-d _) {
        return $req unless $self->is_404_handler;
        return HTTP::Engine::Response->new( status => 404, body => 'not found' );
    }

    my $content_type = 'text/plain';
    if ($file =~ /.*\.(\S{1,})$/xms ) {
        my $mime = $self->mime_types->mimeTypeOf($1);
        $content_type = $mime->type if $mime;
    }

    my $fh = $file->openr;
    die "Unable to open $file for reading : $!" unless $fh;
    binmode $fh;

    my $res = HTTP::Engine::Response->new( body => $fh, content_type => $content_type );
    my $stat = $file->stat;
    $res->header( 'Content-Length' => $stat->size );
    $res->header( 'Last-Modified'  => HTTP::Date::time2str( $stat->mtime ) );
    $res;
};


__MIDDLEWARE__

__END__

=head1 NAME

HTTP::Engine::Middleware::Static - handler for static files

=head1 SYNOPSIS

    my $mw = HTTP::Engine::Middleware->new;
    $mw->install( 'HTTP::Engine::Middleware::Static' => {
        regexp  => qr{^/(robots.txt|favicon.ico|(?:css|js|img)/.+)$},
        docroot => '/path/to/htdocs/',
    });
    HTTP::Engine->new(
        interface => {
            module => 'YourFavoriteInterfaceHere',
            request_handler => $mw->handler( \&handler ),
        }
    )->run();

    # $ GET http//localhost/css/foo.css
    # to get the /path/to/htdocs/css/foo.css

    # $ GET http//localhost/js/jquery.js
    # to get the /path/to/htdocs/js/jquery.js

    # $ GET http//localhost/robots.txt
    # to get the /path/to/htdocs/robots.txt

has multi document root

    my $mw = HTTP::Engine::Middleware->new;
    $mw->install(
        'HTTP::Engine::Middleware::Static' => {
            regexp  => qr{^/(robots.txt|favicon.ico|(?:css|js|img)/.+)$},
            docroot => '/path/to/htdocs/',
        },
        'HTTP::Engine::Middleware::Static' => {
            regexp  => qr{^/foo(/.+)$},
            docroot => '/foo/bar/',
        },
    );
    HTTP::Engine->new(
        interface => {
            module => 'YourFavoriteInterfaceHere',
            request_handler => $mw->handler( \&handler ),
        }
    )->run();

    # $ GET http//localhost/css/foo.css
    # to get the /path/to/htdocs/css/foo.css

    # $ GET http//localhost/robots.txt
    # to get the /path/to/htdocs/robots.txt

    # $ GET http//localhost/foo/baz.html
    # to get the /foo/bar/baz.txt

through only the specific URL to backend

    my $mw = HTTP::Engine::Middleware->new;
    $mw->install( 'HTTP::Engine::Middleware::Static' => {
        regexp  => qr{^/(robots.txt|favicon.ico|(?:css|img)/.+|js/(?!dynamic).+)$},
        docroot => '/path/to/htdocs/',
    });
    HTTP::Engine->new(
        interface => {
            module => 'YourFavoriteInterfaceHere',
            request_handler => $mw->handler( \&handler ),
        }
    )->run();

    # $ GET http//localhost/js/jquery.js
    # to get the /path/to/htdocs/js/jquery.js

    # $ GET http//localhost/js/dynamic-json.js
    # to get the your application response

Will you want to set config from yaml?

    my $mw = HTTP::Engine::Middleware->new;
    $mw->install( 'HTTP::Engine::Middleware::Static' => {
        regexp  => '^/(robots.txt|favicon.ico|(?:css|img)/.+|js/(?!dynamic).+)$',
        docroot => '/path/to/htdocs/',
    });
    HTTP::Engine->new(
        interface => {
            module => 'YourFavoriteInterfaceHere',
            request_handler => $mw->handler( \&handler ),
        }
    )->run();

    # $ GET http//localhost/js/jquery.js
    # to get the /path/to/htdocs/js/jquery.js

    # $ GET http//localhost/js/dynamic-json.js
    # to get the your application response

Do you want 404 handle has backend application?

    my $mw = HTTP::Engine::Middleware->new;
    $mw->install( 'HTTP::Engine::Middleware::Static' => {
        regexp         => qr{^/css/.+)$},
        docroot        => '/path/to/htdocs/',
        is_404_handler => 0, # 404 handling off
    });
    HTTP::Engine->new(
        interface => {
            module => 'YourFavoriteInterfaceHere',
            request_handler => $mw->handler(sub {
                HTTP::Engine::Response->new( body => 'dynamic daikuma' );
            }),
        }
    )->run();

    # if css has foo.css file only

    # $ GET http//localhost/css/foo.css
    # to get the /path/to/htdocs/css/foo.css

    # $ GET http//localhost/css/bar.css
    # to get the 'dynamic daikuma' strings


=head1 DESCRIPTION

On development site, you would feed some static contents from Interface::ServerSimple, or other stuff.
This module helps that.

=head1 AUTHORS

Kazuhiro Osawa

typester (is_404_handler support)

=cut