/**
 * Copyright (c) 2011-2025 Bill Greiman
 * This file is part of the SdFat library for SD memory cards.
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#pragma once
/**
 * \file
 * \brief StdioStream class
 */
#include <limits.h>

#include "ios.h"
//------------------------------------------------------------------------------
/** Total size of stream buffer. The entire buffer is used for output.
 * During input UNGETC_BUF_SIZE of this space is reserved for ungetc.
 */
const uint8_t STREAM_BUF_SIZE = 64;
/** Amount of buffer allocated for ungetc during input. */
const uint8_t UNGETC_BUF_SIZE = 2;
//------------------------------------------------------------------------------
// Get rid of any macros defined in <stdio.h>.
#include <stdio.h>
#undef clearerr
#undef fclose
#undef feof
#undef ferror
#undef fflush
#undef fgetc
#undef fgetpos
#undef fgets
#undef fopen
#undef fprintf
#undef fputc
#undef fputs
#undef fread
#undef freopen
#undef fscanf
#undef fseek
#undef fsetpos
#undef ftell
#undef fwrite
#undef getc
#undef getchar
#undef gets
#undef perror
// #undef printf  // NOLINT
#undef putc
#undef putchar
#undef puts
#undef remove
#undef rename
#undef rewind
#undef scanf
#undef setbuf
#undef setvbuf
// #undef sprintf  // NOLINT
#undef sscanf
#undef tmpfile
#undef tmpnam
#undef ungetc
#undef vfprintf
#undef vprintf
#undef vsprintf

// make sure needed macros are defined
#ifndef EOF
/** End-of-file return value. */
#define EOF (-1)
#endif // EOF
#ifndef NULL
/** Null pointer */
#define NULL 0
#endif // NULL
#ifndef SEEK_CUR
/** Seek relative to current position. */
#define SEEK_CUR 1
#endif // SEEK_CUR
#ifndef SEEK_END
/** Seek relative to end-of-file. */
#define SEEK_END 2
#endif // SEEK_END
#ifndef SEEK_SET
/** Seek relative to start-of-file. */
#define SEEK_SET 0
#endif // SEEK_SET
//------------------------------------------------------------------------------
/** \class StdioStream
 * \brief StdioStream implements a minimal stdio stream.
 *
 * StdioStream does not support subdirectories or long file names.
 */
