#include "svk.hpp"
#include "svk_loader.hpp"
#include <cstring>
#include <chrono>
#include <filesystem>
#include <fstream>

bool _loadShaderModule(char const* filePath,
					   VkDevice device,
					   VkShaderModule& outShaderModule)
{
	std::ifstream file(filePath, std::ios::ate | std::ios::binary);

	if (!file.is_open())
		return false;

	size_t fileSize = static_cast<size_t>(file.tellg());

	std::vector<uint32_t> buffer(fileSize / sizeof(uint32_t));

	file.seekg(0);
	file.read(reinterpret_cast<char*>(buffer.data()), static_cast<std::streamsize>(fileSize));
	file.close();

	VkShaderModuleCreateInfo createInfo = {};
	createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
	createInfo.pNext = nullptr;

	createInfo.codeSize = buffer.size() * sizeof(uint32_t);
	createInfo.pCode = buffer.data();

	VkShaderModule shaderModule;
	if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS)
		return false;

	outShaderModule = shaderModule;
	return true;
}

void _loadShaders(VkDevice device,
				  std::string const& vertPath,
				  std::string const& fragPath,
				  VkShaderModule& vertModule,
				  VkShaderModule& fragModule,
				  bool debug)
{
	std::string vertFolder{vertPath.substr(0, vertPath.rfind('/') + 1)};
	std::string fragFolder{fragPath.substr(0, vertPath.rfind('/') + 1)};

	if (vertFolder.rfind('/') == vertFolder.npos)
		vertFolder = "compiled";
	else
		vertFolder.append("compiled");

	if (fragFolder.rfind('/') == fragFolder.npos)
		fragFolder = "compiled";
	else
		fragFolder.append("compiled");

	if (!std::filesystem::exists(std::filesystem::path(vertFolder)))
	{
		if (debug)
			std::cout << "Folder " << vertFolder << " is missing and will be created" << std::endl;
		std::filesystem::create_directory(vertFolder);
	}

	if (!std::filesystem::exists(std::filesystem::path(fragFolder)))
	{
		if (debug)
			std::cout << "Folder " << fragFolder << " is missing and will be created" << std::endl;
		std::filesystem::create_directory(fragFolder);
	}


	std::string compiledVertPath{vertPath};
	std::string compiledFragPath{fragPath};

	size_t slashPos = vertPath.rfind('/');
	if (slashPos == vertPath.npos)
		compiledVertPath.insert(0, "compiled/").append(".spv");
	else
		compiledVertPath.insert(slashPos, "/compiled").append(".spv");
	
	slashPos = fragPath.rfind('/');
	if (slashPos == fragPath.npos)
		compiledFragPath.insert(0, "compiled/").append(".spv");
	else
		compiledFragPath.insert(slashPos, "/compiled").append(".spv");


	if (!debug) {
		if (_isSourceUpdated(vertPath, compiledVertPath, debug))
			_compileShader(vertPath, compiledVertPath, debug);
		if (_isSourceUpdated(fragPath, compiledFragPath, debug))
			_compileShader(fragPath, compiledFragPath, debug);
		_loadShaderModule(compiledVertPath.c_str(), device, vertModule);
		_loadShaderModule(compiledFragPath.c_str(), device, fragModule);
		return;
	}

	if (_isSourceUpdated(vertPath, compiledVertPath, debug)){
		_compileShader(vertPath, compiledVertPath, debug);
		std::cout << "Succesfully compiled " << vertPath << std::endl;
	}

	if (_isSourceUpdated(fragPath, compiledFragPath, debug)){
		_compileShader(fragPath, compiledFragPath, debug);
		std::cout << "Succesfully compiled " << fragPath << std::endl;
	}

	if (!_loadShaderModule(compiledVertPath.c_str(), device, vertModule))
		std::cout << "Error when loading " << vertPath << std::endl;
	else
		std::cout << "Succesfully loaded " << vertPath << std::endl;

	if (!_loadShaderModule(compiledFragPath.c_str(), device, fragModule))
		std::cout << "Error when loading " << fragPath << std::endl;
	else
		std::cout << "Succesfully loaded " << fragPath << std::endl;
}



