// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// BaseMatrix.hpp
//
// Common base class for matrices. Extends the sangi Vector<T> +
// Vector_DIM<T, N> pattern to matrices.
//
// Design:
//   - No virtual functions (operator(), rows(), cols() are inline, no SIMD inhibition).
//   - shared_storage_ can own dynamic storage (for dynamic derived class Matrix).
//     Static derived classes (StaticMatrix) and non-owning views set
//     shared_storage_ = nullptr, with data_ pointing to external / stack arrays.
//   - row_stride / col_stride are held independently so that row-major,
//     column-major, sub-matrix, and transposed views can all be expressed
//     by the same type.
//   - Derived classes Matrix / StaticMatrix set data_, rows_, cols_,
//     row_stride_, col_stride_ in their constructor (the same structure as
//     sangi Vector_DIM pointing Vector<T>::elements at its own fixed array).

#ifndef SANGI_BASE_MATRIX_HPP
#define SANGI_BASE_MATRIX_HPP

#include <cstddef>
#include <concepts>
#include <memory>
#include <type_traits>

namespace sangi {

template<typename T>
class BaseMatrix {
public:
    // Compile-time dimension traits.
    // -1 = dynamic (size known only at runtime). Derived classes with statically
    // known dimensions (StaticMatrix) shadow these with their template parameters.
    // Used by LinAlg algorithms to enable static_assert dimension checks when
    // both operands carry static sizes.
    static constexpr std::ptrdiff_t static_rows = -1;
    static constexpr std::ptrdiff_t static_cols = -1;

protected:
    // Ownership mode:
    //   shared_storage_ != nullptr: BaseMatrix owns dynamic storage (via shared)
    //   shared_storage_ == nullptr: data_ points to external / derived-class private storage (non-owning view)
    std::shared_ptr<T[]> shared_storage_;

    // Pointer for element access (always valid, used regardless of whether shared_storage_ exists)
    T*          data_;

    // Shape
    std::size_t rows_;
    std::size_t cols_;

    // Strides (element address calculation: data_[i * row_stride_ + j * col_stride_])
    //   row-major contiguous: row_stride_ = cols_, col_stride_ = 1
    //   column-major contiguous: row_stride_ = 1, col_stride_ = rows_
    //   transposed view: swap row_stride_ and col_stride_
    //   block view: inherit parent strides, offset data_
    std::size_t row_stride_;
    std::size_t col_stride_;

public:
    // Empty construction (for setting a view later via assignment; default value = empty view)
    BaseMatrix() noexcept
        : shared_storage_(), data_(nullptr), rows_(0), cols_(0)
        , row_stride_(0), col_stride_(0) {}

protected:
    // For derived classes only: allocate and own dynamic storage
    //   (used when sangi::Matrix inherits from BaseMatrix in the future)
    BaseMatrix(std::size_t rows, std::size_t cols)
        : shared_storage_(rows * cols > 0 ? new T[rows * cols]() : nullptr)
        , data_(shared_storage_.get())
        , rows_(rows), cols_(cols)
        , row_stride_(cols), col_stride_(1) {}

    // For derived classes only: view setter (used by Matrix to sync its own storage to the base).
    //   When pointing to external storage (such as StaticMatrix's stack array), pass shared_storage = nullptr.
    void set_view(std::shared_ptr<T[]> storage, T* data,
                  std::size_t rows, std::size_t cols,
                  std::size_t row_stride, std::size_t col_stride = 1) noexcept {
        shared_storage_ = std::move(storage);
        data_ = data;
        rows_ = rows;
        cols_ = cols;
        row_stride_ = row_stride;
        col_stride_ = col_stride;
    }

    // For derived classes only: lightweight sync that updates only the data ptr without holding shared_storage
    //   (used when StaticMatrix passes the address of its own std::array to the base)
    void set_view_nonowning(T* data, std::size_t rows, std::size_t cols,
                            std::size_t row_stride, std::size_t col_stride = 1) noexcept {
        shared_storage_.reset();
        data_ = data;
        rows_ = rows;
        cols_ = cols;
        row_stride_ = row_stride;
        col_stride_ = col_stride;
    }

public:
    using value_type = T;
    using size_type = std::size_t;

    // === public: ctor for non-owning view ===
    // Directly create a view that references external data (corresponds to sangi
    // Vector<T>(N, p, shared, Step)).
    BaseMatrix(T* data, std::size_t rows, std::size_t cols,
               std::size_t row_stride, std::size_t col_stride = 1) noexcept
        : shared_storage_(), data_(data), rows_(rows), cols_(cols)
        , row_stride_(row_stride), col_stride_(col_stride) {}

