/**
 * Copyright (c) 2011-2022 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.
 */
#ifndef ios_h
#define ios_h
#include "../FsLib/FsLib.h"
/**
 * \file
 * \brief \ref ios_base and \ref ios classes
 */
//==============================================================================
/** For internal use in c++ streams */
typedef fspos_t pos_t;
//==============================================================================
#if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN)
/** Set File type for iostreams. */
typedef FatFile StreamBaseFile;
#elif SDFAT_FILE_TYPE == 2
typedef ExFatFile StreamBaseFile;
#elif SDFAT_FILE_TYPE == 3
typedef FsBaseFile StreamBaseFile;
#else // SDFAT_FILE_TYPE
#error Invalid SDFAT_FILE_TYPE
#endif // SDFAT_FILE_TYPE
/**
 * \class ios_base
 * \brief Base class for all streams
 */
class ios_base
{
  public:
    /** typedef for iostate bitmask */
    typedef unsigned char iostate;
    // State flags.
    /** iostate for no flags */
    static const iostate goodbit = 0x00;
    /** iostate bad bit for a nonrecoverable error. */
    static const iostate badbit = 0X01;
    /** iostate bit for end of file reached */
    static const iostate eofbit = 0x02;
    /** iostate fail bit for nonfatal error */
    static const iostate failbit = 0X04;
#if SDFAT_FILE_TYPE == 1
    /**
     * unsigned size that can represent maximum file size.
     *  (violates spec - should be signed)
     */
    typedef uint32_t streamsize;
    /** type for absolute seek position */
    typedef uint32_t pos_type;
    /** type for relative seek offset */
    typedef int32_t off_type;
#else  // SDFAT_FILE_TYPE
    /**
     * unsigned size that can represent maximum file size.
     *  (violates spec - should be signed)
     */
    typedef uint64_t streamsize;
    /** type for absolute seek position */
    typedef uint64_t pos_type;
    /** type for relative seek offset */
    typedef int64_t off_type;
#endif // SDFAT_FILE_TYPE
    /** enumerated type for the direction of relative seeks */
    enum seekdir
    {
        /** seek relative to the beginning of the stream */
        beg,
        /** seek relative to the current stream position */
        cur,
        /** seek relative to the end of the stream */
        end
    };
    /** type for format flags */
    typedef unsigned int fmtflags;
    /** left adjust fields */
    static const fmtflags left = 0x0001;
    /** right adjust fields */
    static const fmtflags right = 0x0002;
    /** fill between sign/base prefix and number */
    static const fmtflags internal = 0x0004;
    /** base 10 flag*/
    static const fmtflags dec = 0x0008;
    /** base 16 flag */
    static const fmtflags hex = 0x0010;
    /** base 8 flag */
    static const fmtflags oct = 0x0020;
    // static const fmtflags fixed      = 0x0040;
    // static const fmtflags scientific = 0x0080;
    /** use strings true/false for bool */
    static const fmtflags boolalpha = 0x0100;
    /** use prefix 0X for hex and 0 for oct */
    static const fmtflags showbase = 0x0200;
    /** always show '.' for floating numbers */
    static const fmtflags showpoint = 0x0400;
    /** show + sign for nonnegative numbers */
    static const fmtflags showpos = 0x0800;
    /** skip initial white space */
    static const fmtflags skipws = 0x1000;
    // static const fmtflags unitbuf    = 0x2000;
    /** use uppercase letters in number representations */
    static const fmtflags uppercase = 0x4000;
    /** mask for adjustfield */
    static const fmtflags adjustfield = left | right | internal;
    /** mask for basefield */
    static const fmtflags basefield = dec | hex | oct;
    // static const fmtflags floatfield  = scientific | fixed;
    //----------------------------------------------------------------------------
    /** typedef for iostream open mode */
    typedef uint8_t openmode;

    // Openmode flags.
    /** seek to end before each write */
    static const openmode app = 0X4;
    /** open and seek to end immediately after opening */
    static const openmode ate = 0X8;
    /** perform input and output in binary mode (as opposed to text mode) */
    static const openmode binary = 0X10;
    /** open for input */
    static const openmode in = 0X20;
    /** open for output */
    static const openmode out = 0X40;
    /** truncate an existing stream when opening */
    static const openmode trunc = 0X80;
    //----------------------------------------------------------------------------
    ios_base() : m_fill(' '), m_fmtflags(dec | right | skipws), m_precision(2), m_width(0)
    {
    }
    /** \return fill character */
    char fill()
    {
        return m_fill;
    }
    /** Set fill character
     * \param[in] c new fill character
     * \return old fill character
     */
    char fill(char c)
    {
        char r = m_fill;
        m_fill = c;
        return r;
    }
    /** \return format flags */
    fmtflags flags() const
    {
        return m_fmtflags;
    }
    /** set format flags
     * \param[in] fl new flag
     * \return old flags
     */
    fmtflags flags(fmtflags fl)
    {
        fmtflags tmp = m_fmtflags;
        m_fmtflags = fl;
        return tmp;
    }
    /** \return precision */
    int precision() const
    {
        return m_precision;
    }
    /** set precision
     * \param[in] n new precision
     * \return old precision
     */
    int precision(unsigned int n)
    {
        int r = m_precision;
        m_precision = n;
        return r;
    }
    /** set format flags
     * \param[in] fl new flags to be or'ed in
     * \return old flags
     */
    fmtflags setf(fmtflags fl)
    {
        fmtflags r = m_fmtflags;
        m_fmtflags |= fl;
        return r;
    }
    /** modify format flags
     * \param[in] mask flags to be removed
     * \param[in] fl flags to be set after mask bits have been cleared
     * \return old flags
     */
    fmtflags setf(fmtflags fl, fmtflags mask)
    {
        fmtflags r = m_fmtflags;
        m_fmtflags &= ~mask;
        m_fmtflags |= fl;
        return r;
    }
    /** clear format flags
     * \param[in] fl flags to be cleared
     */
    void unsetf(fmtflags fl)
    {
        m_fmtflags &= ~fl;
    }
    /** \return width */
    unsigned width()
    {
        return m_width;
    }
    /** set width
     * \param[in] n new width
     * \return old width
     */
    unsigned width(unsigned n)
    {
        unsigned r = m_width;
        m_width = n;
        return r;
    }