class StdioStream : private StreamBaseFile
{
  public:
    using StreamBaseFile::printField;
    /** Constructor
     *
     */
    StdioStream() : m_buf{0}
    {
    }
    //----------------------------------------------------------------------------
    /** Clear the stream's end-of-file and error indicators. */
    void clearerr()
    {
        m_status &= ~(S_ERR | S_EOF);
    }
    //----------------------------------------------------------------------------
    /** Close a stream.
     *
     * A successful call to the fclose function causes the stream  to be
     * flushed and the associated file to be closed. Any unwritten buffered
     * data is written to the file; any unread buffered data is discarded.
     * Whether or not the call succeeds, the stream is disassociated from
     * the file.
     *
     * \return zero if the stream was successfully closed, or EOF if any any
     * errors are detected.
     */
    int fclose();
    //----------------------------------------------------------------------------
    /** Test the stream's end-of-file indicator.
     * \return non-zero if and only if the end-of-file indicator is set.
     */
    int feof()
    {
        return (m_status & S_EOF) != 0;
    }
    //----------------------------------------------------------------------------
    /** Test the stream's error indicator.
     * \return return non-zero if and only if the error indicator is set.
     */
    int ferror()
    {
        return (m_status & S_ERR) != 0;
    }
    //----------------------------------------------------------------------------
    /** Flush the stream.
     *
     * If stream is an output stream or an update stream in which the most
     * recent operation was not input, any unwritten data is written to the
     * file; otherwise the call is an error since any buffered input data
     * would be lost.
     *
     * \return sets the error indicator for the stream and returns EOF if an
     * error occurs, otherwise it returns zero.
     */
    int fflush();
    //----------------------------------------------------------------------------
    /** Get a byte from the stream.
     *
     * \return If the end-of-file indicator for the stream is set, or if the
     * stream is at end-of-file, the end-of-file indicator for the stream is
     * set and the fgetc function returns EOF. Otherwise, the fgetc function
     * returns the next character from the input stream.
     */
    int fgetc()
    {
        return m_r-- == 0 ? fillGet() : *m_p++;
    }
    //----------------------------------------------------------------------------
    /** Get a string from a stream.
     *
     * The fgets function reads at most one less than the number of
     * characters specified by num from the stream into the array pointed
     * to by str. No additional characters are read after a new-line
     * character (which is retained) or after end-of-file. A null character
     * is written immediately after the last character read into the array.
     *
     * \param[out] str Pointer to an array of where the string is copied.
     *
     * \param[in] num Maximum number of characters including the null
     * character.
     *
     * \param[out] len If len is not null and fgets is successful, the
     * length of the string is returned.
     *
     * \return str if successful. If end-of-file is encountered and no
     * characters have been read into the array, the contents of the array
     * remain unchanged and a null pointer is returned. If a read error
     * occurs during the operation, the array contents are indeterminate
     * and a null pointer is returned.
     */
    char *fgets(char *str, size_t num, size_t *len = 0);
    //----------------------------------------------------------------------------
    /** Open a stream.
     *
     * Open a file and associates the stream with it.
     *
     * \param[in] path file to be opened.
     *
     * \param[in] mode a string that indicates the open mode.
     *
     * <table>
     * <tr>
     * <td>"r" or "rb"</td>
     * <td>Open a file for reading. The file must exist.</td>
     * </tr>
     * <tr>
     * <td>"w" or "wb"</td>
     * <td>Truncate an existing to zero length or create an empty file
     * for writing.</td>
     * </tr>
     * <tr>
     * <td>"wx" or "wbx"</td>
     * <td>Create a file for writing. Fails if the file already exists.</td>
     * </tr>
     * <tr>
     * <td>"a" or "ab"</td>
     * <td>Append; open or create file for writing at end-of-file.</td>
     * </tr>
     * <tr>
     * <td>"r+" or "rb+" or "r+b"</td>
     * <td>Open a file for update (reading and writing).</td>
     * </tr>
     * <tr>
     * <td>"w+" or "w+b" or "wb+"</td>
     * <td>Truncate an existing to zero length or create a file for update.</td>
     * </tr>
     * <tr>
     * <td>"w+x" or "w+bx" or "wb+x"</td>
     * <td>Create a file for update. Fails if the file already exists.</td>
     * </tr>
     * <tr>
     * <td>"a+" or "a+b" or "ab+"</td>
     * <td>Append; open or create a file for update, writing at end-of-file.</td>
     * </tr>
     * </table>
     * The character 'b' shall have no effect, but is allowed for ISO C
     * standard conformance.
     *
     * Opening a file with append mode causes all subsequent writes to the
     * file to be forced to the then current end-of-file, regardless of
     * intervening calls to the fseek function.
     *
     * When a file is opened with update mode, both input and output may be
     * performed on the associated stream. However, output shall not be
     * directly followed by input without an intervening call to the fflush
     * function or to a file positioning function (fseek, or rewind), and
     * input shall not be directly followed by output without an intervening
     * call to a file positioning function, unless the input operation
     * encounters end-of-file.
     *
     * \return true for success or false for failure.
     */
    bool fopen(const char *path, const char *mode);
    //----------------------------------------------------------------------------
    /** Write a byte to a stream.
     *
     * \param[in] c the byte to be written  (converted to an unsigned char).
     *
     * \return Upon successful completion, fputc() returns the value it
     * has written. Otherwise, it returns EOF and sets the error indicator for
     * the stream.
     */
    int fputc(int c)
    {
        return m_w-- == 0 ? flushPut(c) : *m_p++ = c;
    }
    //----------------------------------------------------------------------------
    /** Write a string to a stream.
     *
     * \param[in] str a pointer to the string to be written.
     *
     * \return for success, fputs() returns a non-negative
     * number. Otherwise, it returns EOF and sets the error indicator for
     * the stream.
     */
    int fputs(const char *str);
    //----------------------------------------------------------------------------
    /** Binary input.
     *
     * Reads an array of up to count elements, each one with a size of size
     * bytes.
     * \param[out] ptr pointer to area of at least (size*count) bytes where
     * the data will be stored.
     *
     * \param[in] size the size, in bytes, of each element to be read.
     *
     * \param[in] count the number of elements to be read.
     *
     * \return number of elements successfully read, which may be less than
     * count if a read error or end-of-file is encountered. If size or count
     * is zero, fread returns zero and the contents of the array and the
     * state of the stream remain unchanged.
     */
    size_t fread(void *ptr, size_t size, size_t count);
    //----------------------------------------------------------------------------
    /** Set the file position for the stream.
     *
     * \param[in] offset number of offset from the origin.
     *
     * \param[in] origin position used as reference for the offset. It is
     * specified by one of the following constants.
     *
     * SEEK_SET	- Beginning of file.
     *
     * SEEK_CUR	- Current position of the file pointer.
     *
     * SEEK_END	- End of file.
     *
     * \return zero for success. Otherwise, it returns non-zero and sets the
     * error indicator for the stream.
     */
    int fseek(int32_t offset, int origin);
    //----------------------------------------------------------------------------
    /** Get the current position in a stream.
     *
     * \return If successful, ftell return the current value of the position
     * indicator. On failure, ftell returns −1L.
     */
    int32_t ftell();
    //----------------------------------------------------------------------------
    /** Binary output.
     *
     * Writes an array of up to count elements, each one with a size of size
     * bytes.
     * \param[in] ptr pointer to (size*count) bytes of data to be written.
     *
     * \param[in] size the size, in bytes, of each element to be written.
     *
     * \param[in] count the number of elements to be written.
     *
     * \return number of elements successfully written. if this number is
     * less than count, an error has occurred.  If size or count is zero,
     * fwrite returns zero.
     */
    size_t fwrite(const void *ptr, size_t size, size_t count);
    //----------------------------------------------------------------------------
    /** Get a byte from the stream.
     *
     * getc and fgetc are equivalent but getc is in-line so it is faster but
     * require more flash memory.
     *
     * \return If the end-of-file indicator for the stream is set, or if the
     * stream is at end-of-file, the end-of-file indicator for the stream is
     * set and the fgetc function returns EOF. Otherwise, the fgetc function
     * returns the next character from the input stream.
     */
    inline __attribute__((always_inline)) int getc()
    {
        return m_r-- == 0 ? fillGet() : *m_p++;
    }
    //----------------------------------------------------------------------------
    /** Write a byte to a stream.
     *
     * putc and fputc are equivalent but putc is in-line so it is faster but
     * require more flash memory.
     *
     * \param[in] c the byte to be written  (converted to an unsigned char).
     *
     * \return Upon successful completion, fputc() returns the value it
     * has written. Otherwise, it returns EOF and sets the error indicator for
     * the stream.
     */
    inline __attribute__((always_inline)) int putc(int c)
    {
        return m_w-- == 0 ? flushPut(c) : *m_p++ = c;
    }
    //----------------------------------------------------------------------------
    /** Write a CR/LF.
     *
     * \return two, the number of bytes written, for success or -1 for failure.
     */
    inline __attribute__((always_inline)) int putCRLF()
    {
        if (m_w < 2)
        {
            if (!flushBuf())
            {
                return -1;
            }
        }
        *m_p++ = '\r';
        *m_p++ = '\n';
        m_w -= 2;
        return 2;
    }
    //----------------------------------------------------------------------------
    /** Write a character.
     * \param[in] c the character to write.
     * \return the number of bytes written.
     */
    size_t print(char c)
    {
        return putc(c) < 0 ? 0 : 1;
    }
    //----------------------------------------------------------------------------
    /** Write a string.
     *
     * \param[in] str the string to be written.
     *
     * \return the number of bytes written.
     */
    size_t print(const char *str)
    {
        int n = fputs(str);
        return n < 0 ? 0 : n;
    }
    //----------------------------------------------------------------------------
#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN)
    /** Print a string stored in flash memory.
     *
     * \param[in] str the string to print.
     *
     * \return the number of bytes written.
     */
    size_t print(const __FlashStringHelper *str);
#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN)
    //----------------------------------------------------------------------------
    /** Print a floating point number.
     *
     * \param[in] prec Number of digits after decimal point.
     *
     * \param[in] val the number to be printed.
     *
     * \return the number of bytes written.
     */
    size_t print(double val, uint8_t prec = 2)
    {
        return print(static_cast<float>(val), prec);
    }
    //----------------------------------------------------------------------------
    /** Print a floating point number.
     *
     * \param[in] prec Number of digits after decimal point.
     *
     * \param[in] val the number to be printed.
     *
     * \return the number of bytes written.
     */
    size_t print(float val, uint8_t prec = 2)
    {
        int n = printDec(val, prec);
        return n > 0 ? n : 0;
    }
    //----------------------------------------------------------------------------
    /** Print a number.
     *
     * \param[in] val the number to be printed.
     *
     * \return the number of bytes written.
     */
    template <typename T> size_t print(T val)
    {
        int n = printDec(val);
        return n > 0 ? n : 0;
    }
    //----------------------------------------------------------------------------
    /** Write a CR/LF.
     *
     * \return two, the number of bytes written, for success or zero for failure.
     */
    size_t println()
    {
        return putCRLF() > 0 ? 2 : 0;
    }
    //----------------------------------------------------------------------------
    /** Print a floating point number followed by CR/LF.
     *
     * \param[in] val the number to be printed.
     *
     * \param[in] prec Number of digits after decimal point.
     *
     * \return the number of bytes written.
     */
    size_t println(double val, uint8_t prec = 2)
    {
        return println(static_cast<float>(val), prec);
    }
    //----------------------------------------------------------------------------
    /** Print a floating point number followed by CR/LF.
     *
     * \param[in] val the number to be printed.
     *
     * \param[in] prec Number of digits after decimal point.
     *
     * \return the number of bytes written.
     */
    size_t println(float val, uint8_t prec = 2)
    {
        int n = printDec(val, prec);
        return n > 0 && putCRLF() > 0 ? n + 2 : 0;
    }
    //----------------------------------------------------------------------------
    /** Print an item followed by CR/LF
     *
     * \param[in] val the item to be printed.
     *
     * \return the number of bytes written.
     */
    template <typename T> size_t println(T val)
    {
        int n = print(val);
        return putCRLF() > 0 ? n + 2 : 0;
    }
    //----------------------------------------------------------------------------
    /** Print a char as a number.
     * \param[in] n number to be printed.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(char n)
    {
        if (CHAR_MIN == 0)
        {
            return printDec((unsigned char)n);
        }
        else
        {
            return printDec((signed char)n);
        }
    }
    //----------------------------------------------------------------------------
    /** print a signed 8-bit integer
     * \param[in] n number to be printed.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(signed char n);
    //----------------------------------------------------------------------------
    /** Print an unsigned 8-bit number.
     * \param[in] n number to be print.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(unsigned char n)
    {
        return printDec(static_cast<uint16_t>(n));
    }
    //----------------------------------------------------------------------------
    /** Print a int16_t
     * \param[in] n number to be printed.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(int16_t n);
    //----------------------------------------------------------------------------
    /** print a uint16_t.
     * \param[in] n number to be printed.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(uint16_t n);
    //----------------------------------------------------------------------------
    /** Print a signed 32-bit integer.
     * \param[in] n number to be printed.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(int32_t n);
    //----------------------------------------------------------------------------
    /** Write an unsigned 32-bit number.
     * \param[in] n number to be printed.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(uint32_t n);
    //----------------------------------------------------------------------------
    /** Print a double.
     * \param[in] value The number to be printed.
     * \param[in] prec Number of digits after decimal point.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(double value, uint8_t prec)
    {
        return printDec(static_cast<float>(value), prec);
    }
    //----------------------------------------------------------------------------
    /** Print a float.
     * \param[in] value The number to be printed.
     * \param[in] prec Number of digits after decimal point.
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printDec(float value, uint8_t prec);
    //----------------------------------------------------------------------------
    /** Print HEX
     * \param[in] n number to be printed as HEX.
     *
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printHex(uint32_t n);
    //----------------------------------------------------------------------------
    /** Print HEX with CRLF
     * \param[in] n number to be printed as HEX.
     *
     * \return The number of bytes written or -1 if an error occurs.
     */
    int printHexln(uint32_t n)
    {
        int rtn = printHex(n);
        return rtn < 0 || putCRLF() != 2 ? -1 : rtn + 2;
    }
    //----------------------------------------------------------------------------
    /** Set position of a stream to the beginning.
     *
     * The rewind function sets the file position to the beginning of the
     * file. It is equivalent to fseek(0L, SEEK_SET) except that the error
     * indicator for the stream is also cleared.
     *
     * \return true for success or false for failure.
     */
    bool rewind();
    //----------------------------------------------------------------------------
    /** Push a byte back into an input stream.
     *
     * \param[in] c the byte (converted to an unsigned char) to be pushed back.
     *
     * One character of push-back is guaranteed. If the ungetc function is
     * called too many times without an intervening read or file positioning
     * operation on that stream, the operation may fail.
     *
     * A successful intervening call to a file positioning function (fseek,
     * fsetpos, or rewind) discards any pushed-back characters for the stream.
     *
     * \return Upon successful completion, ungetc() returns the byte pushed
     * back after conversion. Otherwise it returns EOF.
     */
    int ungetc(int c);
    //============================================================================
  private:
    bool fillBuf();
    int fillGet();
    bool flushBuf();
    int flushPut(uint8_t c);
    int write(const void *buf, size_t count);
    //----------------------------------------------------------------------------
    // S_SRD and S_WR are never simultaneously asserted
    static const uint8_t S_SRD = 0x01; // OK to read
    static const uint8_t S_SWR = 0x02; // OK to write
    static const uint8_t S_SRW = 0x04; // open for reading & writing
    static const uint8_t S_EOF = 0x10; // found EOF
    static const uint8_t S_ERR = 0x20; // found error
    //----------------------------------------------------------------------------
    uint8_t m_buf[STREAM_BUF_SIZE];
    uint8_t m_status = 0;
    uint8_t *m_p = m_buf;
    uint8_t m_r = 0;
    uint8_t m_w = 0;
};
