<?php
/**
 * This file is part of the ZBateson\MailMimeParser project.
 *
 * @license http://opensource.org/licenses/bsd-license.php BSD
 */

namespace ZBateson\MailMimeParser\Message;

use Psr\Http\Message\StreamInterface;
use SplSubject;
use ZBateson\MailMimeParser\IErrorBag;
use ZBateson\MailMimeParser\MailMimeParser;

/**
 * An interface representing a single part of an email.
 *
 * The base type for a message or any child part of a message.  The part may
 * contain content, have a parent, and identify the type of content (e.g.
 * mime-type or charset) agnostically.
 *
 * The interface extends SplSubject -- any modifications to a message must
 * notify any attached observers.
 *
 * @author Zaahid Bateson
 */
interface IMessagePart extends IErrorBag, SplSubject
{
    /**
     * Returns this part's parent.
     */
    public function getParent() : ?IMimePart;

    /**
     * Returns true if the part contains a 'body' (content).
     *
     */
    public function hasContent() : bool;

    /**
     * Returns true if the content of this part is plain text.
     *
     */
    public function isTextPart() : bool;

    /**
     * Returns the mime type of the content, or $default if one is not set.
     *
     * @param string $default Optional override for the default return value of
     *        'text/plain.
     * @return string the mime type
     */
    public function getContentType(string $default = 'text/plain') : ?string;

    /**
     * Returns the charset of the content, or null if not applicable/defined.
     *
     * @return string|null the charset
     */
    public function getCharset() : ?string;

    /**
     * Returns the content's disposition, or returns the value of $default if
     * not defined.
     *
     * @param string $default Optional default value to return if not
     *        applicable/defined
     * @return string|null the disposition.
     */
    public function getContentDisposition(?string $default = null) : ?string;

    /**
     * Returns the content transfer encoding used to encode the content on this
     * part, or the value of $default if not defined.
     *
     * @param ?string $default Optional default value to return if not
     *        applicable/defined
     * @return string|null the transfer encoding defined for the part.
     */
    public function getContentTransferEncoding(?string $default = null) : ?string;

    /**
     * Returns the Content ID of the part, or null if not defined.
     *
     * @return string|null the content ID.
     */
    public function getContentId() : ?string;

    /**
     * Returns a filename for the part if one is defined, or null otherwise.
     *
     * @return string|null the file name
     */
    public function getFilename() : ?string;

    /**
     * Returns true if the current part is a mime part.
     *
     */
    public function isMime() : bool;

    /**
     * Overrides the default character set used for reading content from content
     * streams in cases where a user knows the source charset is not what is
     * specified.
     *
     * If set, the returned value from {@see IMessagePart::getCharset()} must be
     * ignored during subsequent read operations and streams created out of this
     * part's content.
     *
     * Note that setting an override on an
     * {@see \ZBateson\MailMimeParser\IMessage} and calling getTextStream,
     * getTextContent, getHtmlStream or getHtmlContent will not be applied to
     * those sub-parts, unless the text/html part is the IMessage itself.
     * Instead, {@see \ZBateson\MailMimeParser\IMessage::getTextPart()} should
     * be called, and setCharsetOverride called on the returned IMessagePart.
     *
     * @see IMessagePart::getContentStream() to get the content stream.
     * @param string $charsetOverride the actual charset of the content.
     * @param bool $onlyIfNoCharset if true, $charsetOverride is used only if
     *        getCharset returns null.
     */
    public function setCharsetOverride(string $charsetOverride, bool $onlyIfNoCharset = false) : static;

    /**
     * Returns the StreamInterface for the part's content or null if the part
     * doesn't have a content section.
     *
     * To get a stream without charset conversion if you know the part's content
     * contains a binary stream, call {@see self::getBinaryContentStream()}
     * instead.
     *
     * The library automatically handles decoding and charset conversion (to the
     * target passed $charset) based on the part's transfer encoding as returned
     * by {@see IMessagePart::getContentTransferEncoding()} and the part's
     * charset as returned by {@see IMessagePart::getCharset()}.  The returned
     * stream is ready to be read from directly.
     *
     * Note that the returned Stream is a shared object.  If called multiple
     * times with the same $charset, and the value of the part's
     * Content-Transfer-Encoding header has not changed, the stream will be
     * rewound.  This would affect other existing variables referencing the
     * stream, for example:
     *
     * ```php
     * // assuming $part is a part containing the following
     * // string for its content: '12345678'
     * $stream = $part->getContentStream();
     * $someChars = $part->read(4);
     *
     * $stream2 = $part->getContentStream();
     * $moreChars = $part->read(4);
     * echo ($someChars === $moreChars);    //1
     * ```
     *
     * In this case the Stream was rewound, and $stream's second call to read 4
     * bytes reads the same first 4.
     *
     * @see IMessagePart::getBinaryContentStream() to get the content stream
     *      without any charset conversions.
     * @see IMessagePart::saveContent() to save the binary contents to file.
     * @see IMessagePart::setCharsetOverride() to override the charset of the
     *      content and ignore the charset returned from calling
     *      IMessagePart::getCharset() when reading.
     * @param string $charset Optional charset for the returned stream.
     * @return StreamInterface|null the stream
     */
    public function getContentStream(string $charset = MailMimeParser::DEFAULT_CHARSET) : ?StreamInterface;

