package HTTP::Parser::XS::PP;
use strict;
use warnings;
use utf8;
use URI::Escape ();

sub HTTP::Parser::XS::parse_http_request {
    my($chunk, $env) = @_;
    Carp::croak("second param to parse_http_request should be a hashref") unless (ref $env|| '') eq 'HASH';

    # pre-header blank lines are allowed (RFC 2616 4.1)
    $chunk =~ s/^(\x0d?\x0a)+//;
    return -2 unless length $chunk;

    # double line break indicates end of header; parse it
    if ($chunk =~ /^(.*?\x0d?\x0a\x0d?\x0a)/s) {
        return _parse_header($chunk, length $1, $env);
    }
    return -2;  # still waiting for unknown amount of header lines
}

sub _parse_header {
    my($chunk, $eoh, $env) = @_;

    my $header = substr($chunk, 0, $eoh,'');
    $chunk =~ s/^\x0d?\x0a\x0d?\x0a//;

    # parse into lines
    my @header  = split /\x0d?\x0a/,$header;
    my $request = shift @header;

    # join folded lines
    my @out;
    for(@header) {
        if(/^[ \t]+/) {
            return -1 unless @out;
            $out[-1] .= $_;
        } else {
            push @out, $_;
        }
    }

    # parse request or response line
    my $obj;
    my ($major, $minor);

    my ($method,$uri,$http) = split / /,$request;
    return -1 unless $http and $http =~ /^HTTP\/(\d+)\.(\d+)$/i;
    ($major, $minor) = ($1, $2);

    my($path, $query) = ( $uri =~ /^([^?]*)(?:\?(.*))?$/s );
    # following validations are just needed to pass t/01simple.t
    if ($path =~ /%(?:[0-9a-f][^0-9a-f]|[^0-9a-f][0-9a-f])/i) {
        # invalid char in url-encoded path
        return -1;
    }
    if ($path =~ /%(?:[0-9a-f])$/i) {
        # partially url-encoded
        return -1;
    }

    $env->{REQUEST_METHOD}  = $method;
    $env->{REQUEST_URI}     = $uri;
    $env->{SERVER_PROTOCOL} = "HTTP/$major.$minor";
    $env->{PATH_INFO}    = URI::Escape::uri_unescape($path);
    $env->{QUERY_STRING} = $query || '';
    $env->{SCRIPT_NAME}  = '';

    # import headers
    my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
    my $k;
    for my $header (@out) {
        if ( $header =~ s/^($token): ?// ) {
            $k = $1;
            $k =~ s/-/_/g;
            $k = uc $k;

            if ($k !~ /^(?:CONTENT_LENGTH|CONTENT_TYPE)$/) {
                $k = "HTTP_$k";
            }
        } elsif ( $header =~ /^\s+/) {
            # multiline header
        } else {
            return -1;
        }

        if (exists $env->{$k}) {
            $env->{$k} .= ", $header";
        } else {
            $env->{$k} = $header;
        }
    }

    return $eoh;
}

# ----------------------------------------------------------

my %PARSER_FUNC = (
    HTTP::Parser::XS::HEADERS_NONE()        => \&_parse_as_none,
    HTTP::Parser::XS::HEADERS_AS_HASHREF()  => \&_parse_as_hashref,
    HTTP::Parser::XS::HEADERS_AS_ARRAYREF() => \&_parse_as_arrayref,
);

sub HTTP::Parser::XS::parse_http_response {
    my ($str, $header_format, $special_headers) = @_;
    return -2 unless $str;

    my $len = length $str;
    
    my ($sl, $remain) = split /\r?\n/, $_[0], 2;
    my ($proto, $rc, $msg) = split(' ', $sl, 3);
    return -1 unless $proto =~m{^HTTP/1.(\d)};
    my $minor_version = $1;
    return -1 unless $rc =~m/^\d+$/;

    my ($header_str, $content) = split /\r?\n\r?\n/, $remain, 2;

    my $parser_func = $PARSER_FUNC{$header_format};
    die 'unknown header format: '. $header_format unless $parser_func;

    my $header = $parser_func->($header_str, $special_headers || +{});

    return -2 if ($str !~/\r?\n\r?\n/ && $remain !~/\r?\n\r?\n/ && !defined $content);
    my $parsed = $len - (defined $content ? length $content : 0);

    return ($parsed, $minor_version, $rc, $msg, $header);
}

# return special headers only
sub _parse_as_none {
    my ($str, $special) = @_;
    return unless defined $str;
    return unless keys %$special;

    my ($field, $value, $f);
    for ( split /\r?\n/, $str ) {
        if ( defined $field ) {
            if ( ord == 9 || ord == 32 ) {
                $value .= "\n$_";
                next;
            }
            $f = lc($field); 
            exists $special->{$f} and $special->{$f} = $value;
        }
        ( $field, $value ) = split /[ \t]*: ?/, $_, 2;
    }
    if ( defined $field ) {
        $f = lc($field); 
        exists $special->{$f} and $special->{$f} = $value;
    }
}

# return headers as arrayref
sub _parse_as_arrayref {
    my ($str, $special) = @_;
    return [] unless defined $str;

    my (@headers, $field, $value, $f );
    for ( split /\r?\n/, $str ) {
        if ( defined $field ) {
            if ( ord == 9 || ord == 32 ) {
                $value .= "\n$_";
                next;
            }
            $f = lc($field); 
            push @headers, $f, $value;
            exists $special->{$f} and $special->{$f} = $value;
        }
        ( $field, $value ) = split /[ \t]*: ?/, $_, 2;
    }
    if ( defined $field ) {
        $f = lc($field); 
        push @headers, $f, $value; 
        exists $special->{$f} and $special->{$f} = $value;
    }
    return \@headers;
}

# return headers as HTTP::Header compatible HashRef
sub _parse_as_hashref {
    my ($str, $special) = @_;
    return +{} unless defined $str;
    
    my ( %self, $field, $value, $f );
    for ( split /\r?\n/, $str ) {
        if ( defined $field ) {
            if ( ord == 9 || ord == 32 ) {
                $value .= "\n$_";
                next;
            }
            $f = lc($field); 
            if ( defined $self{$f} ) {
                my $h = $self{$f};
                ref($h) eq 'ARRAY'
                  ? push( @$h, $value )
                  : ( $self{$f} = [ $h, $value ] );
            }
            else { $self{$f} = $value }
        }
        ( $field, $value ) = split /[ \t]*: ?/, $_, 2;
    }
    if ( defined $field ) {
        $f = lc($field); 
        if ( defined $self{$f} ) {
            my $h = $self{$f};
            ref($h) eq 'ARRAY'
              ? push( @$h, $value )
              : ( $self{$f} = [ $h, $value ] );
        }
        else { $self{$f} = $value }
    }
    return \%self;
}

1;

