/**
 * Copyright (c) 2011-2020 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"


namespace sdfat {


/**
 * \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
/** 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() : m_iostate(0) {}

  /** \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;
};


}; // namespace sdfat


#endif  // ios_h
