// SPDX-FileCopyrightText: 2025 Kohei Yoshida
//
// SPDX-License-Identifier: MIT

#include <cstdlib>
#include <iostream>
#include <sys/time.h>

#include <mdds/mixed_type_matrix.hpp>
#include <mdds/multi_type_matrix.hpp>

double get_current_time()
{
#ifdef _WIN32
    FILETIME ft;
    __int64 *time64 = reinterpret_cast<__int64 *>(&ft);
    GetSystemTimeAsFileTime(&ft);
    return *time64 / 10000000.0;
#else
    timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + tv.tv_usec / 1000000.0;
#endif
}

class stack_watch
{
public:
    explicit stack_watch() : m_start_time(get_current_time()) {}
    void reset() { m_start_time = get_current_time(); }
    double get_duration() const { return get_current_time() - m_start_time; }

private:
    double m_start_time;
};

using namespace std;

typedef mdds::mixed_type_matrix<std::string, bool> mixed_mx_type;
typedef mdds::multi_type_matrix<mdds::mtm::std_string_traits> multi_mx_type;

namespace {

class sum_all_values
{
    double m_sum;
public:
    sum_all_values() : m_sum(0.0) {}
    void operator() (const multi_mx_type::element_block_node_type& blk)
    {
        if (!blk.data)
            return;

        if (mdds::mtv::get_block_type(*blk.data) != mdds::mtv::element_type_double)
            return;

        using mdds::mtv::double_element_block;
        numeric_element_block::const_iterator it = numeric_element_block::begin(*blk.data);
        numeric_element_block::const_iterator it_end = numeric_element_block::end(*blk.data);
        for (; it != it_end; ++it)
            m_sum += *it;
    }

    double get() const { return m_sum; }
};

class count_all_values
{
    long m_count;
public:
    count_all_values() : m_count(0) {}
    void operator() (const multi_mx_type::element_block_node_type& blk)
    {
        if (!blk.data)
            return;

        if (mdds::mtv::get_block_type(*blk.data) != mdds::mtv::element_type_double)
            return;

        m_count += blk.size;
    }

    long get() const { return m_count; }
};

template<typename _Mx>
void init_manual_loop(_Mx& mx, size_t row_size, size_t col_size)
{
    double val = 0.0;
    for (size_t row = 0; row < row_size; ++row)
    {
        for (size_t col = 0; col < col_size; ++col)
        {
            mx.set(row, col, val);
            val += 0.00001;
        }
    }
}

void init_value_vector(vector<double>& vals, size_t row_size, size_t col_size)
{
    vals.reserve(row_size*col_size);

    double val = 0.0;
    for (size_t row = 0; row < row_size; ++row)
    {
        for (size_t col = 0; col < col_size; ++col)
        {
            vals.push_back(val);
            val += 0.00001;
        }
    }
}

template<typename _Mx>
double sum_manual_loop(const _Mx& mx, size_t row_size, size_t col_size)
{
    double sum = 0.0;
    for (size_t row = 0; row < row_size; ++row)
        for (size_t col = 0; col < col_size; ++col)
            sum += mx.get_numeric(row, col);

    return sum;
}

double sum_iterator(const mixed_mx_type& mx)
{
    double sum = 0.0;
    mixed_mx_type::const_iterator it = mx.begin(), it_end = mx.end();
    for (; it != it_end; ++it)
    {
        if (it->m_type == mdds::element_numeric)
            sum += it->m_numeric;
    }
    return sum;
}

}

void perf_construction()
{
    cout << "---" << endl;
    size_t row_size = 20000, col_size = 8000;
    {
        stack_watch sw;
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_zero);
        cout << "construction filled zero: " << sw.get_duration() << " sec (mixed_type_matrix, filled)" << endl;
    }

    {
        stack_watch sw;
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_sparse_zero);
        cout << "construction filled zero: " << sw.get_duration() << " sec (mixed_type_matrix, sparse)" << endl;
    }

    {
        stack_watch sw;
        multi_mx_type mx(row_size, col_size, 0.0);
        cout << "construction filled zero: " << sw.get_duration() << " sec (multi_type_matrix)" << endl;
    }

    {
        stack_watch sw;
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_empty);
        cout << "construction filled empty: " << sw.get_duration() << " sec (mixed_type_matrix, filled)" << endl;
    }

    {
        stack_watch sw;
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_sparse_empty);
        cout << "construction filled empty: " << sw.get_duration() << " sec (mixed_type_matrix, sparse)" << endl;
    }

    {
        stack_watch sw;
        multi_mx_type mx(row_size, col_size);
        cout << "construction filled empty: " << sw.get_duration() << " sec (multi_type_matrix)" << endl;
    }
}

