package Mojo::Content::Single;
use Mojo::Base 'Mojo::Content';

use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;

has asset        => sub { Mojo::Asset::Memory->new(auto_upgrade => 1) };
has auto_upgrade => 1;

sub body_contains { shift->asset->contains(shift) >= 0 }

sub body_size {
  my $self = shift;
  return ($self->headers->content_length || 0) if $self->is_dynamic;
  return $self->{body_size} //= $self->asset->size;
}

sub clone {
  my $self = shift;
  return undef unless my $clone = $self->SUPER::clone();
  return $clone->asset($self->asset);
}

sub get_body_chunk {
  my ($self, $offset) = @_;
  return $self->generate_body_chunk($offset) if $self->is_dynamic;
  return $self->asset->get_chunk($offset);
}

sub new {
  my $self = shift->SUPER::new(@_);
  $self->{read} = $self->on(read => sub { $_[0]->asset($_[0]->asset->add_chunk($_[1])) });
  return $self;
}

sub parse {
  my $self = shift;

  # Parse headers
  $self->_parse_until_body(@_);

  # Parse body
  return $self->SUPER::parse unless $self->auto_upgrade && defined $self->boundary;

  # Content needs to be upgraded to multipart
  $self->unsubscribe(read => $self->{read});
  my $multi = Mojo::Content::MultiPart->new(%$self);
  $self->emit(upgrade => $multi);
  return $multi->parse;
}

1;

=encoding utf8

=head1 NAME

Mojo::Content::Single - HTTP content

=head1 SYNOPSIS

  use Mojo::Content::Single;

  my $single = Mojo::Content::Single->new;
  $single->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
  say $single->headers->content_length;

=head1 DESCRIPTION

L<Mojo::Content::Single> is a container for HTTP content, based on L<RFC 7230|https://tools.ietf.org/html/rfc7230> and
L<RFC 7231|https://tools.ietf.org/html/rfc7231>.

=head1 EVENTS

L<Mojo::Content::Single> inherits all events from L<Mojo::Content> and can emit the following new ones.

=head2 upgrade

  $single->on(upgrade => sub ($single, $multi) {...});

Emitted when content gets upgraded to a L<Mojo::Content::MultiPart> object.

  $single->on(upgrade => sub ($single, $multi) {
    return unless $multi->headers->content_type =~ /multipart\/([^;]+)/i;
    say "Multipart: $1";
  });

=head1 ATTRIBUTES

L<Mojo::Content::Single> inherits all attributes from L<Mojo::Content> and implements the following new ones.

=head2 asset

  my $asset = $single->asset;
  $single   = $single->asset(Mojo::Asset::Memory->new);

The actual content, defaults to a L<Mojo::Asset::Memory> object with L<Mojo::Asset::Memory/"auto_upgrade"> enabled.

=head2 auto_upgrade

  my $bool = $single->auto_upgrade;
  $single  = $single->auto_upgrade($bool);

Try to detect multipart content and automatically upgrade to a L<Mojo::Content::MultiPart> object, defaults to a true
value.

=head1 METHODS

L<Mojo::Content::Single> inherits all methods from L<Mojo::Content> and implements the following new ones.

=head2 body_contains

  my $bool = $single->body_contains('1234567');

Check if content contains a specific string.

=head2 body_size

  my $size = $single->body_size;

Content size in bytes.

=head2 clone

  my $clone = $single->clone;

Return a new L<Mojo::Content::Single> object cloned from this content if possible, otherwise return C<undef>.

=head2 get_body_chunk

  my $bytes = $single->get_body_chunk(0);

Get a chunk of content starting from a specific position. Note that it might not be possible to get the same chunk
twice if content was generated dynamically.

=head2 new

  my $single = Mojo::Content::Single->new;
  my $single = Mojo::Content::Single->new(asset => Mojo::Asset::File->new);
  my $single = Mojo::Content::Single->new({asset => Mojo::Asset::File->new});

Construct a new L<Mojo::Content::Single> object and subscribe to event L<Mojo::Content/"read"> with default content
parser.

=head2 parse

  $single = $single->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
  my $multi = $single->parse("Content-Type: multipart/form-data\x0d\x0a\x0d\x0a");

Parse content chunk and upgrade to L<Mojo::Content::MultiPart> object if necessary.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.

=cut
