package Dezi::MultiTenant;
use strict;
use warnings;
use Dezi::Server;
use Dezi::MultiTenant::Config;
use JSON;
use Plack::Builder;
use Plack::Request;
use Plack::App::URLMap;
use Data::Dump qw( dump );
use Carp;
use Module::Load;
use Scalar::Util qw( blessed );

our $VERSION = '0.003';

=head1 NAME

Dezi::MultiTenant - multiple Dezi::Server applications in a single instance

=head1 SYNOPSIS

 % dezi --server-class Dezi::MultiTenant --dezi-config myconfig.pl
 
 # or in your own Plack app
 
 use Plack::Runner;
 use Dezi::MultiTenant;
 
 my $multitenant_config = { 
   'foo' => Dezi::Config->new(\%foo_opts),
   'bar' => Dezi::Config->new(\%bar_opts),
 };
 
 my $runner = Plack::Runner->new();
 my $app = Dezi::MultiTenant->app( $multitenant_config );
 $runner->run($app);
 
 # /foo mounts a Dezi::Server
 # /bar mounts a Dezi::Server
 
=head1 DESCRIPTION

Dezi::MultiTenant provides a simple way to mount multiple
Dezi::Server applications in a single Plack app using
a single configuration structure.

Dezi::Server allows you to serve multiple indexes, but all
the indexes must have identical schemas.

Dezi::MultiTenant allows you to server multiple indexes per
server, and each server can have a different schema, as well
as individual administration, logging, unique configuration, etc.

=head1 METHODS

=cut

=head2 app( I<config> )

Returns Plack $app instance via Plack::Builder. 

I<config> should either be a hashref with keys representing each
Dezi::Server's mount point, or a Dezi::MultiTenant::Config object.
By default the root '/' mount point is reserved for the 
Dezi::MultiTenant->about() method response. Hash keys should have
the '/' prefix (same syntax as L<Plack::App::URLMap>).

=cut

sub app {
    my $class = shift;
    my $config = shift or croak "config required";
    my $mt_config;
    if ( blessed $config) {
        if ( $config->isa('Dezi::MultiTenant::Config') ) {
            $mt_config = $config;
        }
        else {
            croak "config is not a Dezi::MultiTenant::Config-derived object";
        }
    }
    else {
        $mt_config = Dezi::MultiTenant::Config->new($config);
    }

    my $url_map = Plack::App::URLMap->new();
    my @loaded;
    for my $path ( $mt_config->paths() ) {
        $url_map->map(
            $path => builder {
                mount '/' =>
                    Dezi::Server->app( $mt_config->config_for($path) );
            }
        );
        push @loaded, $path;
    }

    return builder {

        # global logging
        enable "SimpleLogger", level => $config->{'debug'} ? "debug" : "warn";

        # optional gzip compression for clients that request it
        # client must set "Accept-Encoding" request header
        enable "Deflater",
            content_type => [
            'text/css',        'text/html',
            'text/javascript', 'application/javascript',
            'text/xml',        'application/xml',
            'application/json',
            ],
            vary_user_agent => 1;

        # JSONP response based on 'callback' param
        enable "JSONP";

        # / is self-description
        $url_map->map(
            '/' => sub {
                my $req = Plack::Request->new(shift);
                return $class->about( $req, \@loaded );
            }
        );

        $url_map->map(
            '/favicon.ico' => sub {
                my $req = Plack::Request->new(shift);
                my $res = $req->new_response();
                $res->redirect( 'http://dezi.org/favicon.ico', 301 );
                $res->finalize();
            }
        );

        # TODO /admin

        $url_map->to_app;
    };

}

=head2 about( I<plack_request>, I<loaded> )

Returns Plack::Response-like array ref
describing the multi-tenant application.

=cut

sub about {
    my ( $class, $req, $loaded ) = @_;

    if ( $req->path ne '/' ) {
        my $resp = 'Resource not found';
        return [
            404,
            [   'Content-Type'   => 'text/plain',
                'Content-Length' => length $resp,
            ],
            [$resp]
        ];
    }

    my $base_uri = $req->base;
    $base_uri =~ s,/$,,;    # zap any trailing /
    my %avail = ();
    for my $i (@$loaded) {

        # virtual host check
        if ( $i =~ m!^https?:! ) {
            $avail{$i} = $i;
        }
        else {
            $avail{$i} = $base_uri . $i;
        }
    }

    my $about = {
        description => $class,
        version     => $VERSION,
        available   => \%avail,
    };
    my $resp = to_json($about);
    return [
        200,
        [   'Content-Type'   => 'application/json',
            'Content-Length' => length $resp,
        ],
        [$resp],
    ];
}

1;

__END__

=head1 AUTHOR

Peter Karman, C<< <karman at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-dezi-multitenant at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dezi-MultiTenant>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Dezi::MultiTenant


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Dezi-MultiTenant>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Dezi-MultiTenant>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Dezi-MultiTenant>

=item * Search CPAN

L<http://search.cpan.org/dist/Dezi-MultiTenant/>

=back

=head1 COPYRIGHT & LICENSE

Copyright 2013 Peter Karman.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.


=cut