/*
 * Copyright 2023 Hartmut Seichter
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#ifndef PARADISO_MATRIXBASE_HPP
#define PARADISO_MATRIXBASE_HPP

#include <paradiso/globals.hpp>

namespace paradiso {

template <typename Scalar, typename Derived> struct MatrixBase {

    using value_type = Scalar;
    using reference = Scalar&;
    using const_reference = const Scalar&;
    using pointer = Scalar*;
    using const_pointer = const Scalar*;
    using iterator = Scalar*;
    using const_iterator = const Scalar*;

    constexpr pointer data() { return &derived().data[0]; }
    constexpr const_pointer data() const noexcept { return &derived().data[0]; }

    constexpr iterator begin() noexcept { return this->data(); }
    constexpr iterator end() noexcept { return this->data() + size(); }
    constexpr const_iterator begin() const noexcept { return this->data(); }
    constexpr const_iterator end() const noexcept { return this->data() + size(); }
    constexpr const_iterator cbegin() const noexcept { return this->data(); }
    constexpr const_iterator cend() const noexcept { return this->data() + size(); }

    constexpr Scalar& operator[](std::size_t i) { return this->data()[i]; }
    constexpr const Scalar& operator[](std::size_t i) const {
        return this->data()[i];
    }

    Derived& derived() { return static_cast<Derived&>(*this); }
    const Derived& derived() const {
        return static_cast<const Derived&>(*this);
    }

    std::size_t size() const {
        return std::extent<decltype(Derived::data)>::value;
    }

    constexpr Derived& fill(const Scalar& v) noexcept {
        std::fill(std::begin(*this), std::end(*this), Scalar(v));
        return derived();
    }

    static constexpr Derived
    all(const std::convertible_to<Scalar> auto& v) noexcept {
        Derived d;
        d.fill(v);
        return d;
    }

    static constexpr Derived zero() noexcept { return Derived::all(Scalar{0}); }

    constexpr Scalar squared_norm() const { return dot(*this, *this); }

    constexpr Scalar norm() const { return std::sqrt(squared_norm()); }

    constexpr Derived normalized() const { return *this / this->norm(); }

    constexpr void normalize() { *this /= this->norm(); }

    static constexpr Scalar dot(const auto& a, const auto& b) {
        return std::inner_product(std::begin(a), std::end(a), std::begin(b),
                                  Scalar{0});
    }

    static constexpr Derived lerp(const Derived& a, const Derived& b,
                                  const Scalar& t) {
        return a + (b - a) * t;
    }

    constexpr void operator*=(const Scalar& b) {
        for (auto& e : *this)
            e *= b;
    }
    constexpr void operator/=(const Scalar& b) {
        for (auto& e : *this)
            e /= b;
    }
    constexpr void operator+=(const Scalar& b) {
        for (auto& e : *this)
            e += b;
    }
    constexpr void operator-=(const Scalar& b) {
        for (auto& e : *this)
            e -= b;
    }

    constexpr void operator+=(const Derived& b) {
        std::transform((*this).begin(),(*this).end(),b.begin(),(*this).begin(),std::plus<>());
    }
    constexpr void operator-=(const Derived& b) {
        std::transform((*this).begin(),(*this).end(),b.begin(),(*this).begin(),std::minus<>());
    }

    constexpr const Derived operator*(const Scalar& b) const {
        Derived r(derived());
        for (auto& e : r)
            e *= b;
        return r;
    }
    constexpr const Derived operator/(const Scalar& b) const {
        Derived r(derived());
        for (auto& e : r)
            e /= b;
        return r;
    }
    constexpr const Derived operator+(const Scalar& b) const {
        Derived r(derived());
        for (auto& e : r)
            e += b;
        return r;
    }
    constexpr const Derived operator-(const Scalar& b) const {
        Derived r(derived());
        for (auto& e : r)
            e -= b;
        return r;
    }
};

} // namespace paradiso
#endif