// Created By Matthew B. Gately

#include <string>
#include <stdio.h>
#include <iostream>
#include "ISAM.h"

using namespace std;

// Default Constructor
ISAM::ISAM(size_t object_size, string file_name) {
	size = object_size;
	file_name_base = file_name;
	// Specify File Name For Index
	indexFileName = file_name_base + "_index.bin";
	// Specify File Name For Data
	dataFileName = file_name_base + "_data.bin";
	// Speeify File Name For Statistics
	statFileName = file_name_base + "_stat.bin";

	// Load Stats
	FILE *myFile;
	myFile = fopen(statFileName.c_str(), "r+b");
	if (myFile == NULL) {
		myFile = fopen(statFileName.c_str(), "w+b");
		stats.recordsAdded = 0;
		stats.recordsUpdated = 0;
		stats.recordsRetrieved = 0;
		stats.recordsDeleted = 0;
	} else {
		fread(&stats, sizeof(struct ISAM_stat), 1, myFile);
	}
	fclose(myFile);
}

// Default Destructor
ISAM::~ISAM() {
	// Write Stats Back Out
	FILE *myFile;
	myFile = fopen(statFileName.c_str(), "r+b");
	fwrite(&stats, sizeof(struct ISAM_stat), 1, myFile);
	fclose(myFile);
}

// Return File Size
double ISAM::fileSize(FILE *myFile) {
	double start_location = ftell(myFile);
	if (myFile == NULL)
		return -1;
	fseek(myFile, 0, SEEK_END);
	double file_size = ftell(myFile);
	fseek(myFile, start_location, SEEK_SET);
	return file_size;
}

// Return The Number Of Records In The Data File
int ISAM::numDataRecords() {
	FILE *myFile; // Create a file instance
	myFile = fopen(dataFileName.c_str(), "r+b");
	if (myFile == NULL) {
		return -1;
	}

	double file_size = fileSize(myFile);
	if (file_size == -1)
		return -1;

	return file_size / size;
}

// Return The Number Of Records In The Index File
int ISAM::numIndexRecords() {
	FILE *myFile;
	myFile = fopen(indexFileName.c_str(), "r+b");
	if (myFile == NULL)
		return -1;

	double file_size = fileSize(myFile);
	if (file_size == -1)
		return -1;

	return file_size / sizeof(struct index_info);
}

// Returns The Ammount of Wasted Space In The Data File
int ISAM::dataFileWaste() {
	return (numDataRecords() - numIndexRecords()) * size;
}

// Find Record Function
int ISAM::findRecord(int primary_key) {
	double file_size = -1;
	double num_records = -1;
	index_info *indexArray;
	FILE *myFile; // Create a file instance
	myFile = fopen(indexFileName.c_str(), "r+b"); // Convert string to a c string and open file
	if (myFile == NULL) {
		return -1;
	}

	// Get File Size
	file_size = fileSize(myFile);
	if (file_size == -1)
		return -1;
	num_records = file_size / sizeof(struct index_info);
	indexArray = new index_info[num_records];

	// Load Array
	fread(indexArray, sizeof(struct index_info), num_records, myFile);

	// Search Array
	for (int counter = 0; counter < num_records; counter++) {
		if (indexArray[counter].primary_key == primary_key)
			return counter+1;
	}

	fclose(myFile);
	return -1;
}

// Add Index Record Function
int ISAM::addIndexInfo(index_info info) {
	// Load Index File Into Memory
	double file_size = -1;
	int num_records = -1;
	index_info *indexArray;
	FILE *myFile;
	myFile = fopen(indexFileName.c_str(), "r+b");
	if (myFile == NULL)
		return -1;

	file_size = fileSize(myFile);
	if (file_size == -1)
		return -1;
	num_records = file_size / sizeof(struct index_info) + 1;
	indexArray = new index_info[num_records];

	fread(indexArray, sizeof(struct index_info), num_records, myFile);

	// Add New Item To Array
	indexArray[num_records-1].primary_key = info.primary_key;
	indexArray[num_records-1].record_number = info.record_number;
	
	// Sort Array - Using Bubble Sort For Simplicity
	index_info temp;
	for (int counter1 = num_records; counter1 >0; counter1--) {
		for (int counter2 = 0; counter2 < counter1-1; counter2++) {
			if (indexArray[counter2].primary_key > indexArray[counter2+1].primary_key) {
				temp = indexArray[counter2];
				indexArray[counter2] = indexArray[(counter2+1)];
				indexArray[(counter2+1)] = temp;
			}
		}
	}

	// Write Array Back To Binary File
	myFile = fopen(indexFileName.c_str(), "r+b");
	if (myFile == NULL) {
		myFile = fopen(indexFileName.c_str(), "w+b");
	}
	fwrite(indexArray, sizeof(struct index_info), num_records, myFile); // Write struct or class to file
	fclose(myFile);

	return 1;
}

