#include <iostream>
#include <fstream>
#include <math.h>

#define XRES 256
#define YRES 256 

enum {D_AUX, D_PRIM}; 	// the primary dimension is the one that most contributes to the length of the line segment
		  	// while the auxiliary dimension yields the lesser contribution
using namespace std;

ofstream image;
const int HEADER_SIZE = 54;
uint8_t *buffer;

int numberOfPaddingBytes = (4 - ((XRES * 3) % 4)) & 0x03;
int bytesPerRow = (XRES * 3) + numberOfPaddingBytes;

int imageSize = bytesPerRow * YRES;

int int_to_bytes(int valueToConvert, int numberOfBytes, int initOffset)
{
	for (int byte = 0; byte < numberOfBytes; byte++)
	{
	buffer[initOffset + byte] = uint8_t((valueToConvert >> (byte << 3)) & 0xff);
	}

	return initOffset + numberOfBytes;
}

void write_pixel(int R, int G, int B, int x, int y)
{
	int offset = HEADER_SIZE + bytesPerRow * y + x * 3;

	offset = int_to_bytes(B,1,offset);
	offset = int_to_bytes(G,1,offset);
	offset = int_to_bytes(R,1,offset);
}

void add_padding()
{
	int currentOffset = HEADER_SIZE;
	
	for (int y = 0; y < YRES; y++)
		currentOffset = int_to_bytes(0,numberOfPaddingBytes,currentOffset + (XRES * 3));
}

void draw_line(int xInit, int yInit, int xFinal, int yFinal)
{
	int delta[2], inc[2], max, *coord[2]; // the last array was made using pointers to keep the iterating and plotting code as generic
					      // and seperated as possible
	int xDelta = xFinal - xInit;
	int yDelta = yFinal - yInit;

	bool yIsGreater = (abs(yDelta) > abs(xDelta));

	inc[!yIsGreater] = ((xFinal > xInit) << 1) - 1; inc[yIsGreater] = ((yFinal > yInit) << 1) - 1;
	delta[!yIsGreater] = xDelta; delta[yIsGreater] = yDelta;
	max = (!yIsGreater * xFinal) + (yIsGreater * yFinal);
	coord[!yIsGreater] = &xInit; coord[yIsGreater] = &yInit;
	
	bool pDeltaNegative = (delta[D_PRIM] < 0);

	max -= pDeltaNegative; // shitty hack to make sure the below for() loop performs a sufficient number of iterations in spite of the other shitty hack to make its exit condition work in spite of delta[D_PRIM]'s sign. Phew
	
	int fracIncP = abs(delta[D_AUX]) << 1;		// horrible hacky way of decoupling the sign of the x/y coordinate increment logic and the frac increment logic
	int fracIncA = abs(delta[D_PRIM]) << 1;
	int frac = fracIncP - abs(delta[D_PRIM]); // the two ints above were left-shifted to avoid multiplying the second term here by 1/2 and saving us from nasty, no-good floating point arithmetic

	for(; ((*coord[D_PRIM]) <= max) ^ (pDeltaNegative); *coord[D_PRIM] += inc[D_PRIM])
	{
		write_pixel(255,140,0,xInit,yInit); // here's what I meant earlier by "generic and seperated"

		frac += fracIncP;

		if (frac >= 0)
		{
			*coord[D_AUX] += inc[D_AUX]; 
			frac -= fracIncA;
		}
	}
	
}

void generate_image()
{
	add_padding();

	for (int y = 0; y < YRES; y++)
		for (int x = 0; x < XRES; x++)
			write_pixel(0,0,0,x,y);

	draw_line(71,78,119,158);	// entering in these coordinates was tedious but totally worth it
	draw_line(119,158,112,176);	
	draw_line(112,176,96,176);
	draw_line(96,176,96,191);
	draw_line(96,191,123,191);
	draw_line(123,191,163,94);
	draw_line(163,94,177,99);
	draw_line(177,99,183,84);
	draw_line(183,84,151,73);
	draw_line(151,73,128,133);
	draw_line(128,133,93,78);
	draw_line(93,78,71,78);
}


void generate_header()
{ 	
	buffer[0] = 0x42; // the "magic number"
	buffer[1] = 0x4d;
	
	int currentOffset = 2;
	
	currentOffset = int_to_bytes(HEADER_SIZE + imageSize,4,currentOffset);

	currentOffset = int_to_bytes(0,4,currentOffset); // reserved

	currentOffset = int_to_bytes(HEADER_SIZE,4,currentOffset); // offset to data

	currentOffset = int_to_bytes(40,4,currentOffset); // DIB header size

	currentOffset = int_to_bytes(XRES,4,currentOffset);
	currentOffset = int_to_bytes(YRES,4,currentOffset);

	currentOffset = int_to_bytes(1,2,currentOffset); // number of color planes
	currentOffset = int_to_bytes(24,2,currentOffset); // bits per pixel

	currentOffset = int_to_bytes(0,4,currentOffset); // compression type

	currentOffset = int_to_bytes(imageSize,4,currentOffset);
	
	currentOffset = int_to_bytes(0,4,currentOffset); // pixels per meter x
	currentOffset = int_to_bytes(0,4,currentOffset); // pixels per meter y
	
	currentOffset = int_to_bytes(0,4,currentOffset); // number of colors
	currentOffset = int_to_bytes(0,4,currentOffset); // important colors
}

void save_to_bmp()
{
	image.open("bresenham.bmp", ofstream::out | ofstream::trunc);
	
	for (int i = 0; i < (HEADER_SIZE + imageSize); i++)
	{
		image << buffer[i];
	}
	
	image.close();
}

void allocate_memory()
{
	buffer = (uint8_t*)malloc(HEADER_SIZE + imageSize);
}

void free_memory()
{
	free(buffer);
}

int main()
{
	allocate_memory();
	generate_header();
	generate_image();
	save_to_bmp();
	free_memory();	
}
