#ifndef INCLUDED_BOBCAT_FMT_
#define INCLUDED_BOBCAT_FMT_

#include <ostream>
#include <string>

#include <bobcat/exception>

namespace FBB
{

struct FMT
{
                                        // update s_align (data.cc)
    enum Align                          // when this enum is altered
    {
        UNUSED,                         // extraFMT's d_align is not used
                                        // when an ios_base manipulator
                                        // has just been inserted.

        CENTER,                         // FMTFunCH
        HLINE,

        LEFT,                           // FMTFunLR
        RIGHT,
    };

    using FMTFun = FMT (*)(unsigned, unsigned);
    using FMTHline = FMT (*)(unsigned);

    friend class CSVTabDef;
    friend class CSVTabIns;

    friend std::ostream &operator<<(std::ostream &out, FMT const &fmt);

            // when inserting and used with arguments the current column fmt
            // is altered for that insertion, when defining: the next column
            // is defined as specified.
            // When used without arguments and inserting then the alignment
            // of then next column is altered as specified. Instead of
            // FBB::left and FBB::right std::left and std::right can also be
            // used.
    friend FMT center(unsigned width, unsigned precision);              // 1.f
    friend FMT left(unsigned width, unsigned precision);                // 1.f
    friend FMT right(unsigned width, unsigned precision);               // 1.f

    friend FMT center(std::string const &width, unsigned precision);    // 2.f
    friend FMT left(std::string const &width, unsigned precision);      // 2.f
    friend FMT right(std::string const &width, unsigned precision);     // 2.f

    friend FMT hline(unsigned nCols);

            // when inserting:
            // the next nCols (not exceeding d_format.size()) are formatted
            // as specified by fun, using precision ~0U
            // omit 'nCols' to join all remaining columns
    friend FMT join(unsigned nCols, Align align, unsigned precision);

    private:
        Align d_align;

        unsigned d_width = 1;       // width of a single column (nCols == 1)

                                    // if <= d_width then precision is also
        unsigned d_precision = ~0U; // used

                                    // if > 1: use the widths of subsequent cols
        unsigned d_nCols = 1;       // + the width of in =-between separators

        static char const *s_align[];       // Align labels
        static FMTFun s_lrcFun[];           // L,R,C function given Align spec

    public:
        Align align() const;

        unsigned width() const;
        unsigned precision() const;
        unsigned nCols() const;

        static FMTFun lrcFun(Align align);

        static char const *align(Align value);

    private:
        FMT() = default;

        FMT(Align align,  unsigned width, unsigned precision,   //    1.cc
            unsigned nCols = 1);

        std::ostream &insert(std::ostream &out) const;
};

inline FMT::Align FMT::align() const
{
    return d_align;
}
inline unsigned FMT::width() const
{
    return d_width;
}
inline unsigned FMT::precision() const
{
    return d_precision;
}
inline unsigned FMT::nCols() const
{
    return d_nCols;
}
inline std::ostream &FMT::insert(std::ostream &out) const
{
    return out << "align: " << s_align[d_align] <<
                 ", width: " << d_width <<
                 ", precision: " << static_cast<int>(d_precision) <<
                 ", nCols: " << d_nCols;
}
// static
inline char const *FMT::align(FMT::Align value)
{
    return s_align[value];
}

inline std::ostream &operator<<(std::ostream &out, FMT const &fmt)
{
    return fmt.insert(out);
}

inline FMT hline(unsigned nCols = ~0U)

{
    return { FMT::HLINE,  0, ~0U, nCols == 0 ? 1 : nCols };
}

    // when inserting d_size specifies the precision and
    // d_precision is copied from d_format[d_idx].d_precision
    // when defining, 'size' is the width

inline FMT center(unsigned size, unsigned precision = ~0U)
{
    return { FMT::CENTER,  size, precision, 1 };
}
inline FMT left(unsigned size, unsigned precision = ~0U)
{
    return { FMT::LEFT,  size, precision, 1 };
}
inline FMT right(unsigned size, unsigned precision = ~0U)
{
    return { FMT::RIGHT,  size, precision, 1 };
}

inline FMT center(std::string const &size, unsigned precision = ~0U)
{
    return { FMT::CENTER, static_cast<unsigned>(size.length()),
                          precision, 1 };
}
inline FMT left(std::string const &size, unsigned precision = ~0U)
{
    return { FMT::LEFT,  static_cast<unsigned>(size.length()), precision, 1 };
}
inline FMT right(std::string const &size, unsigned precision = ~0U)
{
    return { FMT::RIGHT,  static_cast<unsigned>(size.length()), precision, 1 };
}

FMT join(unsigned nCols, FMT::Align align, unsigned precision = ~0U);  // 1.cc
    // join all remaining columns

inline FMT join(FMT::Align align, unsigned precision = ~0U)
{
    return join(~0U, align, precision);
}

} // FBB

#endif
