/*
** Falling Tower KAI
**	Copyright (c) 2000	ROYALPANDA	All rights reserved
**
**	May 25, 2000 Ver 1.0
*/

#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<pwd.h>
#include<ctype.h>
#include<errno.h>

#include"record.h"

#define FS	'\t'	/* field separator */


/****************************************
**
** Field parsers
**
*****************************************/

static int read_num( char **p )
{
	char *q;
	int i;

	i = 0;
	q = *p;

	if( !isdigit(*q) )
		return -1;

	while( *q && *q != FS && *q != '\n' ){
		if( !isdigit(*q) )
			return -1;
		i = i * 10 + *q - '0';
		q++;
	}
	if( *q == FS )
		*p = q + 1;
	else
		*p = q;
	return i;
}


static int read_str( char **p, char *buf, int bufsiz )
{
	char *q;
	int len;

	if( --bufsiz < 1 )
		return -1;
	len = 0;
	q = *p;
	while( *q && *q != FS && *q != '\n' ){
		if( iscntrl(*q) )
			*buf++ = ' ';
		else
			*buf++ = *q;
		q++;
		if( ++len == bufsiz )
			break;
	}
	*buf = '\0';
	if( *q == FS )
		*p = q + 1;
	else
		*p = q;
	return len;
}

/*************************************
*
*  record database transactions
*
*************************************/

/* unlock */

static int unlock( record_file_t *rp )
{
	if( rp->lock_fd < 0 )
		return 0;

	unlink(rp->lock_file);
	close(rp->lock_fd);
	rp->lock_fd = -1;
	return 0;
}


/*
** lock for reading
*/

int lock_read( record_file_t *rp )
{
	int i;

	if( rp->lock_fd < 0 ){
		i = open(rp->lock_file,O_CREAT|O_WRONLY|O_SHLOCK,0777);
		if( i < 0 ){
			message("Cannot open lock file.");
			return -1;
		}
		rp->lock_fd = i;
	}
	return 0;
}


/*
** lock for writing
*/

int lock_write( record_file_t *rp )
{
	int i;

	if( rp->lock_fd < 0 ){
		i = open(rp->lock_file,O_CREAT|O_WRONLY|O_EXLOCK,0600);
		if( i < 0 ){
			message("Cannot open lock file.");
			return -1;
		}
		rp->lock_fd = i;
	}else{
		flock(rp->lock_fd,LOCK_EX);
	}
	return 0;
}


/*
** commit
*/

static int commit( record_file_t *rp )
{
	return rename(rp->swap_file, rp->record_file);
}

/*
** restore
*/

static int restore( record_file_t *rp )
{
	int fd;

	if(lock_write(rp) < 0 )
		return -1;

	if( (fd = open(rp->record_file,O_RDONLY)) != -1 ){
		close(fd);
		return 1;
	}
	if( errno != ENOENT ){
		message("Cannnot open record file.");
		return -1;
	}

	if( rename(rp->swap_file, rp->record_file) < 0 ){
		if( errno != ENOENT ){
			message("Cannot write record file.");
			return -1;
		}else{
			return 0;
		}
	}
	return 1;
}


/*
** read records from file
**   try to restore from swap when record file is not found.
*/

static int read_record( record_file_t *rp )
{
	FILE *fp;
	char buf[80];
	char *str;
	int i,l;
	int score;
	record_t *p;
	char *q;

	if( (fp = fopen(rp->record_file,"r")) == NULL ){
		if( errno == ENOENT ){
			i = restore(rp);
			if( i < 0 )
				return -1;
			if( i ){
				if( (fp = fopen(rp->record_file,"r")) == NULL ){
					message("Cannot open record file.");
					return -1;
				}
			}else{
				rp->num_record = 0;
				return 0;
			}
		}
	}else{
		/*
		** If swap file exists and its i-node is same as record file,
		** writing to swap file damages record file.
		** So, remove swap file.
		*/
		unlink(rp->swap_file);
	}

	i = 0;
	score = 99999;	/* most high value */
	p = rp->record;
	while( i < rp->max_record && fgets(buf,80,fp) != NULL ){
		str = buf;
		if( (p->score = read_num(&str)) < 0 )
			continue;

		if( p->score > score )
			continue;

		if( (p->uid = read_num(&str)) < 0 )
			continue;

		if( (p->date = read_num(&str)) < 0 )
			continue;

		if( read_str(&str,p->name,32) < 0 )
			continue;

		score = p->score;
		p++;
		i++;
	}

	if( ferror(fp) ){
		message("File reading error.");
		i = -1;
	}

	fclose(fp);
	rp->num_record = i;
	return i;
}


static int write_record( record_file_t *rp )
{
	FILE *fp;
	int i,new_data;
	record_t *p,*np;
	char buf[80];

	np = rp->new_record;

	new_data = 1;
	if( rp->override ){
		for( p = rp->record, i = 0 ; i < rp->num_record ; i++, p++ ){
			if( p->uid == np->uid ){
				if( p->score >= np->score ){
					return 0;
				}else{
					new_data = 0;
					break;
				}
			}
		}
	}

	if( new_data ){
		if( rp->num_record < rp->max_record )
			rp->num_record++;
		else if( rp->record[rp->num_record-1].score >= np->score )
			return 0;
		p = &rp->record[rp->num_record-1];
	}

	p--;
	while( p >= rp->record && p->score < np->score ){
		p[1] = p[0];
		p--;
	}
	p++;
	*p = *np;

	if( (fp = fopen(rp->swap_file,"w")) == NULL ){
		message("Cannot open swap file.");
		return -1;
	}

	for( p = rp->record, i = 0 ; i < rp->num_record ; p++, i++ ){
		sprintf(buf,"%d%c%d%c%d%c%-0.31s\n",
			p->score,FS,
			p->uid,FS,
			p->date,FS,
			p->name);

		if( fputs(buf,fp) == EOF ){
			fclose(fp);
			unlink(rp->swap_file);
			message("File writing error.");
			return -1;
		}
	}
	if( fclose(fp) == EOF ){
		unlink(rp->swap_file);
		message("Cannot close swap file.");
		return -1;
	}
	if( commit(rp) < 0 )
		return -1;

	return 1;
}

/*******
*
* APIs
*
*******/



int RECORD_read( record_file_t *rp )
{
	int i;

	if( lock_read(rp) < 0 )
		return -1;
	i = read_record(rp);
	unlock(rp);
	return i;
}

int RECORD_write( record_file_t *rp )
{
	int i;

	if( lock_write(rp) < 0 )
		return -1;
	if( read_record(rp) < 0 ){
		unlock(rp);
		return -1;
	}
	i = write_record(rp);
	unlock(rp);

	return i;
}


int RECORD_init( record_file_t *rp )
{
	record_t *p;

	p = (record_t*)malloc(sizeof(record_t)*rp->max_record);
	if( p == NULL ){
		message("Memory allocation error.");
		return -1;
	}

	rp->record = p;
	rp->lock_fd = -1;
	rp->num_record = 0;
	umask(022);

	return RECORD_read(rp);
}
