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

void carr_func(int* vec)
{
	std::cout << "carr_func - vec: " << vec << std::endl;
}

int main(void)
{
	std::vector<int> v1 = {-1, 3, 5, -8, 0}; // initialize with list
	std::vector<int> v2; // don't initialize
	auto v3(v1); // initialize v3 via copy

	/**
	 * Managing std::vector capacity
	 */

	// Unlike std::array, std::vector has a more sensible empty() function
	// v2 is currently empty
	std::cout << "v1.empty(): " << v1.empty() << std::endl;
	std::cout << "v2.empty(): " << v2.empty() << std::endl;

	// size() tells you the number of elements
	std::cout << "v1.size(): " << v1.size() << std::endl;
	std::cout << "v2.size(): " << v2.size() << std::endl;

	// max_size() is huuuuuuuuuge for my host machine
	std::cout << "v1.max_size(): " << v1.max_size() << std::endl;
	std::cout << "v2.max_size(): " << v2.max_size() << std::endl;

	// Capacity tells you how many elements can be stored in the currently allocated memory
	std::cout << "v1.capacity(): " << v1.capacity() << std::endl;
	std::cout << "v2.capacity(): " << v2.capacity() << std::endl;

	v2.reserve(10);
	std::cout << "v2.capacity() after reserve(10): " << v2.capacity() << std::endl;
	std::cout << "v2.size(): " << v2.size() << std::endl;

	// If you have reserved space greater than your current needs, you can shrink the buffer
	v2.shrink_to_fit();
	std::cout << "v2.capacity() after shrink_to_fit(): " << v2.capacity() << std::endl;

	/**
	 * Accessing std::vector elements
	 */
	std::cout << "v1.front(): " << v1.front() << std::endl;
	std::cout << "v1.back(): " << v1.back() << std::endl;
	std::cout << "v1[0]: " << v1[0] << std::endl;
	std::cout << "v1.at(4): " << v1.at(4) << std::endl;

	// Bounds checking will generate exceptions. Try:
	// auto b = v2.at(10);

	// However, operator [] is not bounds checked!
	// This may or may not seg fault
	// std::cout << "v2[6]: " << v2[6] << std::endl;

	/*
	 * If you need to interface with legacy code or libraries requiring
	 * a C-style array interface, you can get to the underlying array data ptr
	 */

	// Error:
	// carr_func(v1);

	// OK:
	carr_func(v1.data());

	/**
	 * Playing around with vectors
	 */
	v2 = v1; // copy

	std::cout << "v2.size() after copy: " << v2.size() << std::endl;
	v2.clear();
	std::cout << "v2.size() after clear: " << v2.size() << std::endl;
	std::cout << "v2.capacity(): " << v2.capacity() << std::endl;

	v2.insert(v2.begin(), -1); // insert an element - you need an iterator
	v2.emplace(v2.end(), int(1000)); // construct and place an element at the iterator

	int x = 10;
	v2.push_back(x); // adds element to end
	v2.emplace_back(10); // constructs an element in place at the end

	std::cout << std::endl << "v2: " << std::endl;
	for(const auto& t: v2)
	{
		std::cout << t << " ";
	}
	std::cout << std::endl;

	v2.resize(7); // resize to 7. The new elements will be 0-initialized
	v2.resize(10, -1); // resize to 10. New elements initialized with -1

	v2.pop_back(); // removes last element
	v2.erase(v2.begin()); // removes first element

	std::cout << std::endl << "v2 resized: " << std::endl;
	for(const auto& t: v2)
	{
		std::cout << t << " ";
	}
	std::cout << std::endl;

	v2.resize(4); // shrink and strip off extra elements

	// Container operations work
	std::sort(v2.begin(), v2.end());

	std::cout << std::endl << "v2 shrunk & sorted: " << std::endl;
	for(const auto& t: v2)
	{
		std::cout << t << " ";
	}
	std::cout << std::endl;

	std::cout << "std::vector size: " << sizeof(std::vector<char>) << std::endl;

	return 0;
}
