// 0xXY, where X is background, Y is character color

// In the kernel, the used space must have const address!

// Color console constants:
#define BLACK_BACKGROUND 0x00
#define WHITE_CHARACTER 0x07
#define LIGHT_GREEN_CHARACTER 0x0A
#define GREEN_BACKGROUND 0x20

// Memory map:
#define VIDEO_MODE_MEMORY 0xa000;
#define ROM_CHARACTER_MEMORY 0xffa6e;
#define OS_BOOTCODE 0x7c00 //( - 0x7dff)
#define VIDEO_MEMORY 0xb8000
#define CONVENTIONAL_MEMORY 0x500 //( - 0x7bff ) 30 KiB
#define CONVENTIONAL_MEMORY_2 0x7e00 //( - 0x7ffff ) 480.5 KiB
#define SHELL_COMMAND_BUFFER 0x0500 // (size 256 bytes)
#define EXTENDED_MEMORY 0x100000 // ( - 0xefffff ) 14 MB
#define EXTENDED_MEMORY_END 0xefffff
#define KERNEL_LENGTH 0x00afff
#define MM_BLOCK_SIZE 0xff //256 bytes
/*
	14MB = 14680063 bytes
	14680063 / 1024 = 14335 [blocks]
	14680063 / 512 = 28670 [blocks]
	14680063 / 256 = 57340 [blocks] 
*/

typedef unsigned int uint;

// Free memory managment:
uint mm_free_size = 0;
uint mm_total_blocks = 0;
uint mm_bitmap_size = 0;
char* MM_BITMAP_POINTER = 0;
char* MM_FIRST_BLOCK_POINTER = 0;

#define COLS 80
#define ROWS 25

//Global variables:
uint Cursor_X = 0;
uint Cursor_Y = 0;
uint Color_Char = BLACK_BACKGROUND + WHITE_CHARACTER;
char* userNamePattern = "admin";
char* userPassPattern = "abc";

//Headers of functions:
//I/O port:
unsigned char inp(unsigned short in_port);
void outp(unsigned short in_port, unsigned char in_data);
//Console I/O:
void clearScreen();
uint drawString(char *msg, uint line);
void _drawString(char *msg);
char readRawCharacter();
char readCharacter();
char* readLine();
void drawCharacter(unsigned char in_character);
void disableCursor();
//String:
uint _strequ(char* in_str1, char* in_str2);
uint _strlen(char* in_str);
uint _strequ_n(char* in_str1, char* in_str2, uint in_start, uint in_len);
char* itoa(uint in_number, char* in_buffer);
uint atoi(char* in_str);
void drawNumber(uint in_number);
//font/characters:
void getROMCharacter(char in_character);
//Math:
uint power(uint base, uint degree);
unsigned char getRandomByte();
uint ceilDiv(uint in_number1, uint in_number2);
//Date and time:
void drawTime();
//Bit
uint getBit(char in_byte, uint in_pos);
uint getSequenceBit(char* in_bytes, uint in_pos);
char setBit(char, uint, uint);
void setSequenceBit(char*, uint in_pos, uint in_bit);
void drawBinByte(char);
//Memory managment:
void clearMemoryBlock(char* in_adrBlock, uint in_size);
void fillMemoryBlock(char* in_adrBlock, uint in_size, char in_pattern);
void memoryMoveBlock(char* in_adrBlockSrc, char* in_adrBlockDest);
char* kmalloc(uint in_size);
void kfree(char* in_blockAdr);
void memoryUsage();

//Interrupt:
void timerFreqConfig(uint in_freq);
void install_interrupt(uint in_irq, uint* in_procAdr);

void install_interrupt(uint in_irq, uint* in_procAdr){
}

void timerFreqConfig(uint in_freq){
	// Calculate divisor:
	uint divisor = 1193180 / in_freq;
	// Set the command byte 0x36 in 0x43 register;
	outp(0x43, 0x36);
	// Low byte of divisor:
	outp(0x40, divisor & 0xff);
	// High byte of divisor:
	outp(0x40, divisor >> 8);
}

uint timer_ticks = 0;
void timer_handler(){
	timer_ticks++;
	if(timer_ticks % 18 == 0){
		_drawString("One second has passed\n");
	}
}


