#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dir.h>
#include <dirent.h>

#define MERROR_OK 0
#define MERROR_FILENAME_MUST_BE_SPECIFIED 1
#define MERROR_CREATING_DIRECTORY 2
#define MERROR_FILE_NOT_FOUND 3
#define MERROR_NOT_ENOGHT_MEMORY 4
#define MERROR_NOT_ENOGHT_FREE_SPACE 5
#define MERROR_CREATING_FILE 6
#define MERROR_DIRECTORY_NOT_FOUND 7
#define MERROR_DIRECTORY_ALREADY_EXISTS 8

typedef unsigned char Byte;
typedef unsigned short Word;
typedef unsigned int DWord;

Byte boot_loader[70]={
0xB0, 0x13,
0xCD, 0x10, 0xBA, 0x00, 0xA0, 0x8E, 0xDA, 0x8E, 0xC2, 0xB1, 0x04, 0x32, 0xE4, 0xE4, 0x40, 0x03,
0xF0, 0x26, 0xC6, 0x04, 0x0F, 0xE2, 0xF6, 0xBE, 0xC0, 0xF8, 0x26, 0x8A, 0x04, 0x3C, 0x0F, 0x75,
0x1E, 0xE4, 0x40, 0x02, 0xE0, 0x8A, 0xDC, 0x83, 0xE3, 0x03, 0x74, 0x13, 0x81, 0xC3, 0x3E, 0x01,
0x26, 0x8A, 0x00, 0x0A, 0xC0, 0x75, 0x08, 0x26, 0xC6, 0x04, 0x00, 0x26, 0xC6, 0x00, 0x0F, 0x4E,
0x75, 0xD8, 0xEB, 0xC5
};

typedef struct {
	Byte jump[3];
	Byte oem[8];
	Word sector_size;
	Byte cluster_size;
	Word sector_reserved;
	Byte fat_count;
	Word root_size;
	Word sector_total;
	Byte media;
	Word fat_size;
	Word sectors_per_track;
	Word head_count;
	Word sectors_hidden;
	} BootRecord;

typedef struct {
	Word
		 sec          :5,
		 min          :6,
		 hour         :5;
	} FileTime;

typedef struct {
	Word
		 day          :5,
		 month		  :4,
		 year         :7;
	} FileDate;

typedef struct {
	Byte name[8];
	Byte ext[3];
	Byte
		 attr_readonly:1,
		 attr_hidden  :1,
		 attr_system  :1,
		 attr_volume  :1,
		 attr_dir     :1,
		 attr_archv   :1,
		 attr_none    :2;
	Byte reserved[0x0A];
	FileTime time;
	FileDate date;
	Word ClstrNo;
	DWord filesize;
	} DirectoryEntry;

typedef struct {
	Word start_cluster;
	Word curent_record;
	Word curent_record_in_cluster;
	Word last_record_in_cluster;
	Word curent_cluster;
	Word last_cluster;
	bool root;
	} m_DIR;

typedef struct {
	Word start_cluster;
	Word curent_cluster;
	DWord curent_pos;
	DWord curent_write_pos;
	Word pos_in_cluster;
	Word write_pos_in_cluster;
	DWord size;
	Word size_cluster;
	Word size_pos;
	bool root;
	} m_FILE;

/* Глобальные переменные #####################################################*/
FILE *disk;
BootRecord boot;
Byte* sector;
Word* FAT;
Byte* cluster;
Word clusters_total;
Word last_read_cluster;

/* Чтение сектора и кластера #################################################*/
void sector_Read(Byte *where, Word num) {
	fseek(disk,num*boot.sector_size,SEEK_SET);
	fread(where,1,boot.sector_size,disk);
	}               

void cluster_Read(Byte *where, Word cluster) {
	Word sector;

	Word roodir_sectors=(boot.root_size*32)/boot.sector_size;
	Word fat_sectors=boot.fat_count*boot.fat_size;
	Word data_start=boot.sector_reserved+fat_sectors+roodir_sectors;
	sector=data_start+((cluster-2)*boot.cluster_size);
	for (Word i=0;i<boot.cluster_size;i++)
		sector_Read(where+(i*boot.sector_size),sector+i);
	last_read_cluster=cluster;
	}

/* Запись сектора и кластера #################################################*/
void sector_Write(Byte *where, Word num) {
	fseek(disk,num*boot.sector_size,SEEK_SET);
	fwrite(where,1,boot.sector_size,disk);
	}               

