/*
Vector.h
Written by Matthew Fisher

Template vector library.  Extends functionality of std::vector
*/

#pragma once

#include "Asserts.h"

//
// Intellisense for custom types
//
//http://thejefffiles.com/blog/autoexpdat-your-key-to-better-debugging-part-1/

#pragma warning(disable : 4700)

template <class T> class Vector
{
public:
    Vector()
    {
        _Data = NULL;
        _Length = 0;
        _Capacity = 0;
    }

    explicit Vector(unsigned int Size)
    {
        _Data = new T[Size];
        _Length = Size;
        _Capacity = Size;
    }

    Vector(unsigned int Size, const T &ClearValue)
    {
        _Data = new T[Size];
        _Length = Size;
        _Capacity = Size;
        Clear(ClearValue);
    }

    Vector(const Vector<T> &V)
    {
        const unsigned int Length = V._Length;
        _Length = Length;
        _Capacity = Length;
        if(Length == 0)
        {
            _Data = NULL;
        }
        else
        {
            _Data = new T[Length];
            for(unsigned int Index = 0; Index < Length; Index++)
            {
                _Data[Index] = V._Data[Index];
            }
        }
    }

    ~Vector()
    {
        if(_Data != NULL)
        {
            delete[] _Data;
        }
    }
    
    //
    // Memory
    //
    void FreeMemory()
    {
        if(_Data != NULL)
        {
            delete[] _Data;
            _Data = NULL;
        }
        _Length = 0;
        _Capacity = 0;
    }

    void DeleteMemory()
    {
        if(_Data != NULL)
        {
            for(UINT Index = 0; Index < _Length; Index++)
            {
                delete _Data[Index];
            }
            delete[] _Data;
            _Data = NULL;
        }
        _Length = 0;
        _Capacity = 0;
    }

    //
    // Windows has claimed the nice name "ZeroMemory" for its own uses
    //
    void ZeroMem()
    {
        if(_Length > 0)
        {
            memset(_Data, 0, _Length * sizeof(T));
        }
    }

    void operator = (const Vector<T> &V)
    {
        const unsigned int Length = V._Length;
        _Length = Length;
        _Capacity = Length;
        if(_Data != NULL)
        {
            delete[] _Data;
        }
        if(Length == 0)
        {
            _Data = NULL;
        }
        else
        {
            _Data = new T[Length];
            for(unsigned int Index = 0; Index < Length; Index++)
            {
                _Data[Index] = V._Data[Index];
            }
        }
    }

    void Allocate(unsigned int Size)
    {
        if(Size == 0)
        {
            FreeMemory();
        }
        /*else if(_Length == Size)
        {
            if(typeid(T) != typeid(int) &&
               typeid(T) != typeid(unsigned int) &&
               typeid(T) != typeid(double) &&
               typeid(T) != typeid(float) &&
               typeid(T) != typeid(char) &&
               typeid(T) != typeid(unsigned char))
            {
                T Value;
                for(unsigned int Index = 0; Index < Size; Index++)
                {
                    _Data[Index] = Value;
                }
            }
        }*/
        else
        {
            if(_Data != NULL)
            {
                delete[] _Data;
            }
            _Data = new T[Size];
            _Length = Size;
            _Capacity = Size;
        }
    }

    void Allocate(unsigned int Size, const T &ClearValue)
    {
        Allocate(Size);
        Clear(ClearValue);
    }

    void ReSize(unsigned int Size)
    {
        if(Size == 0)
        {
            FreeMemory();
        }
        else if(Size <= _Capacity)
        {
            _Length = Size;
            if(Size == 0)
            {
                if(_Data != NULL)
                {
                    delete[] _Data;
                    _Capacity = 0;
                }
            }
        }
        else
        {
            unsigned int NewCapacity = Size * 3 / 2 + 4;
            T *NewData = new T[NewCapacity];
            const unsigned int Length = _Length;
            for(unsigned int Index = 0; Index < Length; Index++)
            {
                NewData[Index] = _Data[Index];
            }
            if(_Data)
            {
                delete[] _Data;
            }
            _Data = NewData;
            _Length = Size;
            _Capacity = NewCapacity;
        }
    }

    void Reserve(unsigned int Size)
    {
        if(Size > _Capacity)
        {
            T *NewData = new T[Size];
            if(_Data != NULL)
            {
                memcpy(NewData, _Data, sizeof(T) * _Length);
                for(UINT index = 0; index < _Length; index++)
                {
                    new (_Data + index) T;
                }
                delete[] _Data;
            }
            _Data = NewData;
            _Capacity = Size;
        }
    }

    //
    // Accessors
    //
    __forceinline T& operator [] (unsigned int k)
    {
#ifdef VECTOR_DEBUG
        if(k >= _Length)
        {
            SignalError("Out-of-bounds vector access");
        }
#endif
        return _Data[k];
    }
    __forceinline T& operator [] (int k) 
    {
#ifdef VECTOR_DEBUG
        if(k < 0 || k >= int(_Length))
        {
            SignalError("Out-of-bounds vector access");
        }
#endif
        return _Data[k];
    }
    __forceinline const T& operator [] (unsigned int k) const
    {
#ifdef VECTOR_DEBUG
        if(k >= _Length)
        {
            SignalError("Out-of-bounds vector access");
        }
#endif
        return _Data[k];
    }
    __forceinline const T& operator [] (int k) const
    {
#ifdef VECTOR_DEBUG
        if(k < 0 || k >= int(_Length))
        {
            SignalError("Out-of-bounds vector access");
        }
#endif
        return _Data[k];
    }
    __forceinline unsigned int Length() const
    {
        return _Length;
    }
    __forceinline T* CArray()
    {
#ifdef VECTOR_DEBUG
        if(_Length == 0)
        {
            SignalError("CArray called on zero-length vector");
        }
#endif
        return _Data;
    }
    __forceinline const T* CArray() const
    {
#ifdef VECTOR_DEBUG
        if(_Length == 0)
        {
            SignalError("CArray called on zero-length vector");
        }
#endif
        return _Data;
    }
    __forceinline T& RandomElement()
    {
        Assert(_Length > 0, "RandomElement called with no elements");
        return _Data[rand() % _Length];
    }
    __forceinline const T& RandomElement() const
    {
        Assert(_Length > 0, "RandomElement called with no elements");
        return _Data[rand() % _Length];
    }
    __forceinline T& Last()
    {
        Assert(_Length > 0, "Last called with no elements");
        return _Data[_Length - 1];
    }
    __forceinline const T& Last() const
    {
        Assert(_Length > 0, "Last called with no elements");
        return _Data[_Length - 1];
    }
    __forceinline T& First()
    {
        Assert(_Length > 0, "First called with no elements");
        return _Data[0];
    }
    __forceinline const T& First() const
    {
        Assert(_Length > 0, "First called with no elements");
        return _Data[0];
    }
    __forceinline T* Begin()
    {
        return _Data;
    }
    __forceinline const T* Begin() const
    {
        return _Data;
    }
    __forceinline T* End()
    {
        return _Data + _Length;
    }
    __forceinline const T* End() const
    {
        return _Data + _Length;
    }

    //
    // Modifiers
    //
    void Append(const Vector<T> &V);
    void PushEnd(const T &t);
    void PushEnd();
    void PopEnd();
    void PopFront();
    void RemoveSlow(unsigned int Index);
    void RemoveSwap(unsigned int Index);
    void Randomize();
    void Sort();
    void Scale(const T &t);
    template<class orderingType> void Sort(orderingType Function)
    {
        if(_Length >= 2)
        {
            std::sort(_Data, _Data + _Length, Function);
        }
    }
    void Clear(const T &T);

    //
    // Query
    //
    T Sum() const;
    const T& MaxValue() const;
    unsigned int MaxIndex() const;
    const T& MinValue() const;
    unsigned int MinIndex() const;
    bool Contains(const T &t) const;
    int FindFirstIndex(const T &t) const;
    unsigned int Hash32() const;
    unsigned __int64 Hash64() const;

    //
    // File
    //
    void SaveToASCIIFile(const String &Filename)
    {
        ofstream File(Filename.CString());
        PersistentAssert(!File.fail(), "Failed to open file");
        File << _Length << endl;
        for(unsigned int Index = 0; Index < _Length; Index++)
        {
            File << _Data[Index] << '\n';
        }
    }

    void LoadFromASCIIFile(const String &Filename)
    {
        ifstream File(Filename.CString());
        PersistentAssert(!File.fail(), "Failed to open file");
        unsigned int Length;
        File >> Length;
        Allocate(Length);
        for(unsigned int Index = 0; Index < Length; Index++)
        {
            File >> _Data[Index];
        }
    }

protected:
    //vector<T> Data;
    T *_Data;
    unsigned int _Length;
    unsigned int _Capacity;
};

#pragma warning(default : 4700)

#include "Vector.cpp"