#ifndef INCLUDED_BOBCAT_CSV_
#define INCLUDED_BOBCAT_CSV_

#include <iosfwd>
#include <vector>
#include <string>
#include <iterator>

#include <bobcat/exception>

namespace FBB
{
    namespace IUO
    {
        enum CSVenum
        {
            INT,
            FLOAT,
            STRING
        };

        template <typename Type, typename ...TypeList>
        struct TypeCat;

        template <typename Type, typename Head, typename ...TypeList>
        struct TypeCat<Type, Head, TypeList...>
        {
            typedef typename TypeCat<Type, TypeList...>::type type;
            enum { cat = TypeCat<Type, TypeList...>::cat };
        };

        template <typename Type>
        struct TypeCat<Type>
        {
            typedef Type type;
            enum { cat = INT };
        };

        template <typename ...TypeList>
        struct TypeCat<float, float, TypeList...>
        {
            typedef float type;
            enum { cat = FLOAT };
        };

        template <typename ...TypeList>
        struct TypeCat<double, double, TypeList...>
        {
            typedef double type;
            enum { cat = FLOAT };
        };

        template <typename ...TypeList>
        struct TypeCat<long double, long double, TypeList...>
        {
            typedef long double type;
            enum { cat = FLOAT };
        };

        template <typename ...TypeList>
        struct TypeCat<std::string, std::string, TypeList...>
        {
            typedef std::string const &type;
            enum { cat = STRING };
        };

        template <typename Type>
        struct CSV
        {
            typedef typename 
            TypeCat<Type, float, double, long double, std::string>::type type;

            enum { 
                cat = 
                   TypeCat<Type, float, double, long double, std::string>::cat
            };
        };

        template <typename Type, size_t category>
        struct Avail
        {
            static Type get(std::string const &field);
        };

        template <typename Type>
        struct Avail<Type, FLOAT>
        {
            static Type get(std::string const &field);
        };

        template <typename Type>
        struct Avail<Type, STRING>
        {
            static Type get(std::string const &field);
        };

        template <typename Type>
        struct CSViteratorData
        {
            Type d_value;
        };

        template <>
        struct CSViteratorData<std::string>
        {
        };

    }   // IUO
} // FBB

namespace FBB
{

struct CSV
{
    enum Mode
    {
        RAW             = 0,
        TRAILINGCOMMA   = 1,
        LINE            = 2
    };

    enum InsertType
    {
        LENGTH,
        SIZE,
        COUNT
    };

    private:
        std::vector<std::string> d_field;
        std::vector<bool>        d_available;
        std::string              d_type;
        Mode                     d_mode = RAW;
        InsertType               d_insertType = LENGTH;
        std::ostream &(CSV::*d_insert)(std::ostream &out) const;

        static std::ostream &(CSV::*s_insert[])(std::ostream &out) const;

    public:
        CSV() = default;
        explicit CSV(std::string const &spec, Mode mode = LINE,
                                              InsertType insertType = LENGTH);

        std::string const &spec() const;
        Mode mode() const;
        InsertType insertType() const;

        void setSpec(std::string const &spec);
        void setMode(Mode mode);
        void setInsertType(InsertType insertType);

        size_t length() const;          // number of specs in d_type
        size_t size() const;            // number of fields
        size_t count() const;           // number of valid fields

        template <typename Type>
        typename IUO::CSV<Type>::type field(size_t idx) const;

        template <typename Type>
        typename IUO::CSV<Type>::type get(size_t idx) const;

        CSV &append(char spec, std::string const &value = "");

        std::string const &operator[](size_t idx) const;

        std::vector<std::string> const &data() const;
        std::vector<bool> const &available() const;

        template <typename Type>
        class const_iterator;

        template <typename Type>
        struct const_reverse_iterator;

        template <typename Type = std::string>
        const_iterator<Type> begin() const;

        template <typename Type = std::string>
        const_iterator<Type> end() const;

        template <typename Type = std::string>
        const_reverse_iterator<Type> rbegin() const;

        template <typename Type = std::string>
        const_reverse_iterator<Type> rend() const;

    private:
        void store(size_t idx, std::string const &value);

        friend std::istream &operator>>(std::istream &in, CSV &csv);
        std::istream &extract(std::istream &in);

