#ifndef INCLUDED_BOBCAT_TABLESUPPORT_
#define INCLUDED_BOBCAT_TABLESUPPORT_

#include <ostream>
#include <vector>
#include <unordered_map>
#include <numeric>

#include <bobcat/align>

namespace FBB
{

class TableSupport
{
    public:
        struct Field
        {
            size_t width;       // width of a field
            size_t type;        // handlingtype (ColumnType)
        };

        enum ColumnType
        {
            SKIP        = 0,
            USE         = 1 << 0,
            LEFT_FULL   = 1 << 1,
            RIGHT_FULL  = 1 << 2,
            LEFT_MID    = 1 << 3,
            RIGHT_MID   = 1 << 4
        };

                // define the columns to provide with a (partial) hline
        struct HLine
        {
            size_t d_row;
            size_t d_begin;
            size_t d_end;
            size_t d_type;

            HLine(size_t row, size_t begin, size_t end);
            HLine(ColumnType margins, size_t row, size_t begin, size_t end);
        };

        struct const_iterator
        {
            using iterator_category = std::input_iterator_tag;
            using difference_type   = std::ptrdiff_t;
            using value_type        = Field;
            using pointer           = value_type *;
            using reference         = value_type &;

            private:
                TableSupport const &d_support;

                // iterators to vectors in d_elements. The elements of the
                // vectors contain the types of the column (SKIP, USE, etc)
                // to determine the width of an element call
                // separator.width(iterator)
                std::vector<size_t> const *d_vector;
                std::vector<size_t>::const_iterator d_iter;
                mutable Field d_field;

                static std::vector<size_t> s_empty;

            public:
                const_iterator(TableSupport const &support, size_t row,
                                                            bool begin);

                const_iterator &operator++();                           // .f
                bool operator==(const_iterator const &other) const;     // .f
                bool operator!=(const_iterator const &other) const;     // .f

                Field const &operator*() const;                         // .f
                Field const *operator->() const;
        };

    private:
        std::ostream *d_streamPtr;
        size_t d_nRows;                     // # of rows (defined after def())
        size_t d_nColumns;                  // # of cols
        std::vector<Align> const *d_align;  // pointer to alignment info,
                                            // passed to this by setParam,
                                            // (used by Table/TableBuf)
        size_t d_tableWidth;                // total table width
        std::vector<std::string> d_sep;     // a vector of separators. 0:
                                            // before the leftmost col.

                                            // Element types per row per
                                            // column, wrt hlines
        using UMsizeVector =  std::unordered_map<size_t, std::vector<size_t>>;
        using UMValue =   UMsizeVector::value_type;
        using UMIter =   UMsizeVector::const_iterator;

        UMsizeVector d_elements;

    public:
        TableSupport();
        TableSupport(TableSupport &&tmp);

        TableSupport &operator=(TableSupport &&tmp);

        virtual ~TableSupport();            // empty

        void setParam(std::ostream &ostr, size_t rows, size_t nColumns,
                        std::vector<Align> const &align);

        void hline() const;         // called after the last data row      1.f

            // called by Table/TableBuf for each row (0: before 1st data row)
        void hline(size_t row) const;                                   // 2.f

            // same for columns
        void vline() const;                                             // 1.f
        void vline(size_t col) const;                                   // 2.f

        size_t width() const;               // total width of the table     .f

    protected:

        const_iterator begin(size_t row) const;                         // .f
        const_iterator end(size_t row) const;                           // .f

        size_t colWidth(size_t col) const;  // width of a column and:      .f
        size_t sepWidth(size_t col) const;  // of a separator              .f
                                            // (0: leftmost)

        size_t nColumns() const;            // number of columns           .f
        size_t nRows() const;               // number of rows              .f

        std::ostream &out() const;          // stream to insert into       .f
                                            // the table

                                            // col. alignmnts (not separators)
        std::vector<Align> const &align() const;                        // .f
        std::vector<std::string> const &sep() const;    // separators      .f

    private:
            // called for each row (0: before 1st data row)
        virtual void v_hline(size_t row) const;
        virtual void v_hline() const;         // called after the last data row

            // same for columns
        virtual void v_vline(size_t col) const;
        virtual void v_vline() const;


        size_t width(size_t idx) const;

            // return indices in d_elements of left separator element, etc.
        static size_t leftSeparator(size_t column);                     // .f
        static size_t element(size_t column);                           // .f
        static size_t rightSeparator(size_t column);                    // .f

        static void leftType(size_t *target, size_t type);              // .f
        static void rightType(size_t *target, size_t type);             // .f

    friend const_iterator;
    friend TableSupport &operator<<(TableSupport &support, size_t);
    friend TableSupport &operator<<(TableSupport &support,
                                                std::string const &sep);
    friend TableSupport &operator<<(TableSupport &support,
                                                HLine const &hline);
};

inline std::vector<Align> const &FBB::TableSupport::align() const
{
    return *d_align;
}
inline FBB::TableSupport::const_iterator FBB::TableSupport::begin(size_t row) const
{
    const_iterator ret(*this, row, true);
    return ret;
}
inline size_t FBB::TableSupport::colWidth(size_t col) const
{
    return col < d_align->size() ? (*d_align)[col].col() : 0;
}
inline FBB::TableSupport::const_iterator FBB::TableSupport::end(size_t row) const
{
    const_iterator ret(*this, row, false);
    return ret;
}
inline void FBB::TableSupport::hline() const
{
    v_hline();
}
inline void FBB::TableSupport::hline(size_t row) const
{
    v_hline(row);
}
inline size_t FBB::TableSupport::nColumns() const
{
    return d_nColumns;
}
inline size_t FBB::TableSupport::nRows() const
{
    return d_nRows;
}
inline bool FBB::TableSupport::const_iterator::operator==(
        FBB::TableSupport::const_iterator const &other) const
{
    return d_iter == other.d_iter;
}
inline
FBB::TableSupport::const_iterator &FBB::TableSupport::const_iterator::operator++()
{
    ++d_iter;
    return *this;
}
inline bool FBB::TableSupport::const_iterator::operator!=(
    FBB::TableSupport::const_iterator const &other) const
{
    return not (*this == other);
}
inline FBB::TableSupport::Field const
                            &FBB::TableSupport::const_iterator::operator*() const
{
    return *operator->();
}
inline std::ostream &FBB::TableSupport::out() const
{
    return *d_streamPtr;
}
inline std::vector<std::string> const &FBB::TableSupport::sep() const
{
    return d_sep;
}
inline size_t FBB::TableSupport::sepWidth(size_t col) const
{
    return col < d_sep.size() ? d_sep[col].length() : 0;
}
inline void FBB::TableSupport::vline() const
{
    v_vline();
}
inline void FBB::TableSupport::vline(size_t col) const
{
    v_vline(col);
}
inline size_t FBB::TableSupport::width() const
{
    return d_tableWidth;
}

    // Free Functions

inline FBB::TableSupport::ColumnType operator|(FBB::TableSupport::ColumnType lhs,
                                          FBB::TableSupport::ColumnType rhs)
{
    return static_cast<FBB::TableSupport::ColumnType>(
                    static_cast<int>(lhs) | static_cast<int>(rhs));
}

TableSupport &operator<<(TableSupport &support, size_t);
TableSupport &operator<<(TableSupport &support, std::string const &sep);
TableSupport &operator<<(TableSupport &support,
                                        TableSupport::HLine const &hline);
} // FBB


#endif