bool _isSourceUpdated(std::string const& sourcePath, std::string const& compiledPath, bool debug)
{
	std::chrono::system_clock::rep source;
	std::chrono::system_clock::rep compiled;

	// We want the program to crash if there is no source file.
	source = std::filesystem::last_write_time(std::filesystem::path(sourcePath)).time_since_epoch().count();

	try{
		compiled = std::filesystem::last_write_time(std::filesystem::path(compiledPath)).time_since_epoch().count();
	}
	catch (std::filesystem::filesystem_error& e){
		if (debug)
			std::cout << "Compiled file " << sourcePath + ".spv" << " missing and will be created" << std::endl;
		return true;
	}

	if (compiled < source){
		if (debug)
			std::cout << "Source file " << sourcePath << " is updated and will be recompiled " << std::endl;
		return true;
	}
		
	return false;
}


std::string _compilerPath = std::string("../shared/third_party/glslangValidator"); // Default, change with setShaderCompilerPath
void _compileShader(std::string const& sourcePath, std::string const& compiledPath, bool debug)
{
	if (_compilerPath == "")
		throw std::runtime_error("_compilerPath is an empty string, did you forget to set it with setShaderCompilerPath()?");

	std::string noOutputFlag = "-s";
	if (debug)
		noOutputFlag = "";
	std::string command = "./" + _compilerPath + " -V " + noOutputFlag + " " + sourcePath + " -o " + compiledPath;
	
	int res = std::system(command.c_str());
	if (res != 0)
	{
		throw std::runtime_error("Shader compiler failed to compile " + sourcePath + " and returned " + std::to_string(res));
	}
}


void setShaderCompilerPath(std::string const& path)
{
	_compilerPath = path;
}