        friend std::ostream &operator<<(std::ostream &in, CSV const &csv);
        std::ostream &insertLength(std::ostream &out) const;
        std::ostream &insertSize(std::ostream &out) const;
        std::ostream &insertCount(std::ostream &out) const;
};

template <typename Type>
bool operator==(CSV::const_iterator<Type> const &lhs, 
                CSV::const_iterator<Type> const &rhs);

template <typename Type>
class CSV::const_iterator
:  
    private IUO::CSViteratorData<Type>,
    public  std::iterator<std::bidirectional_iterator_tag, std::string>
{
    friend CSV;
    friend bool operator==<Type>(const_iterator<Type> const &lhs,
                                 const_iterator<Type> const &rhs);

    CSV const *d_csv;
    size_t d_idx;

    public:
        typedef Type const &reference;

        const_iterator() = default;
    
        const_iterator<Type> &operator++();
        const_iterator<Type> operator++(int);
    
        typename IUO::CSV<Type>::type operator*() const;
        Type const *operator->() const;
    
    private:
        const_iterator(CSV const *csv, size_t idx);

        friend class std::reverse_iterator<const_iterator<Type>>;
        const_iterator<Type> &operator--();
};


template <typename Type>
struct CSV::const_reverse_iterator: public 
    std::reverse_iterator<CSV::const_iterator<Type>>
{
    const_reverse_iterator() = default;

    const_reverse_iterator(const_iterator<Type> const &iter)
    :
        std::reverse_iterator<const_iterator<Type>>(iter)
    {}
};







template <typename Type, size_t category>
Type IUO::Avail<Type, category>::get(std::string const &field)
{
    size_t pos;
    long long value = stoll(field, &pos);
    if (pos != field.length())
        throw 1;
    return static_cast<Type>(value);
}

template <typename Type>
Type IUO::Avail<Type, IUO::FLOAT>::get(std::string const &field)
{
    size_t pos;
    long double value = stold(field, &pos);
    if (pos != field.length())
        throw 1;
    return static_cast<Type>(value);
}
template <typename Type>
inline Type IUO::Avail<Type, IUO::STRING>::get(std::string const &field)
{
    if (field.empty())
        throw 1;

    return field;
}
inline std::vector<bool> const &CSV::available() const
{
    return d_available;
}

template <typename Type>
inline typename IUO::CSV<Type>::type CSV::get(size_t idx) const
{
    try
    {
        return IUO::Avail<
                        typename IUO::CSV<Type>::type, 
                        IUO::CSV<Type>::cat
                >::get(d_field[idx]);
    }
    catch (...)
    {
        return Type();
    }
}
template <>
inline typename IUO::CSV<std::string>::type
                CSV::get<std::string>(size_t idx) const
{
    return d_field[idx];
}
                        // must be available before it is used.

template <typename Type>
inline CSV::const_iterator<Type> CSV::begin() const
{
    return const_iterator<Type>(this, 0);
}
template <>
inline CSV::const_iterator<std::string> CSV::begin() const
{
    return const_iterator<std::string>(this, 0);
}
inline std::vector<std::string> const &CSV::data() const
{
    return d_field;
}

template <typename Type>
inline CSV::const_iterator<Type> CSV::end() const
{
    return const_iterator<Type>(this, size());
}
template <>
inline CSV::const_iterator<std::string> CSV::end() const
{
    return const_iterator<std::string>(this, size());
}
template <typename Type>
inline typename IUO::CSV<Type>::type CSV::field(size_t idx) const
{
    try
    {
        return IUO::Avail<typename IUO::CSV<Type>::type, 
                          IUO::CSV<Type>::cat>::get(d_field[idx]);
    }
    catch (...)
    {
       throw Exception() << "Field " << idx << " not available";
    }
}



inline CSV::InsertType CSV::insertType() const
{
    return d_insertType;
}
inline size_t CSV::length() const
{
    return d_type.length();
}
inline CSV::Mode CSV::mode() const
{
    return d_mode;
}
inline std::istream &operator>>(std::istream &in, CSV &csv)
{
    return csv.extract(in);
}
inline std::string const &CSV::operator[](size_t idx) const
{
    return d_field[idx];
}
inline std::ostream &operator<<(std::ostream &out, CSV const &csv)
{
    return (csv.*csv.d_insert)(out);
}
inline CSV::Mode operator|(CSV::Mode lhs, CSV::Mode rhs)
{
    return static_cast<CSV::Mode>(lhs | rhs);
}
template <typename Type>
inline CSV::const_reverse_iterator<Type> CSV::rbegin() const
{
    return const_reverse_iterator<Type>(end<Type>());
}



template <>
inline CSV::const_reverse_iterator<std::string> CSV::rbegin() const
{
    return const_reverse_iterator<std::string>(end<std::string>());
}
template <typename Type>
inline CSV::const_reverse_iterator<Type> CSV::rend() const
{
    return const_reverse_iterator<Type>(begin<Type>());
}
template <>
inline CSV::const_reverse_iterator<std::string> CSV::rend() const
{
    return const_reverse_iterator<std::string>(begin<std::string>());
}
inline void CSV::setMode(Mode mode)
{
    d_mode = mode;
}
inline void CSV::setInsertType(InsertType insertType)
{
    d_insert = s_insert[d_insertType = insertType];
}
inline size_t CSV::size() const
{
    return d_field.size();
}
inline std::string const &CSV::spec() const
{
    return d_type;
}

    // const_iterator members

template <typename Type>
CSV::const_iterator<Type>::const_iterator(CSV const *csv, size_t idx)
:
    d_csv(csv),
    d_idx(idx)   
{}
template <>
inline std::string const *CSV::const_iterator<std::string>::operator->() const
{
    return &d_csv->get<std::string>(d_idx);
}

template <typename Type>
CSV::const_iterator<Type> &CSV::const_iterator<Type>::operator--()
{
    --d_idx;
    return *this;
}
template <typename Type>
inline bool operator==(CSV::const_iterator<Type> const &lhs,
                       CSV::const_iterator<Type> const &rhs)
{
    return lhs.d_idx == rhs.d_idx && lhs.d_csv == rhs.d_csv;
}
template <typename Type>
CSV::const_iterator<Type> &CSV::const_iterator<Type>::operator++()
{
    ++d_idx;
    return *this;
}
template <typename Type>
CSV::const_iterator<Type> CSV::const_iterator<Type>::operator++(int)
{
    const_iterator<Type> tmp(*this);
    ++d_idx;
    return *this;
}
template <typename Type>
inline typename IUO::CSV<Type>::type 
                    CSV::const_iterator<Type>::operator*() const
{
    return d_csv->get<Type>(d_idx);
}
template <typename Type>
inline bool operator!=(CSV::const_iterator<Type> const &lhs,
                       CSV::const_iterator<Type> const &rhs)
{
    return not (lhs == rhs);
}



} // FBB        
#endif