// Return Data Record Function
int ISAM::getRecord(void *data, int primary_key) {
	int record = findRecord(primary_key);
	if (record == -1)
		return -1;
	FILE *myFile; // Create a file instance;
	myFile = fopen(dataFileName.c_str(), "r+b"); // Convert string to c string and open file
	fseek (myFile, size * (record-1), SEEK_SET );
	fread(data, size, 1, myFile); // Read data into struct or class from file
	fclose(myFile);
	// Update Stats
	stats.recordsRetrieved++;
	return 1;
}

// Set Data Record Function
int ISAM::setRecord(void *data, int primary_key) {
	FILE *myFile; // Create a file instance
	myFile = fopen(dataFileName.c_str(), "r+b"); // Convert string to c string and open file
	// Check To See If File Opened
	if (myFile == NULL) { // If Not Create New File
		myFile = fopen(indexFileName.c_str(), "w+b");
		fclose(myFile);
		myFile = fopen(dataFileName.c_str(), "w+b");
	}
	int record = findRecord(primary_key);
	if (record == -1) {
		// Update Index
		struct index_info info;
		info.primary_key = primary_key;
		info.record_number = fileSize(myFile) / size + 1;
		if (addIndexInfo(info) == -1)
			return -1;
		fseek(myFile, 0, SEEK_END);
		// Update Stats
		stats.recordsAdded++;

	} else {
		fseek (myFile, size * (record-1), SEEK_SET );
		// Update Stats
		stats.recordsUpdated++;
	}
	fwrite(data, size, 1, myFile); // Write struct or class to file

	fclose(myFile); // Close File
	return 1;

}

int ISAM::getNumRecordsAdded() {
	return stats.recordsAdded;
}
int ISAM::getNumRecordsUpdated() {
	return stats.recordsUpdated;
}

int ISAM::getNumRecordsDeleted() {
	return stats.recordsDeleted;
}
int ISAM::getNumRecordsRetrieved() {
	return stats.recordsRetrieved;
}

int ISAM::deleteRecord(int primary_key) {
	int record = findRecord(primary_key);
	if (record == -1)
		return -1;

	// Load Index File Into Memory
	double file_size = -1;
	int num_records = -1;
	index_info *indexArray;
	index_info *finalArray;
	FILE *myFile;
	myFile = fopen(indexFileName.c_str(), "r+b");
	if (myFile == NULL)
		return -1;

	file_size = fileSize(myFile);
	if (file_size == -1)
		return -1;
	num_records = file_size / sizeof(struct index_info);
	indexArray = new index_info[num_records];
	finalArray = new index_info[num_records-1];

	fread(indexArray, sizeof(struct index_info), num_records, myFile);

	// Change Primary Key To Filter To The Front
	for (int counter = 0; counter < num_records; counter++) {
		if (indexArray[counter].primary_key == primary_key)
			indexArray[counter].primary_key = -99999;
	}
		
	// Sort Array - Using Bubble Sort For Simplicity
	index_info temp;
	for (int counter1 = num_records; counter1 >0; counter1--) {
		for (int counter2 = 0; counter2 < counter1-1; counter2++) {
			if (indexArray[counter2].primary_key > indexArray[counter2+1].primary_key) {
				temp = indexArray[counter2];
				indexArray[counter2] = indexArray[(counter2+1)];
				indexArray[(counter2+1)] = temp;
			}
		}
	}

	// Put Index File Into New Array
	for (int counter = 1; counter < num_records; counter++)
		finalArray[counter-1] = indexArray[counter];

	// Write Array Back To Binary File
	myFile = fopen(indexFileName.c_str(), "w+b");
	if (myFile == NULL) {
		return -1;
	}
	fwrite(finalArray, sizeof(struct index_info), (num_records-1), myFile); // Write struct or class to file
	fclose(myFile);
	stats.recordsDeleted++;

	return 1;
}

void ISAM::printIndexFile() {
	// Load Index File Into Memory
	double file_size = -1;
	int num_records = -1;
	index_info *indexArray;
	FILE *myFile;
	myFile = fopen(indexFileName.c_str(), "r+b");
	if (myFile == NULL)
		return;

	file_size = fileSize(myFile);
	if (file_size == -1)
		return;
	num_records = file_size / sizeof(struct index_info);
	indexArray = new index_info[num_records];

	fread(indexArray, sizeof(struct index_info), num_records, myFile);


	cout << "Printing Index File" << endl;
	for (int counter = 0; counter < num_records; counter++) {
		cout << "\tRow " << counter << endl;
		cout << "\t\tPrimary Key: " << indexArray[counter].primary_key << endl;
		cout << "\t\tRecord Number: " << indexArray[counter].record_number << endl;
	}

	cout << "Finished Printing Index File" << endl;

}
