#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

// a class implementing a mathematical vector
template <class K, size_t N>
class Vector {
    public:
        // constructor, initializes vector with zeroes of length N
        Vector() : _vector(N, 0) {}
        // initialize by passing vector vec to initialize the vector object with
        Vector(std::vector<K> vec) { _vector = vec; }
        
        // copy constructor, assignment, destructor not needed
        // all handled by std::vector
        
        // reference [] operator, used for assignments
        K& operator[](int i) {
            return _vector[i];
        }

        // const ref [] operator
        K operator[](int i) const {
            return _vector[i];
        }

        // return an iterator pointing to the first element of the underlying std::vector
        typename std::vector<K>::const_iterator begin() const {
            return _vector.begin();
        }
        
        // return an iterator pointing to the last element of the underlying std::vector
        typename std::vector<K>::const_iterator end() const {
            return _vector.end();
        }

        // dot product of two vectors of the same length
        template <class K2>
        K operator*(const Vector<K2,N>& y) {
            std::vector<K> result(N); // new empty std::vector of correct size
            // now multiply one element of the first with one of the second vector
            std::transform(_vector.begin(), _vector.end(), y.begin(), result.begin(), std::multiplies<K>());
            // and sum up the products
            return std::accumulate(result.begin(), result.end(), 0);
        }

        // vector addition
        template <class K2>
        Vector<K, N> operator+(const Vector<K2, N>& y) {
            std::vector<K> result(N); // new empty std::vector of correct size
            // add elements by adding one element of the first to one of the second vector
            std::transform(_vector.begin(), _vector.end(), y.begin(), result.begin(), std::plus<K>());
            return Vector(result);
        }

        // return the maximum value in the vector
        K max() {
            return *std::max_element(_vector.begin(), _vector.end());
        }

        // return the minimum value in the vector
        K min() {
            return *std::min_element(_vector.begin(), _vector.end());
        }

        // return the average value of the vector
        K average() {
            // sum up all elements
            K sum = std::accumulate(_vector.begin(), _vector.end(), 0);
            return sum / N; // and divide by length
        }

        // run the specified function on each vector element
        template <class F>
        Vector<K,N> map(F op) const {
            std::vector<K> result(N); // initialize new std::vector of correct size
            // run op on each element
            std::transform(_vector.begin(), _vector.end(), result.begin(), op);
            return Vector(result);
        }
    private:
        std::vector<K> _vector; // the private std::vector container
};

// print vector
template <class K, size_t N>
std::ostream& operator<<(std::ostream& os, const Vector<K,N>& v) {
    os << "["; // opening brackets
    for (size_t i = 0; i < N; i++) {
        os << v[i]; // print element
        if(i < N-1) {
            os << ", "; // if not the last one, add comma
        }
    }
    os << "]"; // closing brackets
    return os;
}

// scalar multiplication for vector * scalar
template <class K, size_t N, class K2>
Vector<K, N> operator*(const Vector<K,N>& v, const K2 k) {
    // multiply a scalar value using the generic map function
    return v.map(std::bind1st(std::multiplies<K>(), k));
}

// scalar multiplication for scalar * vector
template <class K, size_t N, class K2>
Vector<K, N> operator*(const K2 k, const Vector<K,N>& v) {
    return v * k;
}


// example function
double foo(double val) {
    return val*2+3;
}

// testing out functionalities
int main() {
    Vector<double, 3> a;
    Vector<float, 3> b;
    a[0] = 0;
    a[1] = 0;
    a[2] = 4;

    b[0] = 42;
    b[1] = -20;
    b[2] = 3;

    std::cout << "Vektor a: " << a << ", Vektor b: " << b << std::endl;
    std::cout << "a+b: " << a+b << std::endl;
    std::cout << "a*b: " << a*b << std::endl;
    std::cout << "42*b: " << 42*b << std::endl;
    std::cout << "b*42: " << b*42 << std::endl;
    std::cout << "max(a): " << a.max() << std::endl;
    std::cout << "min(a): " << a.min() << std::endl;
    std::cout << "mean(a): " << a.average() << std::endl;
    std::cout << "foo angewendet auf b: " << b.map(foo) << std::endl;
}