bool loadTGATextureData(char const* filename, TextureData* texture)	// Loads A TGA File Into Memory
{
	unsigned int i;
	unsigned char
		TGAuncompressedheader[12]={ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0},	// Uncompressed TGA Header
		TGAcompressedheader[12]={ 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0},	// Compressed TGA Header
		TGAuncompressedbwheader[12]={ 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0},	// Uncompressed grayscale TGA Header
		TGAcompressedbwheader[12]={ 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0},	// Compressed grayscale TGA Header
		actualHeader[12],	// Used To Compare TGA Header
		header[6];		// First 6 Useful Bytes From The Header
	unsigned int bytesPerPixel,		// Holds Number Of Bytes Per Pixel Used In The TGA File
		imageSize,		// Used To Store The Image Size When Setting Aside Ram
		temp;			// Temporary Variable
	long rowSize, stepSize, bytesRead;
//	long w, h;
	unsigned char *rowP;
	int err;
	unsigned char rle;
	long row, rowLimit;
	unsigned char pixelData[4];	
	
	// Nytt f�r flipping-st�d 111114
	char flipped;
	long step;
	
	// It seems Windows/VS doesn't like fopen any more, but fopen_s is not on the others.
	FILE *file;
	#if defined(_WIN32)
		fopen_s(&file, filename, "rb");
	#else
		file = fopen(filename, "rb"); // rw works everywhere except Windows?
	#endif
//	FILE *file = fopen(filename, "rb");			// Open The TGA File
	err = 0;
	if (file == NULL) err = 1;				// Does File Even Exist?
	else if (fread(actualHeader, 1, sizeof(actualHeader), file) != sizeof(actualHeader)) err = 2; // Are There 12 Bytes To Read?
	else if (
	// Edit 2019: Ignore the origin!
				(memcmp(TGAuncompressedheader, actualHeader, sizeof(TGAuncompressedheader)-4) != 0) &&
				(memcmp(TGAcompressedheader, actualHeader, sizeof(TGAcompressedheader)-4) != 0) &&
				(memcmp(TGAuncompressedbwheader, actualHeader, sizeof(TGAuncompressedheader)-4) != 0) &&
				(memcmp(TGAcompressedbwheader, actualHeader, sizeof(TGAcompressedheader)-4) != 0)
			)
			{
				err = 3; // Does The Header Match What We Want?
				for (i = 0; i < 12; i++)
					printf("%d ", actualHeader[i]);
				printf("\n");
			}
	else if (fread(header, 1, sizeof(header), file) != sizeof(header)) err = 4; // If So Read Next 6 Header Bytes

	if (err != 0)
	{
		switch (err)
		{
			case 1: printf("could not open file %s\n", filename); break;
			case 2: printf("could not read header of %s\n", filename); break;
			case 3: printf("unsupported format in %s\n", filename); break;
			case 4: printf("could not read file %s\n", filename); break;
			default: printf("undefined error when trying to access %s\n", filename); break;
		}
		
		if(file == NULL)		// Did The File Even Exist? *Added Jim Strong*
			return false;
		else
		{
			fclose(file);		// If Anything Failed, Close The File
			return false;
		}
	}

	texture->width  = header[1] * 256 + header[0];	// Determine The TGA Width (highbyte*256+lowbyte)
	texture->height = header[3] * 256 + header[2];	// Determine The TGA Height (highbyte*256+lowbyte)
    
    if (texture->width <= 0 ||	// Is The Width Less Than Or Equal To Zero
	texture->height <= 0 ||		// Is The Height Less Than Or Equal To Zero
	(header[4] != 24 && header[4] != 32 && header[4] != 8))			// Is The TGA 24 or 32 Bit?
	{
		fclose(file);		// If Anything Failed, Close The File
		return false;
	}
	flipped = (header[5] & 32) != 0; // Testa om flipped

// That old power of 2 is no longer needed!
//	w = 1;
//	while (w < texture->width) w = w << 1;
//	h = 1;
//	while (h < texture->height) h = h << 1;
//	texture->texWidth = (GLfloat)texture->width / w;
//	texture->texHeight = (GLfloat)texture->height / h;
	
	texture->bpp = header[4];		// Grab The TGA's Bits Per Pixel (24 or 32)
	bytesPerPixel = texture->bpp/8;		// Divide By 8 To Get The Bytes Per Pixel
	imageSize = texture->width * texture->height * bytesPerPixel;	// Calculate The Memory Required For The TGA Data
	rowSize	= texture->width * bytesPerPixel;	// Image memory per row
	stepSize = texture->width * bytesPerPixel;		// Memory per row
	texture->imageData = (unsigned char *)calloc(1, imageSize);	// Reserve Memory To Hold The TGA Data
	if (texture->imageData == NULL)				// Does The Storage Memory Exist?
	{
		fclose(file);
		return false;
	}

	// Set rowP and step depending on direction
	// Inverted this 120131, since a texture came out wrong.
	// I am still not sure this is OK.
	if (flipped)
	{
		step = -stepSize;
		rowP = &texture->imageData[imageSize - stepSize];
		row = 0 + (texture->height -1) * stepSize;
	}
	else
	{
		step = stepSize;
		rowP = &texture->imageData[0];
		row = 0;
	}

	if (actualHeader[2] == 2 || actualHeader[2] == 3) // uncompressed
	{
		for (i = 0; i < texture->height; i++)
		{
			bytesRead = fread(rowP, 1, rowSize, file);
			rowP += step;
			if (bytesRead != rowSize)
			{
				free(texture->imageData);	// If So, Release The Image Data
				fclose(file);			// Close The File
				return false;			// Return False
			}
		}
	}
	else
	{ // compressed
//		row = 0 + (texture->height -1) * stepSize;
		i = row;
		rowLimit = row + rowSize;
		do
		{
			bytesRead = fread(&rle, 1, 1, file);
			if (rle < 128)
			{ // rle+1 raw pixels
				bytesRead = fread(&texture->imageData[i], 1, (rle+1)*bytesPerPixel, file);
				i += bytesRead;
				if (bytesRead == 0)
					i = imageSize;
			}
			else
			{ // range of rle-127 pixels with a color that follows
				bytesRead = fread(&pixelData, 1, bytesPerPixel, file);
				do
				{
					for (unsigned b = 0; b < bytesPerPixel; b++)
						texture->imageData[i+b] = pixelData[b];
					i += bytesPerPixel;
					rle = rle - 1;
				} while (rle > 127);
			}
			if (i >= rowLimit)
			{
				row = row + step; // - stepSize;
				rowLimit = row + rowSize;
				i = row;
			}
		} while (i < imageSize);
	}

	if (bytesPerPixel >= 3) // if not monochrome	
	for (i = 0; i < (imageSize); i += bytesPerPixel)	// Loop Through The Image Data
	{		// Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
		temp = texture->imageData[i];		// Temporarily Store The Value At Image Data 'i'
		texture->imageData[i] = texture->imageData[i + 2];	// Set The 1st Byte To The Value Of The 3rd Byte
		texture->imageData[i + 2] = temp;	// Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
	}
	fclose (file);

//texture->w = w;
//texture->h = h;

	return true;				// Texture loading Went Ok, Return True
}

void freeTextureData(TextureData* textureData)
{
    free(textureData->imageData);
}

// ALL CODE BELOW ADDED BY INGEMAR 2026 AND MIGHT FIT BETTER IN OTHER FILES
// Very new code, may have limitations and bugs.

// Parser for parsing shaders for information for creating the pipeline using svkBuildPipeline
// I should rewrite for C++ strings... sometime.