void cluster_Write(Byte *where, Word cluster) {
	Word sector;

	Word roodir_sectors=(boot.root_size*32)/boot.sector_size;
	Word fat_sectors=boot.fat_count*boot.fat_size;
	Word data_start=boot.sector_reserved+fat_sectors+roodir_sectors;
	sector=data_start+((cluster-2)*boot.cluster_size);
	for (Word i=0;i<boot.cluster_size;i++)
		sector_Write(where+(i*boot.sector_size),sector+i);
	}

/* Возвращает кластер - точку входа в файл по его имени (для каталогов тоже) #*/
Word get_Cluster(Byte *path) {
	DirectoryEntry dir[16];
	Byte *pToken;
	Byte *ss=strdup(path);
	Byte str[32];
	Word find;
	Word next_cluster=0;
	Word find_cluster=0;

	if (path[0]==0) {
		free(ss);
		return(1);
		}

	pToken = strtok(ss,"\\");
	while (pToken != NULL) {
		find=0;
        find_cluster=0;
		if (next_cluster==0) { /* Ищем PToken в корневом каталоге */
			for (Word j=0;j<boot.root_size/32;j++) {
				sector_Read((Byte*)&dir,j+boot.sector_reserved+boot.fat_count*boot.fat_size);
				for (Word i=0;i<16;i++) {
					if (dir[i].attr_volume==0 && dir[i].name[0]!=0) {
						memset(str,0,32);memcpy(str,dir[i].name,8);
						Word k=0;while (str[k]!=32 && str[k]!=0) k++;str[k]=0;
						if (dir[i].ext[0]!=0 && dir[i].ext[0]!=32) {
							strcat(str,".");
							memcpy(str+strlen(str),dir[i].ext,3);
							Word k=0;while (str[k]!=32 && str[k]!=0) k++;str[k]=0;
							}
						if (strcmp(str,pToken)==0) {
							find=1;
							find_cluster=dir[i].ClstrNo;
							}
						} /* of if dir[i] */
					} /* of for (i) */
				} /* of for (j) */
			} /* of if (next_cluster */
		else { /* Ищем PToken в НЕ корневом каталоге */
			do {
				Word roodir_sectors=(boot.root_size*32)/boot.sector_size;
				Word fat_sectors=boot.fat_count*boot.fat_size;
				Word data_start=boot.sector_reserved+fat_sectors+roodir_sectors;
				Word sector=data_start+((next_cluster-2)*boot.cluster_size);
				for (Word j=0;j<boot.cluster_size;j++) {
					memset(&dir,0,512);
					sector_Read((Byte*)&dir,sector+j);
					for (Word i=0;i<16;i++) {
						if (dir[i].attr_volume==0 && dir[i].name[0]!=0) {
							memset(str,0,32);memcpy(str,dir[i].name,8);
							Word k=0;while (str[k]!=32 && str[k]!=0) k++;str[k]=0;
							if (dir[i].ext[0]!=0 && dir[i].ext[0]!=32) {
								strcat(str,".");
								memcpy(str+strlen(str),dir[i].ext,3);
								Word k=0;while (str[k]!=32 && str[k]!=0) k++;str[k]=0;
								}
							if (strcmp(str,pToken)==0) {
								find=1;
								find_cluster=dir[i].ClstrNo;
								}
							} /* of if dir[i] */
						} /* of for (i) */
					} /* of for (j) */
				next_cluster=FAT[next_cluster];
		 	} while (next_cluster!=0 && next_cluster<clusters_total);
			} /* of else */
	    pToken = strtok(NULL,"\\");
		if (find==0) next_cluster=0; else next_cluster=find_cluster;
		}
	free(ss);
	return(find_cluster);
	}

/* Перекодирует имя файлв из NNNNNNNN.NNN в file.ext #########################*/
Byte* get_NormalName(Byte* name,Byte* ext) {
	Byte str1[16],str2[16];

	memset(str1,0,16);memcpy(str1,name,8);
	Word k=0;while (str1[k]!=32 && str1[k]!=0) k++;str1[k]=0;
	memset(str2,0,16);memcpy(str2,ext,3);
	k=0;while (str2[k]!=32 && str2[k]!=0) k++;str2[k]=0;
	if (strlen(str2)!=0) {
		strcat(str1,".");
		strcat(str1,str2);
		}
	if (str1[0]==229) str1[0]=0;
	return(strdup(str1));
	}


