#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <strings.h> // for strncasecmp
#include <iostream>



/**
 * this class is a wrapper for dbf , sequential reading. 
 * it checks for constraints when reading fields,
 *
 *
 */

struct t_dbf {
	unsigned char	version_no;
	unsigned char	year_last_update;
	unsigned char	month_last_update;
	unsigned char	day_last_update;
	unsigned int	n_records;
	unsigned short int header_length;
	unsigned short int record_length;
	unsigned short reserved_1;
	unsigned char incomplete_trans;
	unsigned char encrypt_flag;
	unsigned int 	lan_reserved;
	unsigned int multi_user_dbase;
	unsigned int multi_user_dbase2;
	unsigned char mdx_flag_db4;
	unsigned char language_driver;
	unsigned short int reserved_2;
};

struct t_field_header {
 public:
	char field_name[11];
	unsigned char field_type;
	unsigned int field_data_address;
	unsigned char field_length;
	unsigned char decimal_count;
	unsigned short int reserved_multiuser;
	unsigned char work_area_id;
	unsigned short int reserved_multiuser2;
	unsigned char set_fields_flag;
	unsigned char reserved_3[8];
	unsigned char index_field_flag;
};


int field_count = 0;

class Dbf_basic {
	public:
		Dbf_basic(const char* _filename);

			

		static const int MAX_FIELDS = 128;
		
		void first() ; // set at first record
		int next_record();  // go to next record, returns record number or -1 if at end
		int  first_field();
		void dump_record(FILE*, char* tempbuf, int buflen );
		
		int next_field();
		// increment to the next field, returns -1 if at end of record

		int fieldname( char* buf, int buflen);
		char  fieldtype();
		void string( char* buf, int buflen);
		bool boolean();
		float numeric();
		int integer();
		
	protected:
		void read_field_headers();
		void calc_record_size();

		
	private:
		const char* filename;
		char buffer[512];
		struct t_field_header fields[MAX_FIELDS];
		int rec_count;
		int filedesc;
		struct t_dbf dbfh;
		int current_rec;
		int record_size;
		int current_field;
		int field_count;
		bool current_field_already_read;
		off_t record_filepos;
};


Dbf_basic::Dbf_basic(const char* _filename) : 
			filename(_filename), 
			filedesc( open( _filename, O_RDONLY) ),
			current_rec(0)

		{
			read(filedesc, &dbfh, 32);
			fprintf(stdout, "version is %1d , last update 20%d/%d/%d , n records = %d, header len = %d, record len = %d \n" ,
				dbfh.version_no, 
				dbfh.year_last_update,
				dbfh.month_last_update, 
				dbfh.day_last_update, 
				dbfh.n_records, 
				dbfh.header_length, 
				dbfh.record_length
				);

			rec_count = dbfh.n_records;

			read_field_headers();
			first();
}

void Dbf_basic::read_field_headers()	{
	field_count = 0;
	lseek( filedesc, 32, SEEK_SET); 
	while(1) {
		read(filedesc, &fields[field_count], 32);
		if (fields[field_count].field_name[0] == 0x0d) 
			break;
		fprintf(stdout,"\n FieldName=%s, fieldtype=%c, fieldlen=%d, indexed = %d\n", fields[field_count].field_name, fields[field_count].field_type, fields[field_count].field_length, fields[field_count].index_field_flag );
		field_count++;
		if (field_count >= MAX_FIELDS) break;
	}

}

void Dbf_basic::calc_record_size() {
	if (field_count == 0) {
		read_field_headers();
	}
	record_size = 0;
	for( int i = 0; i < field_count; ++i) {
		record_size += fields[i].field_length; 
	}
}


void Dbf_basic::first() {
	record_filepos = lseek( filedesc, dbfh.header_length + 1, SEEK_SET);
	current_field = 0;
	current_rec = 0;
}

int Dbf_basic::next_record() {
	
	int retval;

	if ( current_field == field_count) {
		char c;
		read (filedesc, &c, 1);
	} else {
		// havent read all of current record , so need to jump

		for (int i = current_field; i < field_count;++i ) {
			read( filedesc, buffer, fields[i].field_length);
		}
		char c;
		read( filedesc, &c, 1);

	}
	record_filepos = lseek( filedesc, 0, SEEK_CUR);
	++current_rec;
	current_field = 0;
	current_field_already_read = false;

	
	if ( current_rec < rec_count) {
		retval = current_rec;

	} else {
		retval = -1;
	
	}

	return retval;


}