static char* readFile(const char *file)
{
	FILE *fptr;
	long length;
	char *buf;

	if (file == NULL)
			return NULL;

	// It seems Windows/VS doesn't like fopen any more, but fopen_s is not on the others.
	#if defined(_WIN32)
		fopen_s(&fptr, file, "r");
	#else
		fptr = fopen(file, "rb"); // rw works everywhere except Windows?
	#endif
//	fptr = fopen(file, "rb"); /* Open file for reading */
	if (!fptr) /* Return NULL on failure */
		return NULL;
	fseek(fptr, 0, SEEK_END); /* Seek to the end of the file */
	length = ftell(fptr); /* Find out how many bytes into the file we are */
	buf = (char*)malloc(length+1); /* Allocate a buffer for the entire length of the file and a null terminator */
	memset(buf, 0, sizeof(char)*(length + 1)); /* Clean the buffer - suggested for safety by Mateusz 2016 */
	fseek(fptr, 0, SEEK_SET); /* Go back to the beginning of the file */
	int result = fread(buf, length, 1, fptr); /* Read the contents of the file in to the buffer */
	if (result == 0) fprintf(stderr, "Read no data from %s\n", file);
	fclose(fptr); /* Close the file */
	buf[length] = 0; /* Null terminator */

	return buf; /* Return the buffer */
}

#define kAlphaNumericToken 1
#define kNumberToken 2
#define kSpecialCharacterToken 3
#define kNothing 4
#define kComment 5
#define kEOF 0

static int getToken(char *b, int &i, char *token)
{
	char c = b[i];
	// Get a token
	while (c == ' ' || c == 10 || c == 13) // skip
	{
		i++;
		c = b[i];
	}

	if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_'))
	{ // alphanumeric token
		int l = 0;
		c = b[i];
		while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') || (c >= '0' && c <= '9'))
		{
			i++;
			token[l++] = c;
			c = b[i];
		}
		token[l] = 0;
		return kAlphaNumericToken;
	}
	else
	if ((c >= '0' && c <= '9') || (c == '-'))
	{ // number
		int l = 0;
		c = b[i];
		while ((c == '_') || (c == '.') || (c >= '0' && c <= '9'))
		{
			i++;
			token[l++] = c;
			c = b[i];
		}
		token[l] = 0;
		return kNumberToken;
	}
	else
	if (c == kEOF)
	{
		return kEOF;
	}
	else
	{ // Special characters
	// How to get a comment? Important!
	// // = search for end of line or EOF
	// / * search for */ or EOF
		char d;
		i++;
		if (c != 0)
			d = b[i];
		if (c == 0 || c != '/')
		{
		  token[0] = c;
		  token[1] = 0;
		return kSpecialCharacterToken;
		}
		else
		if (c == '/')
		{
		  if (d == '/')
		  { // Search for CR or EOF
		    int l = 0;
			while (c != 10 && c != 13 && c != 0) // skip
			{
				i++;
				c = b[i];
				token[l++] = c;
			}
			token[l] = 0;
			return kComment;
		  }
		  else
		  if (d == '*')
		  { // Search for */
		    int l = 0;
			while (c != '*' && d != '/' && c != 10 && c != 13 && c != 0) // skip
			{
				i++;
				d = c;
				c = b[i];
				token[l++] = c;
			}
			token[l] = 0;
			return kComment;
		  }
		}
		else // A / that is not a comment
		{
		  token[0] = c;
		  token[1] = 0;
		  return kSpecialCharacterToken;
		}

		
	}
	return 0;
}

// Output structs and data
//static int pushVertex, pushFragment
int numBuffers;
//static int dataRates[3]; // Assuming only vertex, normals, texcoords - or not!
/*struct Binding
{
   int binding;
   char name[1024]; // or string name
   bool texture;
};
// location names and numbers for attributes?
struct Attribute
{
	int location;
	char name[1024]; // or string name
	int size;
};*/
static int push = 0; // Intermediate storage for push constants
static std::vector<Attribute> attributes;
static std::vector<Binding> bindings;

