/***************************************************************************
* Copyright (c) 2016, Johan Mabille, Sylvain Corlay and Wolf Vollprecht    *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XTENSOR_CSV_HPP
#define XTENSOR_CSV_HPP

#include <exception>
#include <istream>
#include <iterator>
#include <sstream>
#include <string>
#include <utility>

#include "xtensor.hpp"

namespace xt
{

    /**************************************
     * load_csv and dump_csv declarations *
     **************************************/

    template <class T, class A = std::allocator<T>>
    using xcsv_tensor = xtensor_container<std::vector<T, A>, 2, layout_type::row_major>;

    template <class T, class A = std::allocator<T>>
    xcsv_tensor<T, A> load_csv(std::istream& stream);

    template <class E>
    void dump_csv(std::ostream& stream, const xexpression<E>& e);

    /*****************************************
     * load_csv and dump_csv implementations *
     *****************************************/

    namespace detail
    {
        template <class T>
        inline T lexical_cast(const std::string& cell)
        {
            T res;
            std::istringstream iss(cell);
            iss >> res;
            return res;
        }

        template <>
        inline std::string lexical_cast(const std::string& cell)
        {
            size_t first = cell.find_first_not_of(' ');
            if (first == std::string::npos)
                return cell;

            size_t last = cell.find_last_not_of(' ');
            return cell.substr(first, last==std::string::npos?cell.size():last+1);
        }

        template <>
        inline float lexical_cast<float>(const std::string& cell) { return std::stof(cell); }

        template <>
        inline double lexical_cast<double>(const std::string& cell) { return std::stod(cell); }

        template <>
        inline long double lexical_cast<long double>(const std::string& cell) { return std::stold(cell); }

        template <>
        inline int lexical_cast<int>(const std::string& cell) { return std::stoi(cell); }

        template <>
        inline long lexical_cast<long>(const std::string& cell) { return std::stol(cell); }

        template <>
        inline long long lexical_cast<long long>(const std::string& cell) { return std::stoll(cell); }

        template <>
        inline unsigned int lexical_cast<unsigned int>(const std::string& cell) { return static_cast<unsigned int>(std::stoul(cell)); }

        template <>
        inline unsigned long lexical_cast<unsigned long>(const std::string& cell) { return std::stoul(cell); }

        template <>
        inline unsigned long long lexical_cast<unsigned long long>(const std::string& cell) { return std::stoull(cell); }

        template <class ST, class T, class OI>
        ST load_csv_row(std::istream& row_stream, OI output, std::string cell)
        {
            ST length = 0;
            while (std::getline(row_stream, cell, ','))
            {
                *output++ = lexical_cast<T>(cell);
                ++length;
            }
            return length;
        }
    }

    /**
     * @brief Load tensor from CSV.
     * 
     * Returns an \ref xexpression for the parsed CSV
     * @param stream the input stream containing the CSV encoded values
     */
    template <class T, class A>
    xcsv_tensor<T, A> load_csv(std::istream& stream)
    {
        using tensor_type = xcsv_tensor<T, A>;
        using storage_type = typename tensor_type::storage_type;
        using size_type = typename tensor_type::size_type;
        using inner_shape_type = typename tensor_type::inner_shape_type;
        using inner_strides_type = typename tensor_type::inner_strides_type;
        using output_iterator = std::back_insert_iterator<storage_type>;

        storage_type data;
        size_type nbrow = 0, nbcol = 0;
        {
            output_iterator output(data);
            std::string row, cell;
            while (std::getline(stream, row))
            {
                std::stringstream row_stream(row);
                nbcol = detail::load_csv_row<size_type, T, output_iterator>(row_stream, output, cell);
                ++nbrow;
            }
        }
        inner_shape_type shape = {nbrow, nbcol};
        inner_strides_type strides;  // no need for initializer list for stack-allocated strides_type
        size_type data_size = compute_strides(shape, layout_type::row_major, strides);
        // Sanity check for data size.
        if (data.size() != data_size)
        {
            throw std::runtime_error("Inconsistent row lengths in CSV");
        }
        return tensor_type(std::move(data), std::move(shape), std::move(strides));
    }

    /**
     * @brief Dump tensor to CSV.
     * 
     * @param stream the output stream to write the CSV encoded values
     * @param e the tensor expression to serialize
     */
    template <class E>
    void dump_csv(std::ostream& stream, const xexpression<E>& e)
    {
        using size_type = typename E::size_type;
        const E& ex = e.derived_cast();
        if (ex.dimension() != 2)
        {
            throw std::runtime_error("Only 2-D expressions can be serialized to CSV");
        }
        size_type nbrows = ex.shape()[0], nbcols = ex.shape()[1];
        auto st = ex.stepper_begin(ex.shape());
        for (size_type r = 0; r != nbrows; ++r)
        {
            for (size_type c = 0; c != nbcols; ++c)
            {
                stream << *st;
                if (c != nbcols - 1)
                {
                    st.step(1);
                    stream << ',';
                }
                else
                {
                    st.reset(1);
                    st.step(0);
                    stream << std::endl;
                }
            }
        }
    }
}

#endif