  protected:
    /** \return current number base */
    uint8_t flagsToBase()
    {
        uint8_t f = flags() & basefield;
        return f == oct ? 8 : f != hex ? 10 : 16;
    }

  private:
    char m_fill;
    fmtflags m_fmtflags;
    unsigned char m_precision;
    unsigned int m_width;
};
//------------------------------------------------------------------------------
/** function for boolalpha manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &boolalpha(ios_base &str)
{
    str.setf(ios_base::boolalpha);
    return str;
}
/** function for dec manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &dec(ios_base &str)
{
    str.setf(ios_base::dec, ios_base::basefield);
    return str;
}
/** function for hex manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &hex(ios_base &str)
{
    str.setf(ios_base::hex, ios_base::basefield);
    return str;
}
/** function for internal manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &internal(ios_base &str)
{
    str.setf(ios_base::internal, ios_base::adjustfield);
    return str;
}
/** function for left manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &left(ios_base &str)
{
    str.setf(ios_base::left, ios_base::adjustfield);
    return str;
}
/** function for noboolalpha manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &noboolalpha(ios_base &str)
{
    str.unsetf(ios_base::boolalpha);
    return str;
}
/** function for noshowbase manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &noshowbase(ios_base &str)
{
    str.unsetf(ios_base::showbase);
    return str;
}
/** function for noshowpoint manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &noshowpoint(ios_base &str)
{
    str.unsetf(ios_base::showpoint);
    return str;
}
/** function for noshowpos manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &noshowpos(ios_base &str)
{
    str.unsetf(ios_base::showpos);
    return str;
}
/** function for noskipws manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &noskipws(ios_base &str)
{
    str.unsetf(ios_base::skipws);
    return str;
}
/** function for nouppercase manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &nouppercase(ios_base &str)
{
    str.unsetf(ios_base::uppercase);
    return str;
}
/** function for oct manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &oct(ios_base &str)
{
    str.setf(ios_base::oct, ios_base::basefield);
    return str;
}
/** function for right manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &right(ios_base &str)
{
    str.setf(ios_base::right, ios_base::adjustfield);
    return str;
}
/** function for showbase manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &showbase(ios_base &str)
{
    str.setf(ios_base::showbase);
    return str;
}
/** function for showpos manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &showpos(ios_base &str)
{
    str.setf(ios_base::showpos);
    return str;
}
/** function for showpoint manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &showpoint(ios_base &str)
{
    str.setf(ios_base::showpoint);
    return str;
}
/** function for skipws manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &skipws(ios_base &str)
{
    str.setf(ios_base::skipws);
    return str;
}
/** function for uppercase manipulator
 * \param[in] str The stream
 * \return The stream
 */
inline ios_base &uppercase(ios_base &str)
{
    str.setf(ios_base::uppercase);
    return str;
}
//==============================================================================
/**
 * \class ios
 * \brief Error and state information for all streams
 */
class ios : public ios_base
{
  public:
    /** Create ios with no error flags set */
    ios()
    {
    }

    /** \return null pointer if fail() is true. */
    operator const void *() const
    {
        return !fail() ? reinterpret_cast<const void *>(this) : nullptr;
    }
    /** \return true if fail() else false.  */
    bool operator!() const
    {
        return fail();
    }
    /** \return false if fail() else true.  */
    explicit operator bool() const
    {
        return !fail();
    }
    /** \return The iostate flags for this file. */
    iostate rdstate() const
    {
        return m_iostate;
    }
    /** \return True if no iostate flags are set else false. */
    bool good() const
    {
        return m_iostate == goodbit;
    }
    /** \return true if end of file has been reached else false.
     *
     * Warning: An empty file returns false before the first read.
     *
     * Moral: eof() is only useful in combination with fail(), to find out
     * whether EOF was the cause for failure
     */
    bool eof() const
    {
        return m_iostate & eofbit;
    }
    /** \return true if any iostate bit other than eof are set else false. */
    bool fail() const
    {
        return m_iostate & (failbit | badbit);
    }
    /** \return true if bad bit is set else false. */
    bool bad() const
    {
        return m_iostate & badbit;
    }
    /** Clear iostate bits.
     *
     * \param[in] state The flags you want to set after clearing all flags.
     **/
    void clear(iostate state = goodbit)
    {
        m_iostate = state;
    }
    /** Set iostate bits.
     *
     * \param[in] state Bitts to set.
     **/
    void setstate(iostate state)
    {
        m_iostate |= state;
    }

  private:
    iostate m_iostate = 0;
};
#endif // ios_h