static void parser(char *b, bool isFrag)
{
	char token[1024];
	int tokenType = -1;
	int i = 0;
	
	while (tokenType != kEOF)
	{
		tokenType = getToken(b, i, token);
		if (strcmp(token, "main") == 0) // No declarations are expected after main()
		  return;

		if (strcmp(token, "layout") == 0)
		{
			tokenType = getToken(b, i, token); // Should be (
			tokenType = getToken(b, i, token); // Should be location, binding or push_constant
			if (strcmp(token, "push_constant") == 0)
			{
				// ) uniform constants { sequence of layout, get sizes for vertex and fragment
				while (strcmp(token, "{") != 0 && tokenType != kEOF)
					tokenType = getToken(b, i, token); // Just read to {
				
				// Read anything and if we find a known type, add it!
				while (strcmp(token, "}") != 0 && tokenType != kEOF)
				{
					tokenType = getToken(b, i, token);
					if (strcmp(token, "float") == 0)
						{push += sizeof(float);}
					if (strcmp(token, "mat4") == 0)
						{push += 16*sizeof(float);}
					if (strcmp(token, "mat3") == 0)
						{push += 9*sizeof(float);}
					if (strcmp(token, "vec3") == 0)
						push += 3*sizeof(float);
					if (strcmp(token, "vec4") == 0)
						push += 4*sizeof(float);
					if (strcmp(token, "int") == 0)
						push += sizeof(int);
					// Add anything we need
				}
				// Just add sizes for vertex and fragment
				// Names do not matter
				// Should skip comments
			}
			else if (strcmp(token, "location") == 0)
			{
				Attribute a;
				if (!isFrag)
				{
					tokenType = getToken(b, i, token); // Should be =
					tokenType = getToken(b, i, token); // Should be number - SAVE!
					a.location = atoi(token);
					//int locnr = atoi(token);
					tokenType = getToken(b, i, token); // Should be )
					tokenType = getToken(b, i, token); // Should be in or out. If out, ignore!
					bool isIn = (strcmp(token, "in") == 0);
					tokenType = getToken(b, i, token); // Should be type, assume vec2 or vec3
					 a.size = 1; // default
					if (strcmp(token, "vec3") == 0) a.size = 3;
					if (strcmp(token, "vec2") == 0) a.size = 2;
					
					tokenType = getToken(b, i, token); // Should be name - SAVE
					strcpy(a.name, token);
					
					if (isIn)
					{
	      				  attributes.push_back(a);
					}
				
				} // Skip if frag

				// Append to list of attributes
				
				// If out, or in to fragment, ignore
				// If in to vertex, parse type and name.
				// collect count, location number, name, type size (usually vec2 or vec3)
				// Vital to pipeline!
			}
			else if (strcmp(token, "binding") == 0)
			{
				Binding bnd;
				tokenType = getToken(b, i, token); // Should be =
				tokenType = getToken(b, i, token); // Should be number - SAVE!
				int bindnr = atoi(token);
				bnd.binding = bindnr;
				tokenType = getToken(b, i, token); // Should be )
				tokenType = getToken(b, i, token); // Should be uniform
				tokenType = getToken(b, i, token); // Can be sampler2D (or sampler3D of course). If not it ges messy.
				// if sampler2D
				if (strcmp(token, "sampler2D") == 0)
				{
					tokenType = getToken(b, i, token); // name, save!
					strcpy(bnd.name, token);
					bnd.texture = true;
				}
				else
				{
					strcpy(bnd.name, token); // name of uniform
					bnd.texture = false;
					bnd.buffersize = 0;
				}
				// else name of struct. What to save?
				
				bindings.push_back(bnd);
				
				// binding number, sampler or not, name
				// Important for looking up bindings from host, and for numbers to pipeline
			}
			else
			{
			}
		}
		
		if (tokenType != kEOF && tokenType != kNothing)
		{};
	}
}

int svkGetAttributeLocation(std::vector<Attribute> attributes, const char *s)
{
	for (unsigned int i = 0; i < attributes.size(); i++)
	{
		if (strcmp(attributes[i].name, s) == 0)
			return attributes[i].location;
	}
	return -1;
}
int svkGetBinding(std::vector<Binding> bindings, const char *s)
{
	for (unsigned int i = 0; i <bindings.size(); i++)
		if (strcmp(bindings[i].name, s) == 0)
			return bindings[i].binding;
	return -1;
}

int svkGetAttributeLocationIndex(std::vector<Attribute> attributes, const char *s)
{
	for (unsigned int i = 0; i < attributes.size(); i++)
	{
		if (strcmp(attributes[i].name, s) == 0)
			return i;
	}
	return -1;
}
int svkGetBindingIndex(std::vector<Binding> bindings, const char *s)
{
	for (unsigned int i = 0; i <bindings.size(); i++)
		if (strcmp(bindings[i].name, s) == 0)
			return i;
	return -1;
}