void perf_insertion()
{
    cout << "---" << endl;
    size_t row_size = 10000;
    size_t col_size = 1000;
    {
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_zero);
        stack_watch sw;
        init_manual_loop(mx, row_size, col_size);
        cout << "insertion via loop: " << sw.get_duration() << " sec (mixed_type_matrix, filled zero)" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size);
        stack_watch sw;
        init_manual_loop(mx, row_size, col_size);
        cout << "insertion via loop: " << sw.get_duration() << " sec (multi_type_matrix, init empty)" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size, 0.0);
        stack_watch sw;
        init_manual_loop(mx, row_size, col_size);
        cout << "insertion via loop: " << sw.get_duration() << " sec (multi_type_matrix, init zero)" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size);
        std::vector<double> vals;

        stack_watch sw;
        init_value_vector(vals, row_size, col_size);
        mx.set(0, 0, vals.begin(), vals.end());
        cout << "insertion via single set call: " << sw.get_duration() << " sec (multi_type_matrix, init empty, value initialization included)" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size);
        std::vector<double> vals;
        init_value_vector(vals, row_size, col_size);

        stack_watch sw;
        mx.set(0, 0, vals.begin(), vals.end());
        cout << "insertion via single set call: " << sw.get_duration() << " sec (multi_type_matrix, init empty, value initialization excluded)" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size, 0.0);
        std::vector<double> vals;
        vals.reserve(row_size*col_size);

        stack_watch sw;
        init_value_vector(vals, row_size, col_size);
        mx.set(0, 0, vals.begin(), vals.end());
        cout << "insertion via single set call: " << sw.get_duration() << " sec (multi_type_matrix, init zero, value initialization included)" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size, 0.0);
        std::vector<double> vals;
        init_value_vector(vals, row_size, col_size);

        stack_watch sw;
        mx.set(0, 0, vals.begin(), vals.end());
        cout << "insertion via single set call: " << sw.get_duration() << " sec (multi_type_matrix, init zero, value initialization excluded)" << endl;
    }
}

void perf_sum_all_values()
{
    cout << "---" << endl;
    size_t row_size = 10000;
    size_t col_size = 1000;
    {
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_zero);
        init_manual_loop(mx, row_size, col_size);

        {
            stack_watch sw;
            double sum = sum_manual_loop(mx, row_size, col_size);
            cout << "sum all values (" << sum << ") : " << sw.get_duration() << " sec (mixed_type_matrix, manual loop)" << endl;
        }

        {
            stack_watch sw;
            double sum = sum_iterator(mx);
            cout << "sum all values (" << sum << ") : " << sw.get_duration() << " sec (mixed_type_matrix, iterator)" << endl;
        }

        {
            stack_watch sw;
            long count = 0;
            for (size_t row = 0; row < row_size; ++row)
                for (size_t col = 0; col < col_size; ++col)
                {
                    if (mx.get_type(row, col) == mdds::element_numeric)
                        ++count;
                }

            cout << "count all values (" << count << ") : " << sw.get_duration() << " sec (mixed_type_matrix, manual loop)" << endl;
        }

        {
            stack_watch sw;
            long count = 0;
            mixed_mx_type::const_iterator it = mx.begin(), it_end = mx.end();
            for (; it != it_end; ++it)
            {
                if (it->m_type == mdds::element_numeric)
                    ++count;
            }

            cout << "count all values (" << count << ") : " << sw.get_duration() << " sec (mixed_type_matrix, iterator)" << endl;
        }
    }

    {
        multi_mx_type mx(row_size, col_size, 0.0);
        init_manual_loop(mx, row_size, col_size);

        {
            stack_watch sw;
            double sum = sum_manual_loop(mx, row_size, col_size);
            cout << "sum all values (" << sum << ") : " << sw.get_duration() << " sec (multi_type_matrix, manual loop)" << endl;
        }

        {
            stack_watch sw;
            sum_all_values func;
            mx.walk(func);
            cout << "sum all values (" << func.get() << ") : " << sw.get_duration() << " sec (multi_type_matrix, walk)" << endl;
        }

        {
            stack_watch sw;
            count_all_values func;
            mx.walk(func);
            cout << "count all values (" << func.get() << ") : " << sw.get_duration() << " sec (multi_type_matrix, walk)" << endl;
        }
    }
}