int  Dbf_basic::fieldname( char* buf, int buflen) {
	int len;
	if ( current_field < field_count ) {
		strncpy( buf, fields[current_field].field_name , buflen);
	}
	return strlen(buf);
}

char  Dbf_basic::fieldtype( ) {
	
	char retval;
	
	if ( current_field < field_count) {
		retval =  fields[current_field].field_type;	
	
	} else {
		retval = 0;
	
	}

	return retval;
}

void  Dbf_basic::string( char* buf, int buflen) {
	char c;
	if (	current_field < field_count && 
		( ( c = fields[current_field].field_type )== 'C' ||
			c == 'D' )  && 
		buflen > fields[current_field].field_length
	) {
		read( filedesc, buf, fields[current_field].field_length);
		buf[fields[current_field].field_length+1] = '\0';	
		current_field_already_read = true;


	}
}

bool  Dbf_basic::boolean() {
	bool val = false;
	if ( 	current_field < field_count && 
		fields[current_field].field_type == 'L' ) {
		char c;
		read( filedesc, &c, 1);
		current_field_already_read = true;


		if ( c == 'T' || c == 't' || c == 'Y' || c == 'y' ) {
			val = true;
		}
	}
	return val;
}

float  Dbf_basic::numeric() {
	float val = 0.0;
	char c;
	if ( 	current_field < field_count && 
		((c = fields[current_field].field_type ) == 'N' || c == 'F')  )
	{
			
			read( filedesc, buffer, 
				fields[current_field].field_length);
		
			current_field_already_read = true;

			val = strtof( 	buffer, NULL);
		
	}
	return val;
}

int  Dbf_basic::integer() {
	int val = 0;
	char c;
	if ( 	current_field < field_count && 
		(c = fields[current_field].field_type) == 'I' )  
	{
		// I is little endian , so this should work on x86
		read( filedesc, &val, 4);
		current_field_already_read = true;

	// make memo a integer for the moment
	} else if ( current_field < field_count &&  ( c == 'N' || c == 'M' ) ) {
		read( filedesc, buffer, fields[current_field].field_length);
		current_field_already_read = true;
		val = atoi( buffer);
	}
	return val;
}

int Dbf_basic::next_field() {
	int retval;
	if ( !current_field_already_read) {
		read( filedesc, buffer, fields[current_field].field_length);
		
	}
	current_field++;

	if ( current_field >= field_count) {
		retval =  -1;
	
	} else {
		retval =  current_field;
	
	}

	return retval;
}

int Dbf_basic::first_field() {
	lseek( filedesc, record_filepos, SEEK_SET);

}

void Dbf_basic::dump_record(FILE* f, char* buffer, int buflen) {
	int buf2_offset;
	first_field();
	do {
			buf2_offset = this->fieldname( buffer, buflen) + 1;
			char t = this->fieldtype();
			
			switch (t) {
				case 'C': case 'D':
					this->string( buffer+buf2_offset, buflen-buf2_offset);
					fprintf(f, "%s: %s", buffer, buffer+buf2_offset);
					break;
				case 'N' : case 'F':
					fprintf(f, "%s: %f", buffer, this->numeric());
					break;

				case 'L' :
					fprintf(f, "%s: %s", buffer, this->boolean() ? "yes":"no");
					break;
				case 'I' : case 'M':
					fprintf(f, "%s: %d", buffer, this->integer());
				default:
					break;
			};
			fprintf(f, "\n");
		} while ( this->next_field() != -1);
		fprintf(f, "\n\n");

}

int main(int argc , char ** argv) {
	
	char buffer[600];
		
	if (argc == 1) {

		fprintf(stdout, "usage: filename.dbf\n");
		exit(-1);
	}
	
	Dbf_basic dbf(argv[1]);

	do {
		dbf.dump_record(stdout, buffer, 600);
		
	} while ( dbf.next_record() != -1);

	

	return 0;
}
