#pragma once
#include <format>
#include <complex>
#include <iostream>
#include <ranges>
#include <vector>

#include "type.hpp"

// FORMATING
template<typename T, class CharT=char>
concept FormatableRange = std::ranges::forward_range<T> && std::formattable<std::ranges::range_value_t<T>, CharT>;

/**
 * @brief Format ranges
 * @details
 *  Format string accepts c = {curly brackets}, b = [box brackets], a = <angle brackets>, r = (round brackets)
 * @note With some clang lib update, this became obsolete!
 */
/*template<typename U, class CharT> requires FormatableRange<U, CharT>*/
/*struct std::formatter<U, CharT> {*/
/*    using T = std::ranges::range_value_t<U>;*/
/*    constexpr auto parse(std::format_parse_context& ctx){*/
/*        auto pos = ctx.begin();*/
/*        while (pos != ctx.end() && *pos != '}') {*/
/*            if (*pos == 'a') { useChar = 0; }       // <a>ngle*/
/*            else if (*pos == 'b') { useChar = 1; }  // [b]ox*/
/*            else if (*pos == 'c') { useChar = 2; }  // {c}urly*/
/*            else if (*pos == 'p') { useChar = 3; }  // (r)ound*/
/*            else {*/
/*                throw std::format_error("Invalid format args for forward range.");*/
/*            }*/
/*            ++pos;*/
/*        }*/
/*        return pos;  // expect `}` at this position, otherwise its an error*/
/*    }*/
/*    auto format(const U& v, auto& ctx) const {*/
/*        auto out = ctx.out();*/
/*        char c = openChars.at(useChar);*/
/*        out = std::format_to(out, "{}", c);*/
/**/
/*        auto it = v.begin();*/
/*        if (it != v.end()) {*/
/*            out = std::format_to(out, "{}", *it);*/
/*        }*/
/*        it++;*/
/*        for (; it != v.end(); it++) {*/
/*            out = std::format_to(out, ", {}", *it);*/
/*        }*/
/*        c = closeChars.at(useChar);*/
/*        return std::format_to(out, "{}", c);*/
/*        // return out;*/
/*    }*/
/*    public:*/
/*    size_t useChar = 2;  */
/*    static constexpr std::string_view openChars = "<[{()";*/
/*    static constexpr std::string_view closeChars{">]})"};*/
/*};*/


/**
 * @brief Format complex numbers
 */
template<typename U>
struct std::formatter<std::complex<U>> : std::formatter<U> {
    using T = std::complex<U>;
    // use default formater for the value type
    using std::formatter<U>::parse;

    /*constexpr auto parse(std::format_parse_context& ctx){*/
    /*    auto pos = ctx.begin();*/
    /*    while (pos != ctx.end() && *pos != '}') {*/
    /*        if (*pos == 'i') { useImag = true; }*/
    /*        ++pos;*/
    /*    }*/
    /*    return pos;*/
    /*}*/
    auto format(const T& v, auto& ctx) const {
        auto out = ctx.out();
        if (useImag) {
            out = std::format_to(out, "{}+{}i", v.real(), v.imag());
        }
        else {
            out = std::format_to(out, "({}, {})", v.real(), v.imag());
        }
        return out;
    }
    bool useImag = false;
};

// Make every class with public std::string format() const member formatable
template <typename T>
concept HasFormat = requires(const T obj) {
    { obj.format() } -> std::convertible_to<std::string>;
};
template<HasFormat T>
struct std::formatter<T> {
    constexpr auto parse(std::format_parse_context& ctx) {
        return ctx.begin();
    }
    auto format(const T& obj, auto& ctx) const {
        return std::format_to(ctx.out(), "{}", obj.format());
    }
};


// OTHER
template<typename T, typename string_type>
void printTensor(const T& t, string_type name="") {
    std::cout << name << "(" << t.dimensions() << ") = \n" << t << "\n" << std::endl;
}

template<typename T, typename string_type>
void printTensorDims(const T& t, string_type name="") {
    std::cout << name << "(" << t.dimensions() << ")" << std::endl;
}


template<typename T>
void throwIfNaN(const std::complex<T>& v, std::string_view name) {
    if (std::isnan(v.real())) {
        throw std::invalid_argument(std::format("Real part of variable '{}' is NaN", name));
    } 
    else if (std::isnan(v.imag())) {
        throw std::invalid_argument(std::format("Imaginary part of variable '{}' is NaN", name));
    }
}

template<typename T>
void throwIfLessThan(const T& v, const T& compare, std::string_view name) {
    if (v < compare) {
        throw std::invalid_argument(std::format("Variable '{}' must not be less than '{}', but is '{}'", name, v, compare));
    } 
}