    /**
     * Returns the raw data stream for the current part, if it exists, or null
     * if there's no content associated with the stream.
     *
     * This is basically the same as calling
     * {@see IMessagePart::getContentStream()}, except no automatic charset
     * conversion is done.  Note that for non-text streams, this doesn't have an
     * effect, as charset conversion is not performed in that case, and is
     * useful only when:
     *
     * - The charset defined is not correct, and the conversion produces errors;
     *   or
     * - You'd like to read the raw contents without conversion, for instance to
     *   save it to file or allow a user to download it as-is (in a download
     *   link for example).
     *
     * @see IMessagePart::getContentStream() to get the content stream with
     *      charset conversions applied.
     * @see IMessagePart::getBinaryContentResourceHandle() to get a resource
     *      handle instead.
     * @see IMessagePart::saveContent() to save the binary contents to file.
     * @return StreamInterface|null the stream
     */
    public function getBinaryContentStream() : ?StreamInterface;

    /**
     * Returns a resource handle for the content's raw data stream, or null if
     * the part doesn't have a content stream.
     *
     * The method wraps a call to {@see IMessagePart::getBinaryContentStream()}
     * and returns a resource handle for the returned Stream.
     *
     * @see IMessagePart::getBinaryContentStream() to get a stream instead.
     * @see IMessagePart::saveContent() to save the binary contents to file.
     * @return resource|null the resource
     */
    public function getBinaryContentResourceHandle() : mixed;

    /**
     * Saves the binary content of the stream to the passed file, resource or
     * stream.
     *
     * Note that charset conversion is not performed in this case, and the
     * contents of the part are saved in their binary format as transmitted (but
     * after any content-transfer decoding is performed).  {@see
     * IMessagePart::getBinaryContentStream()} for a more detailed description
     * of the stream.
     *
     * If the passed parameter is a string, it's assumed to be a filename to
     * write to.  The file is opened in 'w+' mode, and closed before returning.
     *
     * When passing a resource or Psr7 Stream, the resource is not closed, nor
     * rewound.
     *
     * @see IMessagePart::getContentStream() to get the content stream with
     *      charset conversions applied.
     * @see IMessagePart::getBinaryContentStream() to get the content as a
     *      binary stream.
     * @see IMessagePart::getBinaryContentResourceHandle() to get the content as
     *      a resource handle.
     * @param string|resource|StreamInterface $filenameResourceOrStream
     */
    public function saveContent($filenameResourceOrStream) : static;

    /**
     * Shortcut to reading stream content and assigning it to a string.  Returns
     * null if the part doesn't have a content stream.
     *
     * The returned string is encoded to the passed $charset character encoding.
     *
     * @see IMessagePart::getContentStream()
     * @param string $charset the target charset for the returned string
     * @return ?string the content
     */
    public function getContent(string $charset = MailMimeParser::DEFAULT_CHARSET) : ?string;

    /**
     * Attaches the stream or resource handle for the part's content.  The
     * stream is closed when another stream is attached, or the MimePart is
     * destroyed.
     *
     * @see IMessagePart::setContent() to pass a string as the content.
     * @see IMessagePart::getContentStream() to get the content stream.
     * @see IMessagePart::detachContentStream() to detach the content stream.
     * @param StreamInterface $stream the content
     * @param string $streamCharset the charset of $stream
     */
    public function attachContentStream(StreamInterface $stream, string $streamCharset = MailMimeParser::DEFAULT_CHARSET) : static;

    /**
     * Detaches the content stream.
     *
     * @see IMessagePart::getContentStream() to get the content stream.
     * @see IMessagePart::attachContentStream() to attach a content stream.
     */
    public function detachContentStream() : static;