void drawNumber(uint in_number){
	char* b = "               ";
	itoa(in_number, b);
	_drawString(b);
}

uint ceilDiv(uint in_number1, uint in_number2){
	//General in_number1 / in_number2
	if(in_number1 % in_number2 == 0){
		return in_number1 / in_number2;
	}else{
		return in_number1 / in_number2 + 1;
	}
}

/*
	MAIN KERNEL PROCEDURE
*/
void kmain(){
	//uint timerProc = (uint)timer_handler;
	//uint* ivt = (uint*)0x0020;
	//ofset
	//ivt[0] = 0;
	//ivt[1] = 0;
	//base segment
	//ivt[2] = 0x00;
	//ivt[3] = 0x00;
	//asm("cli");
	//ivt[0] = timerProc;
	//asm("sti");

	disableCursor();
	clearScreen();
	//getROMCharacter('A');
	//return;

	// MEMORY INITIALISE:
	// Size of free memory:
	mm_free_size = EXTENDED_MEMORY_END - (EXTENDED_MEMORY + KERNEL_LENGTH);
	// Total blocks after division:
	mm_total_blocks = mm_free_size / MM_BLOCK_SIZE;
	// Start of first block, after bitmap:
	// Bitmap is information of free blocks
	MM_BITMAP_POINTER = (char*)(EXTENDED_MEMORY + KERNEL_LENGTH);
	mm_bitmap_size = (mm_total_blocks/MM_BLOCK_SIZE);
	MM_FIRST_BLOCK_POINTER = (char*)(MM_BITMAP_POINTER + mm_bitmap_size*256);
	for(uint i=0; i<(mm_bitmap_size); i++){
		setSequenceBit(MM_BITMAP_POINTER, i, 1);
	}
	for(uint i=mm_bitmap_size; i<(mm_total_blocks); i++){
		setSequenceBit(MM_BITMAP_POINTER, i, 0);
	}
	MM_BITMAP_POINTER[0] = setBit(MM_BITMAP_POINTER[0],0,1);
	kmalloc(256);

	//drawNumber(ceilDiv(200,256));
	//memoryUsage();
	//return;

/*	char* start = (char*)(0x100000+8000);
	//char* b = "       ";
	char c;
	for (uint i = 0x00; i<2000; i++){
		//start[i] = 0x00;
		//itoa(start[i], b);
		//_drawString("[");
		c = start[i];
		if(c == 0x00){
			c = '_';
			continue;
		}
		drawCharacter(c);
		//_drawString("]");
	}
	return;
*/
beginMainLoop:
	// Clear screen:
	clearScreen();
	// Show welcome message:
	_drawString("System Milicja Obywatelska\n");
	_drawString("Wersja 1.00");
	// LOGIN SECTION:
	while(1){
		int validUserName = 0;
		int validPass = 0;
		// USER prompt:
		_drawString("\n(user)>>");
		char* shellCmd = readLine();
		if(_strequ(shellCmd, userNamePattern)==0){
			validUserName = 1;
		}
		// PASS prompt:
		_drawString("\n(pass)>>");
		shellCmd = readLine();
		if(_strequ(shellCmd, userPassPattern)==0){
			validPass = 1;
		}
		if(validUserName==1 && validPass==1){
			break;
		}
		_drawString("\nInvalid login");	
	}

	// MAIN KERNEL LOOP:
	while(1){
		// Show general prompt:
		_drawString("\n(?)>>");
		// Read line from console:
		char* shellCmd = readLine();
		// *((char*)SHELL_COMMAND_BUFFER) = 'a';
		_drawString("\n");
		//_drawString(shellCmd);
		//SHELL INTERPRETER:
		if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"shutdown")==0x00){
			_drawString("It is now safe to turn off your computer");
			break;
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"clear")==0x00){
			clearScreen();
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"logout")==0x00){
			goto beginMainLoop;
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"chpass")==0x00){
			_drawString("\nInput the new password:");
			_drawString("\n(pass)>>");
			char* subShellCmd = readLine();
			userPassPattern = subShellCmd;
			_drawString("The password has been changed");
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"time")==0x00){
			drawTime();
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"reboot")==0x00){
			asm("jmp 0xffff0");
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"mem")==0x00){
			clearScreen();
			memoryUsage();
		}else if(_strequ_n((char*)SHELL_COMMAND_BUFFER, (char*)"vgafont", 0, 6)==0x00){
			if(((char*)SHELL_COMMAND_BUFFER)[7] == ' '){
				Color_Char = BLACK_BACKGROUND | LIGHT_GREEN_CHARACTER;
				getROMCharacter(((char*)SHELL_COMMAND_BUFFER)[8]);
			}
		}else if(_strequ((char*)SHELL_COMMAND_BUFFER, (char*)"debug")==0x00){
			char* b = "          ";
			itoa((uint)kmain, b);
			_drawString("Function kernel start address:");
			_drawString(b);
			_drawString("\n");
		}else{
			_drawString("Unknown command");
		}
	}
};