/* Считываем FAT12, преобразуем и записываем в массив ########################*/
void read_FAT(void) {
	Byte *buff=(Byte*)malloc(boot.fat_size*boot.sector_size+1);
	if (!buff) {
		printf("\nError reading FAT !!!\n");
		return;
		}
	/* Читаем FAT12 */
	for (int i=0;i<boot.fat_size;i++)
		sector_Read(buff+(i*boot.sector_size),i+boot.sector_reserved);
	/* Конвертируем FAT12 */
	for (int i=0;i<(boot.fat_size*boot.sector_size*2)/3;i++) {
		Word position=(i+i+i)>>1;
		Word next=buff[position]+(buff[position+1]<<8);
		next=i%2==0?next&0xFFF:(next>>4)&0xFFF;
		FAT[i]=next;
		}

	free(buff);
	}


/* Массив преобразуем в FAT12 и записываем на диск ###########################*/
void write_FAT(int copy) {
	Byte *buff=(Byte*)malloc(boot.fat_size*boot.sector_size);
	memset(buff,0,boot.fat_size*boot.sector_size);
	if (!buff) {
		printf("\nError writing FAT !!!\n");
		return;
		}

	/* Конвертируем FAT12 */
	for (int i=0;i<(boot.fat_size*boot.sector_size*2)/3;i++) {
		Word value=FAT[i]&0xFFF;
		Word position=(i+i+i)>>1;
		if (i%2==0) {
			buff[position+1]=(value>>8)&0x0F;
			buff[position]=value&0xFF;
			}
		else {
			buff[position]=buff[position]|((value<<4)&0xF0);
			buff[position+1]=((value>>4)&0xFF);
			}
		}
	/* Пишем FAT12 */
	for (int i=0;i<boot.fat_size;i++)
		sector_Write(buff+(i*boot.sector_size),i+boot.sector_reserved+((copy-1)*boot.fat_size));

	free(buff);
	}

/* Ищет первый свободный кластер (если 0, то нет больше свободных) ###########*/
Word cluster_findfirst(void) {
	int i=2;
	while (FAT[i]!=0 && i<clusters_total) i++;
	if (FAT[i]==0 && i<clusters_total) return(i); else return(0);
	}

/* UPCASE для русских и латинских бука #######################################*/
Byte rtoupper(Byte ch) {
	if (ch>96 && ch<123) ch=ch-0x20;
	if (ch>159 && ch<176) ch=ch-0x20;
	if (ch==241) ch=240;
	if (ch>223 && ch<240) ch=ch-80;
	return(ch);
	}

/* Стандартные функции работа с каталогом ####################################*/
m_DIR* m_opendir(Byte *path) {
	Word cluster=get_Cluster(path);
	if (cluster==0) return(NULL);
	m_DIR *d=(m_DIR*)malloc(sizeof(m_DIR));
	d->start_cluster=cluster;
	d->curent_record=0;
	d->curent_record_in_cluster=0;
	d->curent_cluster=cluster;
	if (cluster==1) {
		d->root=true;
		d->curent_cluster=boot.fat_count*boot.fat_size+boot.sector_reserved;
		}
	else d->root=false;
	return(d);
	}

void m_closedir(m_DIR* d) {
	if (!d) return;
	free(d);
	}

DirectoryEntry* m_readdir(m_DIR* d, DirectoryEntry* curent_direntry) {
	if (!curent_direntry) return(NULL);

	d->last_record_in_cluster=d->curent_record_in_cluster;
	d->last_cluster=d->curent_cluster;
	if (d->root) {
		sector_Read(sector,d->curent_cluster);
		memcpy(curent_direntry,sector+32*d->curent_record_in_cluster,32);

		d->curent_record_in_cluster++;
		d->curent_record++;
		
		if (d->curent_record_in_cluster>=(boot.sector_size/32)) {
			d->curent_cluster++;
			d->curent_record_in_cluster=0;
			}
		if (d->curent_record>boot.root_size) return(NULL);
		else return(curent_direntry);
		}
	else {
		cluster_Read(cluster,d->curent_cluster);
		memcpy(curent_direntry,cluster+32*d->curent_record_in_cluster,32);

		d->curent_record_in_cluster++;
		d->curent_record++;
		if (d->curent_record_in_cluster>=((boot.sector_size*boot.cluster_size)/32)) {
			d->curent_cluster=FAT[d->curent_cluster];
			d->curent_record_in_cluster=0;
			}
		if (d->curent_cluster>clusters_total) return(NULL);
		else return(curent_direntry);
		}
	}

