/*
 * Vector.h
 * Copyright (C) James Goppert 2010 <james.goppert@gmail.com>
 *
 * This file is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef Vector_H
#define Vector_H

#include "../FastSerial/BetterStream.h"
#include <stdlib.h>
#include <inttypes.h>
#if defined(ARDUINO) && ARDUINO >= 100
	#include "Arduino.h"
#else
	#include <WProgram.h>
#endif

#ifdef ASSERT
const static char vectorSource[] ="Vector.hpp";
#endif

// vector
template <class dataType,class sumType=dataType>
class Vector
{
private:
    size_t size;
    size_t extraAllocationSize; // extra space to add after each allocation
    size_t sizeAllocated; // total allocated size
    dataType* data;
public:
    // default constructor
    Vector(const size_t & _size=0, const size_t & _extraAllocationSize=0) : size(0), extraAllocationSize(_extraAllocationSize), sizeAllocated(0), data(NULL) {
        setSize(_size);
    }
    // 3 vector constructor
    Vector(const dataType & a, const dataType & b, const dataType & c) : size(3), extraAllocationSize(extraAllocationSize), sizeAllocated(0), data(NULL) {
        setSize(size);
        (*this)[0]=a;
        (*this)[1]=b;
        (*this)[2]=c;
    }

    // construct from array
    Vector(const dataType* array, const size_t & _size, const size_t & _extraAllocationSize=0) : size(0), extraAllocationSize(_extraAllocationSize), sizeAllocated(0), data(NULL) {
        setSize(_size);
        for (size_t i=0; i<getSize(); i++)
            (*this)[i]=array[i];
    }
    // copy constructor
    Vector(const Vector &v) : size(0), extraAllocationSize(0), sizeAllocated(0), data(NULL) {
        setSize(v.getSize());
        for (size_t i=0; i<getSize(); i++)
            (*this)[i]=v[i];
    }
    // convert to float vector
    Vector<float> toFloat() const {
        Vector<float> v(getSize());
        for (size_t i=0; i<getSize(); i++)
            v[i]=(*this)[i];
        return v;
    }
    // destructor
    virtual ~Vector() {
        empty();
    }
    void empty() {
        if (data) delete [] data;
        data = NULL;
        sizeAllocated=0;
        size=0;
    }
    // set the size
    void setSize(const size_t & n) {
        if (n==0) {
            if (data) delete [] data;
            data = NULL;
            sizeAllocated=0;
        }
        if (n>sizeAllocated) {
            dataType * newData = new dataType[n+extraAllocationSize];
            memcpy(newData,data,sizeof(dataType)/sizeof(char)*getSize());
            memset(newData+size,0,sizeof(dataType)/sizeof(char)*(n-getSize()));
            delete[] data;
            data = newData;
            sizeAllocated=n+extraAllocationSize;
        }
        size=n;
    }
    // return size
    const size_t & getSize() const {
        return size;
    }
    // insert
    void insert(const size_t index, const dataType value) {
        //Serial.println("insert called");
#ifdef ASSERT
        assert(index<size+1,vectorSource,__LINE__);
#endif
        //Serial.print("Old Size: ");
        //Serial.println(getSize());
        setSize(getSize()+1);
        //Serial.print("New Size: ");
        //Serial.println(getSize());
        //Serial.print("Size of dataType");
        //Serial.println(sizeof(dataType));
        if (index != getSize()-1) {
            memmove(data+index+1,data+index,sizeof(dataType)/sizeof(char)*(getSize()-1-index));
            //Serial.println("memmove called and completed");
        }
        (*this)[index]=value;
        //Serial.println("insert done");
    }
    // remove
    void remove(const size_t & index) {
#ifdef ASSERT
        assert(index<size,vectorSource,__LINE__);
#endif
        memmove(data+index,data+index+1,getSize()-index-1);
        setSize(getSize()-1);
    }
    // push_back
    void push_back(const dataType & value) {
        //Serial.println("push_back called");
        insert(getSize(),value);
        //Serial.println("push_back done");
    }
    // pop_front
    dataType & pop_front() {
        dataType tmp = (*this)[0];
        remove(0);
        return tmp;
    }
    // push_back a vector
    void push_back(const Vector & vector) {
        for (size_t i=0; i<vector.getSize(); i++)
            push_back(vector[i]);
    }
    // const array access operator
    const dataType & operator[](const size_t & index) const {
#ifdef ASSERT
        assert(index<getSize(),vectorSource,__LINE__);
#endif
        return data[index];
    }
    // array access operator
    dataType & operator[](const size_t & index) {
#ifdef ASSERT
        assert(index<getSize(),vectorSource,__LINE__);
#endif
        return data[index];
    }
    // assignment operator
    Vector & operator=(const Vector & v) {
        setSize(v.getSize());
        for (size_t i=0; i<getSize(); i++)
            (*this)[i]=v[i];
        return *this;
    }
    // equal
    const bool operator==(const Vector& v) const {
#ifdef ASSERT
        assert(getSize()==v.getSize(),vectorSource,__LINE__);
#endif
        for (size_t i=0; i<getSize(); i++) {
            if ((*this)[i]!=v[i]) return false;
        }
        return true;
    }
    // not equal
    const bool operator!=(const Vector& v) const {
        return !((*this)==v);
    }
    // addition
    const Vector operator+(const Vector& v) const {
#ifdef ASSERT
        assert(v.getSize() == getSize(),vectorSource,__LINE__);
#endif
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result(i)=(*this)[i]+v[i];
        return result;
    }
    // addition
    const Vector operator+(const dataType& s) const {
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result[i]=(*this)[i]+s;
        return result;
    }
    // subtraction
    const Vector operator-(const Vector& v) const {
#ifdef ASSERT
        assert(v.getSize() == getSize(),vectorSource,__LINE__);
#endif
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result[i]=(*this)[i]-v[i];
        return result;
    }
    // negation
    const Vector operator-() const {
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result[i]=-(*this)[i];
        return result;
    }
    // +=
    Vector& operator+=(const Vector& v) {
#ifdef ASSERT
        assert(v.getSize() == getSize(),vectorSource,__LINE__);
#endif
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            (*this)(i)+=v(i);
        return *this;
    }
    // -=
    Vector& operator-=( const Vector& v) {
#ifdef ASSERT
        assert(v.getSize() == getSize(),vectorSource,__LINE__);
#endif
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            (*this)(i)-=v(i);
        return *this;
    }
    // elementwise  mult.
    const Vector operator*(const Vector & v) const {
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result(i)=(*this)(i)*v(i);
        return result;
    }

    // mult. by a scalar
    const Vector operator*(const dataType & s) const {
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result(i)=(*this)(i)*s;
        return result;
    }
    // div. by a scalar
    const Vector operator/(const dataType & s) const {
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result(i)=(*this)(i)/s;
        return result;
    }
    // elementwise div.
    const Vector operator/(const Vector & v) const {
        Vector result(getSize());
        for (size_t i=0; i<getSize(); i++)
            result(i)=(*this)(i)/v(i);
        return result;
    }

    // div. by a scalar
    Vector & operator/=(const dataType & s) {
        for (size_t i=0; i<getSize(); i++)
            (*this)(i)/=s;
        return *this;
    }
    // mult. by a scalar
    Vector & operator*=(const dataType & s) {
        for (size_t i=0; i<getSize(); i++)
            (*this)(i)*=s;
        return *this;
    }
    // cross/vector product
    const Vector cross(const Vector& v) const {
        Vector result(3), u=*this;
#ifdef ASSERT
        assert(u.getSize()==3 && v.getSize()==3,vectorSource,__LINE__);
#endif
        result(0) = u(1)*v(2)-u(2)*v(1);
        result(1) = -u(0)*v(2)+u(2)*v(0);
        result(2) = u(0)*v(1)-u(1)*v(0);
        return result;
    }
    // dot/scalar product
    const dataType dot(const Vector& v) const {
#ifdef ASSERT
        assert(getSize()==v.getSize(),vectorSource,__LINE__);
#endif
        dataType result;
        for (size_t i=0; i<getSize(); i++) result += (*this)(i)*v(i);
        return result;
    }
    // norm
    const dataType norm() const {
        return sqrt(dot(*this));
    }
    // unit vector
    const Vector unit() const {
        return (*this)*(1/norm());
    }
    // sum
    const sumType sum(const size_t & start=0,const int & end=-1) const {
        size_t end2;
        if (end==-1) end2=getSize()-1;
        else end2=end;
        sumType _sum = 0;
        for (size_t i=start; i<=end2; i++) _sum += (*this)(i);
        return _sum;
    }
    void sumFletcher(uint8_t & CK_A, uint8_t & CK_B, const size_t & start=0,const int & end=-1) const {
        size_t end2;
        if (end==-1) end2=getSize()-1;
        else end2=end;

        for (size_t i = start; i<=end2; i++) {
            CK_A += (*this)(i);
            CK_B += CK_A;
        }
    }
    // range
    const Vector range(const size_t & start, const size_t & stop) const {
        Vector v(stop-start+1);
        for (size_t i=start; i<=stop; i++) v(i-start) = (*this)(i);
        return v;
    }
    // to Array
    const dataType* toArray() const {
        dataType array[getSize()];
        for (size_t i=0; i<getSize(); i++) array[i] = (*this)(i);
        return array;
    }
    // printing
    void print(Stream & serial=Serial, const char * msg="", size_t format=0) const {
        serial.print(msg);
        for (size_t i=0; i<getSize(); i++) {
            serial.print((*this)(i),format);
            serial.print(" ");
        }
        serial.println();
    }
    // self test
    static bool selfTest(Stream & serial=Serial) {
        serial.println("Vector self test.");
        Vector u(3),v(3),w(3);
        u(0) = 1;
        u(1) = 2;
        u(2) = 3;
        v(0) = -4;
        v(1) = -5;
        v(2) = -6;
        u.print(serial,"u: ");
        v.print(serial,"v: ");
        (u+v).print(serial,"u + v: ");
        (u-v).print(serial,"u - v: ");
        Serial.print("u dot v: ");
        Serial.println(u.dot(v));
        Serial.print("size of u: ");
        Serial.println(u.getSize());
        Serial.print("size of v: ");
        Serial.println(v.getSize());
        w=u.cross(v);
        w.print(serial,"u cross v: ");
        Serial.print("size of u cross v: ");
        Serial.println(w.getSize());
        return true;
    }

};

#endif

// vim:ts=4:sw=4:expandtab