char* kmalloc(uint in_blockSize){
	uint blks = in_blockSize/MM_BLOCK_SIZE;
	if((in_blockSize % MM_BLOCK_SIZE) != 0){
		blks++;
	}
	// Searching for free space for blocks:
	// In bitmap:
	char* b = "       ";
	for(uint i=0; i<mm_total_blocks; i++){
		
		//drawCharacter(getBit(0xaa, 7-i)+0x30);
		//itoa(power(2,i), b);
		//_drawString(b);
	}
	for(uint i=0; i<mm_total_blocks; i++){
		for(uint j=0; j<8; j++){
			
		}
	}
	//itoa(blks, b);
	//_drawString(b);	
	return 0x00;
}

void drawTime(){
	char minutes;
	char hours;
	char seconds;
	//asm("cli");
	//Read hours:
	outp(0x70, 0x04);
	hours = inp(0x71);
	//Read minutes:
	outp(0x70, 0x02);
	minutes = inp(0x71);
	//Read seconds:
	outp(0x70, 0x00);
	seconds = inp(0x71);
	//asm("sti");

	char* b = "       ";
	//Draw hours:
	itoa(hours >> 4, b);
	_drawString(b);
	itoa(hours & 0x0f, b);
	_drawString(b);
	_drawString(":");
	//Draw minutes:
	itoa(minutes >> 4, b);
	_drawString(b);
	itoa(minutes & 0x0f, b);
	_drawString(b);
	_drawString(":");
	//Draw seconds:
	itoa(seconds >> 4, b);
	_drawString(b);
	itoa(seconds & 0x0f, b);
	_drawString(b);
}

char* itoa(uint in_number, char* in_buffer){
	uint i = 0;
	if(in_number == 0){
		in_buffer[i] = '0';
		i++;
	}
	uint n = in_number;
	uint r;
	while(n != 0){
		r = n % 10;
		if(r > 9){
			in_buffer[i] = (r-10) + 'a';
		}else{
			in_buffer[i] = r + '0';
		}
		//in_buffer[i] = (r>9) ? (r-10) + 'a' : r + '0';
		i++;
		n /= 10;
	}
	in_buffer[i] = '\0';
	char x;
	for(int j=0; j<(i/2); j++){
		//drawCharacter(in_buffer[i-1-j]);
		x = in_buffer[j];
		in_buffer[j] = in_buffer[i-1-j];
		in_buffer[i-1-j] = x;
	}
	//drawCharacter('_');
	return in_buffer;
}

uint atoi(char* in_str){
	uint n = 0;
	for(uint i = 0; in_str[i] != '\0'; ++i){
		n = n * 10 + in_str[i] - '0';
	}
	return n;
}

uint _strlen(char* in_str){
	uint i=0;
	while(1){
		if(*in_str==0x00)
			break;
		//drawCharacter(*in_str);
		in_str++;
		i++;
	}
	return i;
}

uint _strequ_n(char* in_str1, char* in_str2, uint in_start, uint in_len){
	for(uint i=in_start; i<in_len; i++){
		if(*in_str1 != *in_str2){
			return 0x01;
		}
		in_str1++;
		in_str2++;
	}
	return 0x00;
}