    // Implicit conversion: implicit construction (non-owning view) from a row-major
    // contiguous matrix type (Matrix, StaticMatrix, etc.).
    // Existing sangi::Matrix<T> / StaticMatrix<T, R, C> have data()/rows()/cols(),
    // so this ctor can convert them to a BaseMatrix view.
    // (Note: for a const M& input, const_cast is used to store a non-const ptr in data_.
    //  Intended for read-only use through const BaseMatrix<T>& parameters.)
    template<typename M>
        requires std::is_same_v<typename M::value_type, T>
              && (!std::is_same_v<std::remove_cvref_t<M>, BaseMatrix<T>>)
              && requires(const M& m) {
                     { m.data() } -> std::convertible_to<const T*>;
                     { m.rows() } -> std::convertible_to<std::size_t>;
                     { m.cols() } -> std::convertible_to<std::size_t>;
                 }
    BaseMatrix(const M& m) noexcept
        : shared_storage_()
        , data_(const_cast<T*>(m.data()))
        , rows_(m.rows()), cols_(m.cols())
        , row_stride_(m.cols()), col_stride_(1) {}

    // === access ===
    [[nodiscard]] size_type rows()       const noexcept { return rows_; }
    [[nodiscard]] size_type cols()       const noexcept { return cols_; }
    [[nodiscard]] size_type row_stride() const noexcept { return row_stride_; }
    [[nodiscard]] size_type col_stride() const noexcept { return col_stride_; }
    [[nodiscard]] size_type size()       const noexcept { return rows_ * cols_; }

    [[nodiscard]] const T& operator()(size_type i, size_type j) const noexcept {
        return data_[i * row_stride_ + j * col_stride_];
    }
    [[nodiscard]] T& operator()(size_type i, size_type j) noexcept {
        return data_[i * row_stride_ + j * col_stride_];
    }

    [[nodiscard]] const T* data() const noexcept { return data_; }
    [[nodiscard]] T*       data()       noexcept { return data_; }

    // Whether this is a square matrix
    [[nodiscard]] bool is_square() const noexcept { return rows_ == cols_; }

    // === Contiguity checks (for fast path) ===
    [[nodiscard]] bool is_row_major_contiguous() const noexcept {
        return col_stride_ == 1 && row_stride_ == cols_;
    }
    [[nodiscard]] bool is_col_major_contiguous() const noexcept {
        return row_stride_ == 1 && col_stride_ == rows_;
    }

    // === View operations (non-owning, retains a reference to the parent storage) ===
    // block(r0, r1, c0, c1): submatrix view for rows [r0, r1), columns [c0, c1)
    [[nodiscard]] BaseMatrix<T> block(size_type r0, size_type r1,
                                       size_type c0, size_type c1) const noexcept {
        BaseMatrix<T> v(data_ + r0 * row_stride_ + c0 * col_stride_,
                         r1 - r0, c1 - c0,
                         row_stride_, col_stride_);
        v.shared_storage_ = shared_storage_;  // Keep the parent storage's lifetime alive
        return v;
    }

    // transposed(): transposed view (data_ unchanged, strides swapped, rows/cols swapped)
    [[nodiscard]] BaseMatrix<T> transposed() const noexcept {
        BaseMatrix<T> v(data_, cols_, rows_, col_stride_, row_stride_);
        v.shared_storage_ = shared_storage_;
        return v;
    }

    // row_view(i): obtain the i-th row as a 1 x cols view
    [[nodiscard]] BaseMatrix<T> row_view(size_type i) const noexcept {
        BaseMatrix<T> v(data_ + i * row_stride_, 1, cols_, 0, col_stride_);
        v.shared_storage_ = shared_storage_;
        return v;
    }

    // col_view(j): obtain the j-th column as a rows x 1 view
    [[nodiscard]] BaseMatrix<T> col_view(size_type j) const noexcept {
        BaseMatrix<T> v(data_ + j * col_stride_, rows_, 1, row_stride_, 0);
        v.shared_storage_ = shared_storage_;
        return v;
    }

    // === Storage ownership check (for debugging) ===
    [[nodiscard]] bool owns_storage() const noexcept {
        return static_cast<bool>(shared_storage_);
    }
};

} // namespace sangi

#endif // SANGI_BASE_MATRIX_HPP