void perf_sum_all_values_multi_block()
{
    cout << "---" << endl;
    size_t row_size = 10000;
    size_t col_size = 1000;
    double step = 0.00001;
    {
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_zero);
        double val = 0.0;
        for (size_t row = 0; row < row_size; ++row)
        {
            for (size_t col = 0; col < col_size; ++col)
            {
                if ((col % 3) == 0)
                {
                    mx.set_empty(row, col);
                }
                else
                {
                    mx.set(row, col, val);
                    val += step;
                }
            }
        }

        {
            stack_watch sw;
            double sum = sum_manual_loop(mx, row_size, col_size);
            cout << "sum all values multi-block (" << sum << ") : " << sw.get_duration() << " sec (mixed_type_matrix, manual loop)" << endl;
        }

        {
            stack_watch sw;
            double sum = sum_iterator(mx);
            cout << "sum all values multi-block (" << sum << ") : " << sw.get_duration() << " sec (mixed_type_matrix, iterator)" << endl;
        }

        {
            stack_watch sw;
            long count = 0;
            for (size_t row = 0; row < row_size; ++row)
                for (size_t col = 0; col < col_size; ++col)
                {
                    if (mx.get_type(row, col) == mdds::element_numeric)
                        ++count;
                }

            cout << "count all values multi-block (" << count << ") : " << sw.get_duration() << " sec (mixed_type_matrix, manual loop)" << endl;
        }

        {
            stack_watch sw;
            long count = 0;
            mixed_mx_type::const_iterator it = mx.begin(), it_end = mx.end();
            for (; it != it_end; ++it)
            {
                if (it->m_type == mdds::element_numeric)
                    ++count;
            }

            cout << "count all values multi-block (" << count << ") : " << sw.get_duration() << " sec (mixed_type_matrix, iterator)" << endl;
        }
    }

    {
        multi_mx_type mx(row_size, col_size);
        double val = 0.0;
        vector<double> vals;
        vals.reserve(row_size);
        for (size_t col = 0; col < col_size; ++col)
        {
            if ((col % 3) == 0)
                continue;

            vals.clear();
            for (size_t row = 0; row < row_size; ++row)
            {
                vals.push_back(val);
                val += step;
            }

            mx.set(0, col, vals.begin(), vals.end());
        }

        {
            stack_watch sw;
            double sum = sum_manual_loop(mx, row_size, col_size);
            cout << "sum all values multi-block (" << sum << ") : " << sw.get_duration() << " sec (multi_type_matrix, manual loop)" << endl;
        }

        {
            stack_watch sw;
            sum_all_values func;
            mx.walk(func);
            cout << "sum all values multi-block (" << func.get() << ") : " << sw.get_duration() << " sec (multi_type_matrix, walk)" << endl;
        }

        {
            stack_watch sw;
            count_all_values func;
            mx.walk(func);
            cout << "count all values multi-block (" << func.get() << ") : " << sw.get_duration() << " sec (multi_type_matrix, walk)" << endl;
        }
    }
}

void perf_init_with_value()
{
    cout << "---" << endl;
    size_t row_size = 10000;
    size_t col_size = 2000;
    double val = 12.3;
    {
        stack_watch sw;
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_zero);
        for (size_t row = 0; row < row_size; ++row)
            for (size_t col = 0; col < col_size; ++col)
                mx.set(row, col, val);
        cout << "init with value: " << sw.get_duration() << " sec (mixed_type_matrix, filled zero, manual loop)" << endl;
    }

    {
        stack_watch sw;
        multi_mx_type mx(row_size, col_size, 0.0);
        for (size_t row = 0; row < row_size; ++row)
            for (size_t col = 0; col < col_size; ++col)
                mx.set(row, col, val);
        cout << "init with value: " << sw.get_duration() << " sec (multi_type_matrix, manual loop)" << endl;
    }

    {
        stack_watch sw;
        multi_mx_type(row_size, col_size, val);
        cout << "init with value: " << sw.get_duration() << " sec (multi_type_matrix, constructor)" << endl;
    }

    {
        stack_watch sw;
        multi_mx_type mx(row_size, col_size);
        vector<double> vals(row_size*col_size, val);
        mx.set(0, 0, vals.begin(), vals.end());
        cout << "init with value: " << sw.get_duration() << " sec (multi_type_matrix, vector + set)" << endl;
    }
}

void perf_heap_vs_array()
{
    cout << "---" << endl;
    size_t row_size = 20000, col_size = 3000;
    {
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_empty);
        init_manual_loop(mx, row_size, col_size);
        stack_watch sw;
        double sum = 0.0;
        for (size_t row = 0; row < row_size; ++row)
            for (size_t col = 0; col < col_size; ++col)
                sum += mx.get_numeric(row, col);
        cout << "sum with values on the heap (mixed_type_matrix): " << sw.get_duration() << " sec (" << sum << ")" << endl;
    }

    {
        mixed_mx_type mx(row_size, col_size, mdds::matrix_density_filled_zero);
        init_manual_loop(mx, row_size, col_size);
        stack_watch sw;
        double sum = 0.0;
        for (size_t row = 0; row < row_size; ++row)
            for (size_t col = 0; col < col_size; ++col)
                sum += mx.get_numeric(row, col);
        cout << "sum with array values (mixed_type_matrix): " << sw.get_duration() << " sec (" << sum << ")" << endl;
    }

    {
        multi_mx_type mx(row_size, col_size, 0.0);
        init_manual_loop(mx, row_size, col_size);
        sum_all_values func;
        stack_watch sw;
        mx.walk(func);
        double sum = func.get();
        cout << "sum with array values (multi_type_matrix): " << sw.get_duration() << " sec (" << sum << ")" << endl;
    }
}

int main()
{
    perf_construction();
    perf_insertion();
    perf_sum_all_values();
    perf_sum_all_values_multi_block();
    perf_init_with_value();
    perf_heap_vs_array();
    return EXIT_SUCCESS;
}