uint _strequ(char* in_str1, char* in_str2){
	while(1){
		if(*in_str1 == 0x00){
			if(*in_str2 == 0x00){
				return 0x00;
			}else{
				return 0x01;
			}
		}
		if(*in_str1 != *in_str2){
			return 0x01;
		}
		in_str1++;
		in_str2++;
	}
	/*while(*in_str1 != 0x00){
		if(*in_str1 != *in_str2){
			return 0x01;
		}
		in_str1++;
		in_str2++;
	}

	return 0x00;*/
}

void disableCursor(){
	// Cursor OFF:
	asm("push %ax");
	asm("push %dx");

	asm("mov $0x0a, %al");
	asm("mov $0x03d4, %dx");
	asm("out %al, %dx");

	asm("mov $0x03d5, %dx");
	asm("in %dx, %al");
	asm("or $16, %ax"); //00010000b as 16
	asm("out %ax, %dx");

	asm("pop %dx");
	asm("pop %ax");
}

char* readLine(){
	char* shellCmdBuffer = (char*)SHELL_COMMAND_BUFFER;
	clearMemoryBlock(shellCmdBuffer, 256);
	unsigned char i=0;
	while(1){
		char c = readCharacter();
		if(c == 10){ //Detect Enter scan code:
			//*((char*)SHELL_COMMAND_BUFFER) = 'x';
			//_drawString((char*)SHELL_COMMAND_BUFFER);
			drawCharacter(0xd9);
			return (char*)SHELL_COMMAND_BUFFER;
		}else if(c == 0x08){ //Detect Backspace code:
			Cursor_X--;
			c = ' ';
			drawCharacter(c);
			Cursor_X--;
			shellCmdBuffer[i-1] = c;
			i--;
			
		}else if(c == 0x00){
			//Omit null character
			//Not recognize character.
		}else{
			drawCharacter(c);
			shellCmdBuffer[i] = c;
			i++;
		}
	}
}

char readCharacter(){
		char c1 = readRawCharacter();
		while(1){
			char c2 = readRawCharacter();
			if(c1 != c2)
				return c1;
		}
};

unsigned char inp(unsigned short in_port){
	unsigned char ret;
	asm("in %1, %0" : "=a" (ret) : "d" (in_port));
	return ret;
}

void outp(unsigned short in_port, unsigned char in_data){
	asm("out %1, %0" : : "d" (in_port), "a"(in_data));
}

void drawCharacter(unsigned char in_char){
	if(in_char == 0x00)
		return;
	char* vidmem = (char*)VIDEO_MEMORY;
	vidmem[(Cursor_Y*COLS+Cursor_X)*2] = in_char;
	vidmem[(Cursor_Y*COLS+Cursor_X)*2+1] = Color_Char;
	Cursor_X++;
	if(Cursor_X==COLS){
		//New line:
		Cursor_X=0;
		Cursor_Y++;
		if(Cursor_Y==ROWS){
			Cursor_Y = 0;
		}
	}
}

char readRawCharacter(){
	char c = inp(0x60);
	//for(int i=0; i<10000; i++)
	//	asm("nop");
	switch(c){
		case 30:
			return 'a';
		case 48:
			return 'b';
		case 46:
			return 'c';
		case 32:
			return 'd';
		case 18:
			return 'e';
		case 33:
			return 'f';
		case 34:
			return 'g';
		case 35:
			return 'h';
		case 23:
			return 'i';
		case 36:
			return 'j';
		case 37:
			return 'k';
		case 38:
			return 'l';
		case 50:
			return 'm';
		case 49:
			return 'n';
		case 24:
			return 'o';
		case 25:
			return 'p';
		case 19:
			return 'r';
		case 31:
			return 's';
		case 20:
			return 't';
		case 22:
			return 'u';
		case 47:
			return 'v';
		case 17:
			return 'w';
		case 21:
			return 'y';
		case 44:
			return 'z';
		case 1:
			return 0x00; //ESCAPE
		case 2:
			return '1';
		case 3:
			return '2';
		case 4:
			return '3';
		case 5:
			return '4';
		case 6:
			return '5';
		case 7:
			return '6';
		case 8:
			return '7';
		case 9:
			return '8';
		case 10:
			return '9';
		case 11:
			return '0';
		case 28: //Enter
			return 10;
		case 57: //Space
			return 32;
		case 14:
			return 0x08; //BACKSPACE
		default:
			return 0x00; //NULL char	
	}	
}