char *buffer;

void getShaderInfo(const char *fileName, int &pushConstantSize, std::vector<Attribute> &attributesList,
std::vector<Binding> &bindingsList, bool isFrag)
{
	push = 0;
	buffer = readFile(fileName);
	parser(buffer, isFrag /* is frag = skip "in" locations */);
		
	attributesList = attributes;
	bindingsList = bindings;
	pushConstantSize = push;
	
	free(buffer);
}

// Shader/pipeline extensions. Sorry about the prefix mess (svkSomething vs svk.someThing) but that is because these calls are not in the svk file/class.

// Smart pipeline builder, based on parsing the shaders
void svkBuildPipeline(PipelineData &p, const char *vertexShader, const char *fragmentShader)
{
        VertexInputDescriptionHandler handler;
        int dataRates[3] = {0,0,0};
        int pushConstantSizeVertex = 0;
        int pushConstantSizeFragment = 0;
        p.attributesList = {};
        p.bindingsList = {};
        attributes = {};
        bindings = {};

      // Critical step: Parse the shaders for information so we don't have to do it manually!
      getShaderInfo(vertexShader, pushConstantSizeVertex, p.attributesList, p.bindingsList, false); // parser!
      getShaderInfo(fragmentShader, pushConstantSizeFragment, p.attributesList, p.bindingsList, true); // parser!

      for (uint i = 0; i < p.attributesList.size(); i++)
        {dataRates[i] = p.attributesList[i].size;}


        for (uint i = 0; i < p.attributesList.size(); i++)
        {
          handler.addBindingDescription(i, dataRates[i]*sizeof(float)); // parameters: binding, stride
          handler.addAttributeDescription(i, i, VK_FORMAT_R32G32B32_SFLOAT, 0); // parameters: location, binding, format, offset
	}
        if (p.bindingsList.size() > 0)
        {
          std::vector<std::pair<uint32_t, VkDescriptorType>> bindings;
          for (unsigned int i = 0; i < p.bindingsList.size(); i++)
          {
           if (p.bindingsList[i].texture)
            bindings.push_back({p.bindingsList[i].binding, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
          else
            bindings.push_back({p.bindingsList[i].binding, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER});
          }
//          if (pushConstantSizeVertex + pushConstantSizeFragment == 0)
//  	    svk.initDescriptor(p.descriptorSetLayout, 0, bindings);
  	    // THIS WAS WRONG! I thought I was configuring push constants but it seems to be uniforms?!
  	    // If so, should I count uniforms in fragment and vertex?
//  	  else if (pushConstantSizeVertex == 0)
//  	    svk.initDescriptor(p.descriptorSetLayout, VK_SHADER_STAGE_FRAGMENT_BIT, bindings);
//  	  else if (pushConstantSizeFragment == 0)
//  	    svk.initDescriptor(p.descriptorSetLayout, VK_SHADER_STAGE_VERTEX_BIT, bindings);
//  	  else
  	    svk.initDescriptor(p.descriptorSetLayout, VK_SHADER_STAGE_FRAGMENT_BIT + VK_SHADER_STAGE_VERTEX_BIT, bindings);
	}
	else
          svk.initEmptyDescriptor(p.descriptorSetLayout);
	
	svk.initPipelineLayout(p.pipelineLayout, p.descriptorSetLayout, pushConstantSizeVertex, pushConstantSizeFragment);
	svk.initPipeline(p.pipeline, p.pipelineLayout, handler, vertexShader, fragmentShader);
	
	// Set back to defaults
        svk.setDepthTestEnabled(true);
        svk.setCullMode(VK_CULL_MODE_FRONT_BIT);	
}

// DRAFT TESTER!
// Make my own test for errors, in order to report some errors by name
// To begin with, search for duplicates
// Bindings: Good test. Attributes: Superfluous since the shader compiler stops.
// Also test varyings?
void svkValidator(PipelineData &p)
{
      for (uint i = 0; i < p.attributesList.size(); i++)
      for (unsigned int i = 0; i < p.attributesList.size(); i++)
      for (unsigned int j = i+1; j < p.attributesList.size(); j++)
        {
        if (p.attributesList[i].location == p.attributesList[j].location)
          printf("Warning! Attributes '%s' and '%s' has the same location %d\n", p.attributesList[i].name, p.attributesList[j].name, p.attributesList[i].location);
        }
      if (p.bindingsList.size() > 0)
      for (unsigned int i = 0; i < p.bindingsList.size(); i++)
      for (unsigned int j = i+1; j < p.bindingsList.size(); j++)
        {
        if (p.bindingsList[i].binding == p.bindingsList[j].binding)
          printf("Warning! Uniforms %s %s has the same binding %d\n", p.bindingsList[i].name, p.bindingsList[j].name, p.bindingsList[i].binding);
        }

}

void svkAllocateAndUploadVertexBuffer(AllocatedBuffer &buffer, float *bufferData, int bufferSize)
{
  svk.allocateVertexBuffer(buffer, bufferSize);
  svk.uploadToGPUBuffer(buffer, bufferData, bufferSize);
}

void svkAllocateAndUploadIndexBuffer(AllocatedBuffer &buffer, uint32_t *bufferData, int bufferSize)
{
  svk.allocateIndexBuffer(buffer, bufferSize);
  svk.uploadToGPUBuffer(buffer, bufferData, bufferSize);
}

void svkBindPipelineData(VkCommandBuffer cmd, PipelineData &p)
{
  svk.beginBindings(p.descriptorSetLayout);
  // Tell pld what data that goes with each binding? Requires adding how to replace them.
   for (unsigned int i = 0; i < p.bindingsList.size(); i++)
   {
      //printf("binding #%d\n", i);
      if (p.bindingsList[i].texture)
      {
      //printf("texture binding #%d is %d\n", i, p.bindingsList[i].binding);
        svk.bindTexture(&p.bindingsList[i].image, p.bindingsList[i].binding);
      }
      else
      {
         //printf("uniform binding #%d is %d with size %d\n", i, p.bindingsList[i].binding, p.bindingsList[i].buffersize);
         if (p.bindingsList[i].buffersize <= 0)
           printf("uniform %s not defined. Is svkInstallUniform missing or error in shader?\n", p.bindingsList[i].name);
         else
           svk.bindUniform(p.bindingsList[i].buffer, p.bindingsList[i].buffersize, p.bindingsList[i].binding);
      }
   }

  svk.endBindings(cmd, p.pipelineLayout);
}

void printPipelineData(PipelineData p)
{
        int numTex = 0, numBind = 0;
      printf("number of attributes = %ld\n", p.attributesList.size());
      printf("number of bindings = %ld\n", p.bindingsList.size());
      for (unsigned int i = 0; i < p.bindingsList.size(); i++)
        if (p.bindingsList[i].texture)
        {
        numTex ++;
        printf("texture %s has binding %d\n", p.bindingsList[i].name, p.bindingsList[i].binding);
        }
        else
        {  numBind ++;
                printf("uniform %s has binding %d with size %d\n", p.bindingsList[i].name, p.bindingsList[i].binding, p.bindingsList[i].buffersize);
        }
      printf("number of texture bindings = %d\n", numTex);
      printf("number of non-texture (uniform) bindings = %d\n", numBind);
      for (uint i = 0; i < p.attributesList.size(); i++)
      {
        printf("attribute: %d %s %d\n", p.attributesList[i].location,  p.attributesList[i].name, p.attributesList[i].size);
      }
      for (uint i = 0; i < p.attributesList.size(); i++)
        {
          printf("datarate %d is %d\n", i, p.attributesList[i].size);
        }

}

void svkInstallUniform(PipelineData &p, const char *name, AllocatedBuffer buffer, int bufferSize)
{
 int i = svkGetBindingIndex(p.bindingsList, name);
 if (i >= 0)
 {
   if (p.bindingsList[i].texture == true) printf("Warning! %s is a texture!\n", name);
   p.bindingsList[i].buffer = buffer;
   p.bindingsList[i].buffersize = bufferSize;
 }
 else
  printf("%s not found!\n", name);
}

void svkInstallTexture(PipelineData &p, const char *name, AllocatedImage texture)
{
   int i = svkGetBindingIndex(p.bindingsList, name);
   if (i >= 0)
   {
     if (p.bindingsList[i].texture == false) printf("Warning! %s is not a texture!\n", name);
     p.bindingsList[i].image = texture;
   }
   else
    printf("%s not found!\n", name);
}

void svkBindVertexBuffer(PipelineData pld, VkCommandBuffer cmd, AllocatedBuffer &buffer, const char *name)
{
  int binding = svkGetAttributeLocation(pld.attributesList, name);

  VkDeviceSize offsets[] = {0};
  vkCmdBindVertexBuffers(cmd, binding, 1, &buffer.buffer, offsets);
}



// LittleOBJLoader extensions, load and draw. Does not support interleaving yet.

Model *_internalLoadModel(ModelData *modelData)
{
        if (modelData == NULL)
        {
           fprintf(stderr, "Failed to load data\n");
           return NULL;
        }
         Model *model;
        model = (Model *)malloc(sizeof(Model));
        model->modelData = modelData;
        
        printf("Texture coords: \n");
        for (int i = 0; i < modelData->numVertices; i++)
          printf("%f %f \n", modelData->texCoordArray[i].s, modelData->texCoordArray[i].t);
        printf("numIndices %d \n", modelData->numIndices);
        for (int i = 0; i < modelData->numIndices; i++)
          printf("Index %d \n", modelData->indexArray[i]);

        size_t vertexBufferSize = model->modelData->numVertices * sizeof(vec3);
	svk.allocateVertexBuffer(model->vertexBuffer, vertexBufferSize);
	svk.uploadToGPUBuffer(model->vertexBuffer, model->modelData->vertexArray, vertexBufferSize);

        size_t normalBufferSize = model->modelData->numVertices * sizeof(vec3);
	svk.allocateVertexBuffer(model->normalBuffer, normalBufferSize);
	svk.uploadToGPUBuffer(model->normalBuffer, model->modelData->normalArray, normalBufferSize);
	
	if (model->modelData->texCoordArray != NULL)
	{
	  size_t texcoordBufferSize = model->modelData->numVertices * sizeof(vec2);
	  svk.allocateVertexBuffer(model->texcoordBuffer, texcoordBufferSize);
	  svk.uploadToGPUBuffer(model->texcoordBuffer, model->modelData->texCoordArray, texcoordBufferSize);
	  model->hasTexture = true;
	}
	else
	  model->hasTexture = false;
//	  model->texcoordBuffer = NULL;

	size_t indexBufferSize = model->modelData->numIndices * sizeof(uint32_t);
	svk.allocateIndexBuffer(model->indexBuffer, indexBufferSize);
	svk.uploadToGPUBuffer(model->indexBuffer, model->modelData->indexArray, indexBufferSize);
	return model;
}

Model *LoadModel(const char *filename)
{
        return _internalLoadModel(LoadModelData(filename));
}

Model *LoadDataToModel(
              vec3 *vertices,
              vec3 *normals,
              vec2 *texCoords,
              uint *indices,
              int numVert,
              int numIndices
              )
{
        ModelData *modelData = (ModelData *)malloc(sizeof(ModelData));
	memset(modelData, 0, sizeof(ModelData));

        modelData->vertexArray = vertices;
        modelData->normalArray = normals;
        modelData->texCoordArray = texCoords;
        modelData->indexArray = indices;
        modelData->numVertices = numVert;
        modelData->numIndices = numIndices;
        
        return _internalLoadModel(modelData);
}

void DrawModel(Model *model, VkCommandBuffer cmd, int b0, int b1, int b2)
{
	VkDeviceSize offsets[] = {0};
	
	vkCmdBindVertexBuffers(cmd, b0, 1, &model->vertexBuffer.buffer, offsets); // parameters: commandBuffer, firstBinding, bindingCount, pBuffers. pOffsets
	vkCmdBindVertexBuffers(cmd, b1, 1, &model->normalBuffer.buffer, offsets);
	if (model->hasTexture)
	{
	  vkCmdBindVertexBuffers(cmd, b2, 1, &model->texcoordBuffer.buffer, offsets);
	}
        vkCmdBindIndexBuffer(cmd, model->indexBuffer.buffer, *offsets, VK_INDEX_TYPE_UINT32);
        vkCmdDrawIndexed(cmd, model->modelData->numIndices, 1, 0, 0, 0); // commandbuffer, indexcount, instancecount, firstindex, vertexoffset, firstinstance
}

// Assumes locations 0 1 2. Simple but requires you to know the default.
void DrawModel(Model *model, VkCommandBuffer cmd)
{
  DrawModel(model, cmd, 0, 1, 2);
}

// Look up in the pipeline info. Not fast but easy.
void DrawModel(Model *model, VkCommandBuffer cmd, PipelineData pld, const char *vertex, const char *normal, const char *texCoord)
{
  int b0 = svkGetAttributeLocation(pld.attributesList, vertex);
  int b1 = svkGetAttributeLocation(pld.attributesList, normal);
  int b2 = svkGetAttributeLocation(pld.attributesList, texCoord);

  DrawModel(model, cmd, b0, b1, b2);
}