/* Стандартные функции работы с файлами ######################################*/
m_FILE* m_fopen(char *ipath) {
	if (ipath[0]==0) return(NULL);

	DirectoryEntry *dirent;
	char *path;
	char *iipath=strdup(ipath);

	/* Разделим путь и имя файла */
	char *name=strrchr(iipath,92);
	if (name) {
		name[0]=0;
		name++;
		path=iipath;
		}
	else {
		name=iipath;
		path=name+strlen(name);
		}
	/* Теперь считаем каталог и найдем этот файл. */
	m_DIR *d=m_opendir(path);
	if (!d) {
		free(iipath);
		return(NULL);
		}
	bool find=false;
	m_FILE *f=NULL;
	dirent=(DirectoryEntry*)malloc(sizeof(DirectoryEntry));
	while (m_readdir(d,dirent)!=NULL && !find) {
		char *sname=get_NormalName(dirent->name,dirent->ext);
		if (strcmp(sname,name)==0 && dirent->attr_dir==0 && dirent->attr_volume==0) {
			find=true;
			f=(m_FILE*)malloc(sizeof(m_FILE));
			f->size=dirent->filesize;
			f->start_cluster=dirent->ClstrNo;
			f->curent_cluster=dirent->ClstrNo;
			f->curent_pos=0;
			f->pos_in_cluster=0;    
			f->size_cluster=d->curent_cluster;
			f->size_pos=d->last_record_in_cluster*32+28;
            f->root=d->root;

			/* Ищем то место, где файл кончается, и устанавливаем туда указатель на запись */
			Word clstr=dirent->ClstrNo;
			while (FAT[clstr]<clusters_total) clstr=FAT[clstr];
			f->curent_write_pos=clstr;
			f->write_pos_in_cluster=dirent->filesize%(boot.sector_size*boot.cluster_size);
			}
        free(sname);
		}

	free(dirent);
	m_closedir(d);
	free(iipath);
	return(f);
	}

void m_fclose(m_FILE * fp) {
	if (boot.fat_count>0) write_FAT(1);
	if (boot.fat_count>1) write_FAT(2);
	if (fp->root) sector_Read(cluster,fp->size_cluster);
    else cluster_Read(cluster,fp->size_cluster);
	cluster[fp->size_pos+0]=fp->size&0xFF;
	cluster[fp->size_pos+1]=(fp->size>>8)&0xFF;
	cluster[fp->size_pos+2]=(fp->size>>16)&0xFF;
	cluster[fp->size_pos+3]=(fp->size>>24)&0xFF;
	if (fp->root) sector_Write(cluster,fp->size_cluster);
    else cluster_Write(cluster,fp->size_cluster);
	if (fp) free(fp);

	}

int m_fread(char* where, int isize, int count, m_FILE* f) {
	DWord read_size=0;
	DWord size=isize*count;
	if (size>(f->size-f->curent_pos)) size=f->size-f->curent_pos;
	DWord portion_size=boot.cluster_size*boot.sector_size;

	while (size>0 && f->curent_cluster<clusters_total) {
		if (portion_size>size) portion_size=size;
		cluster_Read(cluster,f->curent_cluster);
		memcpy(where,cluster,portion_size);
		read_size+=portion_size;
		where+=portion_size;
		size-=portion_size;
		f->curent_pos+=portion_size;
		f->curent_cluster=FAT[f->curent_cluster];
		}
	return(read_size);
	}

int m_fwrite(char* where, int isize, int count, m_FILE* f) {
	Word csize=boot.cluster_size*boot.sector_size;
	DWord size=isize*count;
	int error=MERROR_OK;

	while (size!=0 && error==MERROR_OK) {
		cluster_Read(cluster,f->curent_write_pos);
		int copy_size=size;
		if (copy_size>csize-f->write_pos_in_cluster) copy_size=csize-f->write_pos_in_cluster;
		memcpy(cluster+f->write_pos_in_cluster,where,copy_size);
		where+=copy_size;
		f->write_pos_in_cluster+=copy_size;
		size-=copy_size;
		f->size+=copy_size;
		if (csize-f->write_pos_in_cluster>0) memset(cluster+f->write_pos_in_cluster,0,csize-f->write_pos_in_cluster);
		cluster_Write(cluster,f->curent_write_pos);
		if (f->write_pos_in_cluster>=csize) {
			Word new_cluster=cluster_findfirst();
			FAT[f->curent_write_pos]=new_cluster;
			f->curent_write_pos=new_cluster;
			FAT[f->curent_write_pos]=4095;
			f->write_pos_in_cluster=0;
			}
		}
	return(error);
	}

