package API::Dispatch::PSGI;
use strict;
use warnings;
use JSON;
use Plack::Request;
use Plack::Response;
use Try::Tiny;
use Module::Load;
use lib 'libs';
use lib './libs';
use Scalar::Util qw/dualvar blessed/;
use Hash::MultiValue;

use parent qw/Plack::Component/;
use Plack::Util::Accessor qw/dispatchTo/;

# dispatch http requests (POST, GET, PUT, DELETE) to the correct class and function
# takes class from dispatchTo, method from PATH_INFO

sub call {
    my $self = shift;
    my ($env) = @_;
    my $p = {};

    my $req = Plack::Request->new( $env );
    my $path_info = $req->path; 
    if ($req->method  !~ m{^GET|POST|PUT|DELETE$}) {
        my $res = Plack::Response->new( status_line( 405 => "Method Prohibited" ) );
        return $res->finalize;
    }
    if ($req->method =~ m{^POST|PUT$} && 
        (!$req->content || !$req->content_type || $req->content_type !~ m{^application/json(\s*;\s*charset)?})) {
        my $res = Plack::Response->new( status_line( 405 => "Method Prohibited" ) );
        return $res->finalize;
    }
    my $dispatch = try {
        my $dispatch = join "::", q/API/, dispatchTo $self;
        load $dispatch;
        $dispatch;
    };
    unless ($dispatch) {
        my $res = Plack::Response->new( status_line( 404 => "Unknown path") );
        return finalize $res;
    }
    my (undef, $path, @path_parts) = split(q{/+}, $path_info);
    $path = lcfirst $path;
    unless ($path && can $dispatch(lcfirst $path)) {
        my $res = Plack::Response->new( status_line( 404 => "No such function call") );
        return $res->finalize;
    }
    my $json = JSON->new->allow_blessed->convert_blessed->utf8(1);
    my $data;
    if ($req->content) {
        $data = $json->decode( $req->content );
    }
    $p->{params} = scalar($req->param) ? $req->parameters : Hash::MultiValue->new();
    $p->{data} = Hash::MultiValue->new( $data ? %$data : () );
    $p->{path} = $path;
    my $return = try {
        local *STDERR = $env->{'psgi.errors'} || *STDERR;
        $dispatch->new($p)->$path( @path_parts );
    } catch {
        if (blessed( $_ ) && $_->isa("ECS::WarehouseOS::Error")) {
            return $_;
        }
        return { errorCode => 1024, message => "$_" };  
    };
    my $res = Plack::Response->new( 200 );
    if ($return) {
        $res->content_type('application/json');
        $res->body( $json->utf8(0)->encode( $return ) );
    }
    return $res->finalize;
}

# convert an HTTP status code and status line tuple into a status response 
# (200 => "OK") to "200 OK", and return a dualvar over the status code and
# status response 
sub status_line {
    my ($code, $line) = @_;
    ($line) = split(/\R/s, $line);
    my $status = $line ? "$code $line" : "$code";
	#dualvar( $code, $line );
	return($code, ($_[1] ? ['X-API-Error' => $_[1]] : ()));
}

1;