    /**
     * Sets the content of the part to the passed string, resource, or stream.
     *
     * @see IMessagePart::getContentStream() to get the content stream.
     * @see IMessagePart::attachContentStream() to attach a content stream.
     * @see IMessagePart::detachContentStream() to detach the content stream.
     * @param string|resource|StreamInterface $resource the content.
     * @param string $resourceCharset the charset of the passed $resource.
     */
    public function setContent($resource, string $resourceCharset = MailMimeParser::DEFAULT_CHARSET) : static;

    /**
     * Returns a resource handle for the string representation of this part,
     * containing its headers, content and children.  For an IMessage, this
     * would be the entire RFC822 (or greater) email.
     *
     * If the part has not been modified and represents a parsed part, the
     * original stream should be returned.  Otherwise a stream representation of
     * the part including its modifications should be returned.  This insures
     * that an unmodified, signed message could be passed on that way even after
     * parsing and reading.
     *
     * The returned stream is not guaranteed to be RFC822 (or greater) compliant
     * for the following reasons:
     *
     *  - The original email or part, if not modified, is returned as-is and may
     *    not be compliant.
     *  - Although certain parts may have been modified, an original unmodified
     *    header from the original email or part may not be compliant.
     *  - A user may set headers in a non-compliant format.
     *
     * @see IMessagePart::getStream() to get a Psr7 StreamInterface instead of a
     *      resource handle.
     * @see IMessagePart::__toString() to write the part to a string and return
     *      it.
     * @see IMessage::save() to write the part to a file, resource handle or
     *      Psr7 stream.
     * @return resource the resource handle containing the part.
     */
    public function getResourceHandle() : mixed;

    /**
     * Returns a Psr7 StreamInterface for the string representation of this
     * part, containing its headers, content and children.
     *
     * If the part has not been modified and represents a parsed part, the
     * original stream should be returned.  Otherwise a stream representation of
     * the part including its modifications should be returned.  This insures
     * that an unmodified, signed message could be passed on that way even after
     * parsing and reading.
     *
     * The returned stream is not guaranteed to be RFC822 (or greater) compliant
     * for the following reasons:
     *
     *  - The original email or part, if not modified, is returned as-is and may
     *    not be compliant.
     *  - Although certain parts may have been modified, an original unmodified
     *    header from the original email or part may not be compliant.
     *  - A user may set headers in a non-compliant format.
     *
     * @see IMessagePart::getResourceHandle() to get a resource handle.
     * @see IMessagePart::__toString() to write the part to a string and return
     *      it.
     * @see IMessage::save() to write the part to a file, resource handle or
     *      Psr7 stream.
     * @return StreamInterface the stream containing the part.
     */
    public function getStream() : StreamInterface;

    /**
     * Writes a string representation of this part, including its headers,
     * content and children to the passed file, resource, or stream.
     *
     * If the part has not been modified and represents a parsed part, the
     * original stream should be written to the file.  Otherwise a stream
     * representation of the part including its modifications should be written.
     * This insures that an unmodified, signed message could be passed on this
     * way even after parsing and reading.
     *
     * The written stream is not guaranteed to be RFC822 (or greater) compliant
     * for the following reasons:
     *
     *  - The original email or part, if not modified, is returned as-is and may
     *    not be compliant.
     *  - Although certain parts may have been modified, an original unmodified
     *    header from the original email or part may not be compliant.
     *  - A user may set headers in a non-compliant format.
     *
     * If the passed $filenameResourceOrStream is a string, it's assumed to be a
     * filename to write to.
     *
     * When passing a resource or Psr7 Stream, the resource is not closed, nor
     * rewound after being written to.
     *
     * @see IMessagePart::getResourceHandle() to get a resource handle.
     * @see IMessagePart::__toString() to get the part in a string.
     * @see IMessage::save() to write the part to a file, resource handle or
     *      Psr7 stream.
     * @param string|resource|StreamInterface $filenameResourceOrStream the
     *        file, resource, or stream to write to.
     * @param string $filemode Optional filemode to open a file in (if
     *        $filenameResourceOrStream is a string)
     */
    public function save(mixed $filenameResourceOrStream, string $filemode = 'w+') : static;

    /**
     * Returns the message/part as a string, containing its headers, content and
     * children.
     *
     * Convenience method for calling getContents() on
     * {@see IMessagePart::getStream()}.
     *
     * @see IMessagePart::getStream() to get a Psr7 StreamInterface instead of a
     *      string.
     * @see IMessagePart::getResourceHandle() to get a resource handle.
     * @see IMessage::save() to write the part to a file, resource handle or
     *      Psr7 stream.
     */
    public function __toString() : string;
}