void clearMemoryBlock(char* in_adrBlock, uint in_size){
	for(int i=0; i<in_size; i++){
		in_adrBlock[i] = 0x00;
	}
}

void fillMemoryBlock(char* in_adrBlock, uint in_size, char in_pattern){
	for(int i=0; i<in_size; i++){
		in_adrBlock[i] = in_pattern;
	}
}

void clearScreen(){
	Cursor_X = 0;
	Cursor_Y = 0;
	char* screenBuffer = (char*) VIDEO_MEMORY;
	uint i=0;
	while(i < (COLS*ROWS*2)){
		screenBuffer[i]=' '; //Space character
		i++;
		screenBuffer[i]=Color_Char;
		i++;
	};
};

void _drawString(char* msg){
	char* screenBuffer = (char*) VIDEO_MEMORY;
	while(*msg != 0x00){
		if(*msg == '\n'){
			//New line:
			Cursor_X=0;
			Cursor_Y++;
			if(Cursor_Y==ROWS){
				Cursor_Y = 0;
			}
		}else{
			screenBuffer[(Cursor_Y*COLS+Cursor_X)*2] = *msg;
			screenBuffer[(Cursor_Y*COLS+Cursor_X)*2+1] = Color_Char;
			Cursor_X++;
			if(Cursor_X==COLS){
				//New line;
				Cursor_X=0;
				Cursor_Y++;
				if(Cursor_Y==ROWS){
					Cursor_Y=0;
				}
			}
			//*msg++;
			//screenBuffer[(Cursor_Y*COLS+Cursor_X+1)*2] = WHITE_CHARACTER;
		}
		*msg++;
	}
}

uint drawString(char* msg, uint line){
	char *vidmem = (char*)VIDEO_MEMORY;
	uint i=0;
	i=(line*COLS*2);
	while(*msg!=0){
		if(*msg=='\n'){
			line++;
			i=(line*COLS*2);
			*msg++;
		}else{
			vidmem[i]=*msg;
			*msg++;
			i++;
			vidmem[i]=Color_Char;
			i++;
		};
		Cursor_X++;
	};

	return(1);
}

void getROMCharacter(char in_character){
	char* data = (char*)ROM_CHARACTER_MEMORY; //0xffbee
	for(uint i=0; i<8; i++){
		drawBinByte(data[i+8*in_character]);
		_drawString("\n");
	}
}

void drawBinByte(char in_byte){
	uint bit = 0;
	for(uint i=0; i<8; i++){
		bit = getBit(in_byte, 7-i);
		if(bit == 0){
			//drawCharacter(0xb0);
			drawCharacter(' ');
		}else{
			drawCharacter(0xdb);
		}
	}
}

uint power(uint base, uint degree){
	uint result = 1;
	uint term = base;
	while(degree){
		if(degree & 1)
			result *= term;
		term *= term;
		degree = degree >> 1;
	}
	return result;
}

uint getBit(char in_byte, uint in_pos){
	return ((in_byte & power(2,in_pos)) >> in_pos);
}

uint getSequenceBit(char* in_bytes, uint in_pos){
	uint byteAdr = in_pos/8;
	return getBit(in_bytes[byteAdr], (in_pos % 8));
}

char setBit(char in_byte, uint in_pos, uint in_bit){
	char byte;
	if(in_bit == 0){
		byte = in_byte & ~(1 << in_pos);
	}else if(in_bit == 1){
		byte = in_byte | (1 << in_pos);
	}
	return byte;
}

void setSequenceBit(char* in_bytes, uint in_pos, uint in_bit){
	uint byteAdr = in_pos/8;
	in_bytes[byteAdr] = setBit(in_bytes[byteAdr], in_pos % 8, in_bit);
}

void memoryUsage(){
	char b = 0;
	uint e = 2000; //mm_total_blocks;
	for(uint i=0; i<e; i++){
		b = getSequenceBit(MM_BITMAP_POINTER, i);
		if(b == 0){
			drawCharacter(0xb0);
		}else{
			drawCharacter(0xdb);
		}
		//drawCharacter(b+0x30);
	}
}