#ifndef INCLUDED_BOBCAT_CSVTABINS_
#define INCLUDED_BOBCAT_CSVTABINS_

#include <iosfwd>
#include <sstream>
#include <vector>
#include <string>
#include <iomanip>

#include <bobcat/string>

#include <bobcat/fmt>
#include <bobcat/sep>

namespace FBB
{
    // CSVTabIns objects are returned by members of CSVTable, and
    // allow for insertions into a line of the table. When the insertions
    // end CSVTabIns's destructor is called, ending the line. Its operator()
    // member is called by CSVTable's operator().

class CSVTabIns
{
    friend class CSVTable;

            // insert values of insertable types
    template <typename Type>
    friend CSVTabIns &operator<<(CSVTabIns &tab, Type const &value);    // 1.f

            // insert FMTs specifying LEFT, RIGHT, or CENTER
    friend CSVTabIns &operator<<(CSVTabIns &tab, FMT const &fmt);       // 2.f

            // insert FBB::left, FMT::right, FMT::center in the next col
    friend CSVTabIns &operator<<(CSVTabIns &tab, FMT::Align align);     // 3.f

            // writes a hline in the next column (calls hline())
    friend CSVTabIns &operator<<(CSVTabIns &tab, FMT::FMTHline);        // 4.f

            // change the separator while inserting (calls sep())
    friend CSVTabIns &operator<<(CSVTabIns &tab, Sep const &sep);       // 5.f

            // insert manipulators like std::left and std::right
    friend CSVTabIns &operator<<(CSVTabIns &tab,                        // 6.f
                                std::ios_base &(*func)(std::ios_base &));

    unsigned *d_tabIdx;
    unsigned d_idx;
    std::ostream *d_out;
    std::vector<FMT> const &d_format;
    std::string d_sep;

    FMT  d_extraFMT;
    bool d_useExtraFMT;                 // use d_extraFMT instead of d_format
    bool d_more;

    public:
        CSVTabIns(CSVTabIns const &other) = delete;
        ~CSVTabIns();

    private:
        CSVTabIns(unsigned *idx, std::ostream &out,
                  std::vector<FMT> const &format,
                  unsigned startIdx, std::string const &sep, bool more);

                                                    // inserts 1 row, via
        void operator()(std::string const &text);   // CSVTable.operator()

        template <typename Type>
        std::string centered(FMT const &fmt,                        //     .f
                             unsigned fieldWidth, Type const &value);

                                            // total width incluing seps
                                            // (not using end's sep) from cols
        unsigned width(unsigned begin, unsigned end) const;     //      1.cc
        unsigned width(FMT const &fmt) const;       // see README       2.cc

        template <typename Type>                  // also updates d_idx
        void insertFormatted(FMT const &fmt,        //                     .f
                             Type const &value);

        template <typename Type>
        CSVTabIns &insert(Type const &value);       //                    1.f

        CSVTabIns &sep(Sep const &sep);             //                     .f

        CSVTabIns &hline(FMT const &fmt);   // insert hline over fmt.nCols()

            // inserting FMT in col. d_idx means: if nCols() == 1: use the
            // FMT's width as precision, then use d_format[d_idx]'s width, and
            // set that format at d_extraFMT if nCols > 1: use the combined
            // width of the next nCols columns as width.

        CSVTabIns &insert(FMT const &fmt);          //                    2.cc
        CSVTabIns &insert(FMT::Align align);        //                    3.cc
        CSVTabIns &insert(std::ios_base &(*func)(std::ios_base &)); //    6.cc
};

inline CSVTabIns &CSVTabIns::sep(Sep const &sep)
{
    d_sep = sep;
    return *this;
}
template <typename Type>
CSVTabIns &CSVTabIns::insert(Type const &value)
{
                                            // beyond the table: insert as-is
    if (d_idx == d_format.size())           // but width a separator
        *d_out << d_sep << value;

    else                                    // value is needed for centering
        insertFormatted(d_useExtraFMT ? d_extraFMT : d_format[d_idx],
                        value);             // also: updates d_idx

    d_useExtraFMT = false;
    return *this;
}
template <typename Type>
std::string CSVTabIns::centered(FMT const &fmt,
                                unsigned fieldWidth, Type const &value)
{
    using namespace std;

    ostringstream stream;                  // format value in 'stream'

    stream.copyfmt(*d_out);                     // xfer d_out's format to
    stream.fill(d_out->fill());                 // stream

    if (fmt.d_precision < fieldWidth)           // maybe set the precision
        stream.precision(fmt.d_precision);

    stream << setw(fieldWidth) << value;        // convert value to string

    string ret = String::trim(stream.str()); // remove surrounding spaces

                                                // extra spaces needed
    if (unsigned length = ret.length();  length < fieldWidth)
    {
                                                // prefix this #spaces
        ret.insert(ret.begin(), (fieldWidth - length) / 2, ' ');
        ret.resize(fieldWidth, ' ');
    }


    return ret;
}
template <typename Type>
void CSVTabIns::insertFormatted(FMT const &fmt, Type const &value)
{
    if (d_idx == d_format.size())           // beyond the last column:
    {
        unsigned width = d_out->width();    // get the current width
                                            // insert 'value' as-is
        *d_out << std::setw(0) << d_sep << std::setw(width) << value;
        return;
    }

                    // before the last column

    unsigned fieldWidth = width(fmt);       // determine the field width

                                            // when centering: plain insertion
    if (fmt.align() == FMT::CENTER)         // because centered fills the full
        *d_out << centered(fmt, fieldWidth, value); // field width

    else
    {
        *d_out << (fmt.align() == FMT::RIGHT ? std::right : std::left);

        if (fmt.precision() < fieldWidth)
            d_out->precision(fmt.precision());

        *d_out <<  std::setw(fieldWidth) << value;
    }

    d_idx += fmt.nCols();

    if (d_idx != d_format.size())
        *d_out << d_sep;
}

template <typename Type>
inline CSVTabIns &operator<<(CSVTabIns &tab, Type const &value)
{
    return tab.insert(value);
}

        // this one is called from CSVTable::operator CSVTabIns():
        //
template <typename Type>
inline CSVTabIns &operator<<(CSVTabIns &&tab, Type const &value)
{
    return tab << value;
}
inline CSVTabIns &operator<<(CSVTabIns &tab, FMT const &fmt)
{
    return tab.insert(fmt);
}

inline CSVTabIns &operator<<(CSVTabIns &&tab, FMT const &fmt)
{
    return tab << fmt;
}
inline CSVTabIns &operator<<(CSVTabIns &tab, FMT::Align align)
{
    return tab.insert(align);
}

inline CSVTabIns &operator<<(CSVTabIns &&tab, FMT::Align align)
{
    return tab << align;
}
inline CSVTabIns &operator<<(CSVTabIns &tab, FMT::FMTHline hline)
{
    return tab << (*hline)(1);      // insert hline in the next column
}

inline CSVTabIns &operator<<(CSVTabIns &&tab, FMT::FMTHline hline)
{
    return tab << hline;
}
inline CSVTabIns &operator<<(CSVTabIns &tab, Sep const &sep)
{
    return tab.sep(sep);
}

inline CSVTabIns &operator<<(CSVTabIns &&tab, Sep const &sep)
{
    return tab << sep;
}
inline CSVTabIns &operator<<(CSVTabIns &tab,
                            std::ios_base &(*func)(std::ios_base &))
{
    return tab.insert(func);
}

inline CSVTabIns &operator<<(CSVTabIns &&tab,
                            std::ios_base &(*func)(std::ios_base &))
{
    return tab << func;
}


} // FBB
#endif
