/*
 * Copyright (c) 1999-2019 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 PW_CORE_MATRIXBASE_HPP
#define PW_CORE_MATRIXBASE_HPP

#include <algorithm>
#include <iostream>
#include <iterator>
#include <numeric>
#include <type_traits>
#include <utility>
#include <initializer_list>
#include <cmath>

#include <iostream>

namespace pw {

template <typename T, typename Derived>
struct matrixbase_ {

	typedef T value_type;

	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;
    }

    Derived& fill(const T& v) {
        std::fill(std::begin(derived().data), std::end(derived().data), T(v));
        return derived();
    }

    Derived& zero() {
        return derived().fill(0);
    }

	inline T squared_norm() const {
		return std::accumulate(std::begin(derived().data),std::end(derived().data), T(0),
							   [&](const T& a,const T& b){
			return a + b * b;
		});
	}

	inline T norm() const {
		return std::sqrt(squared_norm());
	}

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

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

    using iterator = T*;
    using const_iterator = const T*;
    iterator begin() { return &derived().data[0]; }
    iterator end() { return &derived().data[0] + size(); }
    const_iterator begin() const { return &derived().data[0]; }
    const_iterator end() const { return &derived().data[0] + size(); }

    T& operator [] (std::size_t i) {
        return derived().data[i];
    }

    const T& operator [] (std::size_t i) const {
        return derived().data[i];
    }

	static T dot(const Derived &a,const Derived &b) {
#if 0
		Derived r; for (size_t i = 0;i < a.size();i++) r[i] = a[i] * b[i];
		return std::accumulate(std::begin(r), std::end(r), T(0));
#else
	return std::inner_product(std::begin(a),std::end(a),std::begin(b),T(0));
#endif
	}

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


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

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



};

//
//
//




///**
// * \brief base class for all matrix and vector operations
// */
//template <typename T> class matrixbase {
//public:

//    typedef T value_type;
//    typedef int offset_type;
//    typedef unsigned int size_type;


//    matrixbase() {}

//	//! assignment constructor
//    explicit matrixbase(int rows, int cols,T* ptr,bool row_major,int data_offset = 0)
//        : _data(ptr)
//        , _data_offset(data_offset)
//        , _rows(rows)
//        , _cols(cols)
//        , _row_stride(row_major ? 1    : cols)
//        , _col_stride(row_major ? rows : 1)
//	{
//	}

//    inline const T& get_element(int e) const { return this->at(e); }
//    inline void set_element(int e,const T &v) { this->at(e) = v; }

//    //! return number of rows
//    inline unsigned int rows() const { return _rows; }
//    //! return number of columns
//    inline unsigned int cols() const { return _cols; }

//	//! get cell count
//    inline unsigned int cells() const { return _rows * _cols; }

//    //! get data
//    inline T* data() { return _data + _data_offset; }

//    //! get data
//    inline const T* data() const { return _data + _data_offset; }

//    //! get item at index
//    inline T& at(unsigned int idx) { return data()[idx]; }

//    //! get item at index
//    inline const T& at(unsigned int idx) const { return data()[idx]; }

//    //! get item at position
//    inline T& at(unsigned int r,unsigned int c) { return data()[r * _row_stride + c * _col_stride]; }

//    //! get item at position
//    inline const T& at(unsigned int r,unsigned int c) const { return data()[r * _row_stride + c * _col_stride]; }

//    //! fill data
//    inline matrixbase& fill(const T& val) {
//        for (unsigned int r = 0; r < this->rows(); r++)
//            for (unsigned int c = 0; c < this->cols(); c++)
//                this->at(r,c) = val;
//        return *this;
//    }

//    //! set identity
//    inline matrixbase& set_identity() {
//        for (unsigned int r = 0;r < rows(); r++)
//            for (unsigned int c = 0; c < cols(); c++)
//                this->at(r,c) = (c == r) ? T(1) : T(0);
//		return *this;
//	}

//    //! set strides for row and column
//    inline void set_stride(int row_stride,int col_stride) {
//        _row_stride = row_stride; _col_stride = col_stride;
//    }

//    //! return row stride (elements between consequtive rows)
//    inline int row_stride() const { return _row_stride; }

//    //! return column stride (elements between consequtive columns)
//    inline int col_stride() const { return _col_stride; }

//    //! get row major
//    inline bool is_row_major() const { return row_stride() > col_stride(); }

//    //! check if squared
//    inline bool is_square() const { return (_rows == _cols); }

//    //! flip row/column order
//    inline void flip_order() { set_stride(col_stride(),row_stride()); }

//    //! get the offset for data in the matrix
//    inline int get_data_offset(int r,int c) const { return (r * _row_stride + c * _col_stride) + _data_offset; }

//protected:

//    T* _data;
//    int _data_offset;

//    int _cols;
//    int _rows;

//    int _row_stride;
//    int _col_stride;

//};

}


#endif
