/*
 * Copyright (c) 2000-2001 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <xfs/xfs.h>
#include <xfs/jdm.h>

#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <assert.h>
#include <string.h>

#include "config.h"

#include "types.h"
#include "mlog.h"
#include "inv_priv.h"



/*----------------------------------------------------------------------*/
/* insert_invindexentry                                                 */
/*                                                                      */
/* given a time, find the invindex that has the time-period it can fit  */
/* into.                                                                */
/*----------------------------------------------------------------------*/
uint
idx_insert_newentry( int fd, /* kept locked EX by caller */
		     int *stobjfd, /* OUT */
		     invt_entry_t *iarr, invt_counter_t *icnt,
		     time32_t tm )
{
	uint i;
	inv_oflag_t forwhat = INV_SEARCH_N_MOD;
/*	invt_entry_t ient;
	ient.ie_timeperiod.tp_start = ient.ie_timeperiod.tp_end = tm; */
	
	/* If time period of the new entry is before our first invindex,
	   we have to insert a new invindex in the first slot */
	if ( iarr[0].ie_timeperiod.tp_start > tm ) {
		/* *stobjfd = idx_put_newentry( fd, 0, iarr, icnt, &ient );*/
		*stobjfd = open( iarr[0].ie_filename, INV_OFLAG(forwhat) );
		return 0;
	}

	for ( i = 0; i < icnt->ic_curnum; i++ ) {
		/* if our time is nicely within an existing entry's time
		   period, hellalujah */
		if ( IS_WITHIN( &iarr[i].ie_timeperiod, tm ) ) {
#ifdef INVT_DEBUG
			mlog( MLOG_DEBUG | MLOG_INV, "INV: is_within %d\n",i );
#endif
			*stobjfd = open( iarr[i].ie_filename, INV_OFLAG(forwhat) );
			return i;
		}
		if ( iarr[i].ie_timeperiod.tp_end == 0  && 
		     iarr[i].ie_timeperiod.tp_start  == 0 ) {
#ifdef INVT_DEBUG
			mlog( MLOG_DEBUG | MLOG_INV, "INV: end = start \n" );
			mlog( MLOG_DEBUG | MLOG_INV,"BEF: st %ld end %ld\n",
			     iarr[i].ie_timeperiod.tp_start,
			     iarr[i].ie_timeperiod.tp_end );
#endif
			
			iarr[i].ie_timeperiod.tp_start = 
				iarr[i].ie_timeperiod.tp_end = tm;
			PUT_REC_NOLOCK( fd, iarr, 
				       icnt->ic_curnum * sizeof(invt_entry_t),
				       (off64_t) sizeof( invt_counter_t ) );
#ifdef INVT_DEBUG
			mlog( MLOG_DEBUG | MLOG_INV,"AFT: st %ld end %ld\n",
			     iarr[i].ie_timeperiod.tp_start,
			     iarr[i].ie_timeperiod.tp_end );
#endif
			*stobjfd = open( iarr[i].ie_filename, INV_OFLAG(forwhat) );
			return i;
		}	



		/* if it is beyond the end of this timeperiod, see if we 
		   belong to a timeperiod that doesn't have an entry */
		if ( iarr[i].ie_timeperiod.tp_end < tm ) {
			/* see if we're the last entry here */
			if ( i == icnt->ic_curnum - 1 ) {
				/* our slot is (i+1)th entry. Make the 
				   timeperiod's the same as it was. As far
				   as I can see there is no way that 
				   timeperiods can overlap.
				
				   insert the new entry and write back 
				   icnt and invindex entry */
				/* *stobjfd = idx_put_newentry( fd, i+1, iarr, 
							     icnt, &ient );*/
			      *stobjfd = open( iarr[i].ie_filename, INV_OFLAG(forwhat) );
			      return i;
			}
			/* see if the next entry starts later than us */
			if ( iarr[i+1].ie_timeperiod.tp_start > tm ) {
				
				
				/* We have the option of pushing entries
				   after (i) forward by one slot, and 
				   taking the (i+1)th slot, OR just hooking
				   up with the next entry. 
				   We choose the former. */
				
				/* the timeperiods had better not overlap */
				assert(( tm > iarr[i].ie_timeperiod.tp_end ) &&
				       ( tm < iarr[i+1].ie_timeperiod.tp_start ));

				/* shift everything from (i+1) onwards by 
				   one. Then insert the new entry and write
				   back icnt and invindex entries */
				/* *stobjfd = idx_put_newentry( fd, i+1, iarr, 
							     icnt, &ient );*/
			      *stobjfd = open( iarr[i].ie_filename, INV_OFLAG(forwhat) );
				return i;
			}
		}	
	}		

	/* We couldnt find anything that fits */
	assert( 0 );	/* We can't get here ! */
	return -1;

	
}


/*----------------------------------------------------------------------*/
/* idx_put_newentry                                                     */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_put_newentry( 
	invt_idxinfo_t *idx, 
	invt_entry_t *ient )
{
	invt_entry_t	*idxarr;
	int stobjfd;
	
	int fd = idx->invfd; 	/* kept locked EX by caller */
	uint index = idx->index + 1;
	invt_entry_t *iarr = idx->iarr;
	invt_counter_t *icnt = idx->icnt;

	stobj_makefname( ient->ie_filename );
	if ( ( stobjfd = stobj_create( ient->ie_filename ) ) < 0 )
		return -1;

	icnt->ic_curnum++; /* there is no maximum */

	idxarr = ( invt_entry_t * ) calloc ( icnt->ic_curnum, 
					     sizeof( invt_entry_t ) );
	memcpy( idxarr, iarr, ( size_t ) ( sizeof( invt_entry_t ) * index ) );

	/* shift everything from (i+1) onwards by one */
	if ( index <  icnt->ic_curnum - 1 ) 
		memcpy( &idxarr[ index + 1 ], &iarr[ index ], 
		       ( size_t ) ( ( icnt->ic_curnum - index - 1 ) *
				    sizeof( invt_entry_t ) ) );
	/* insert the new entry */
	memcpy( &idxarr[ index ], ient, sizeof( invt_entry_t ) );

	
	if ( ( PUT_COUNTERS( fd, icnt ) < 0 ) ||
		( PUT_REC_NOLOCK( fd, idxarr, 
				  icnt->ic_curnum * sizeof( invt_entry_t ), 
				  sizeof( invt_counter_t ) ) < 0 ) ) {
			/* XXX delete the stobj that we just created */
			
			memset( ient->ie_filename, 0 , INV_STRLEN );
			free( idxarr );
			return -1;
		}

	free( iarr );
	idx->iarr = idxarr;
	return stobjfd;
	
}




/*----------------------------------------------------------------------*/
/* idx_find_stobj                                                       */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/


int
idx_find_stobj( invt_idxinfo_t *idx,
	        time32_t tm )
{

	int 		stobjfd;

	/* since sessions can be inserted in random order, the invindex
	   table can contain time-periods that don't have corresponding
	   entries for */
	if ( GET_ALLHDRS_N_CNTS_NOLOCK( idx->invfd, (void **)&idx->iarr, 
						     (void **)&idx->icnt, 
						     sizeof( invt_entry_t ),
				sizeof( invt_counter_t ) ) < 0 ) {
		return -1;
	}

#ifdef INVT_DEBUG
	printf( "idx_find_stobj Time: %ld\n", tm );
	idx_DEBUG_printinvindices( idx->iarr, idx->icnt->ic_curnum );
#endif

	/* Now figure out where we are going to insert this stobj among the
	   invindices and put it there */
	idx->index = idx_insert_newentry( idx->invfd, &stobjfd, idx->iarr, 
						 idx->icnt, tm );

	return stobjfd;
}






/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

inv_idbtoken_t
idx_create( char *fname, inv_oflag_t forwhat )
{
	int stobjfd, fd;
	inv_idbtoken_t tok;

	/* This is not to be called when the user wants to open
	   the db for SEARCH_ONLY. */
	assert( forwhat != INV_SEARCH_ONLY );

	if ((fd = open ( fname , INV_OFLAG(forwhat) | O_CREAT, S_IRUSR|S_IWUSR ) ) < 0 ) {
		INV_PERROR ( fname );
		return INV_TOKEN_NULL;
	}
	
	INVLOCK( fd, LOCK_EX );
	fchmod( fd, INV_PERMS );

#ifdef INVT_DEBUG
	mlog( MLOG_NITTY | MLOG_INV, "creating InvIndex %s\n", fname);
#endif
	
	/* create the first entry in the new inv_index */
	stobjfd = idx_create_entry( &tok, fd, BOOL_TRUE );
	
	INVLOCK( fd, LOCK_UN );

	if ( stobjfd < 0 )
		return INV_TOKEN_NULL;
	return tok;
}


/*----------------------------------------------------------------------*/
/* idx_recons_time                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/
int
idx_recons_time( time32_t tm, invt_idxinfo_t *idx )
{
	invt_timeperiod_t *tp = &idx->iarr[idx->index].ie_timeperiod;
	if ( tp->tp_start && IS_WITHIN( tp, tm ) )
		return 1;

	if ( tm > tp->tp_end || tp->tp_end == 0 ) 
		tp->tp_end =  tm;
	if ( tm < tp->tp_start || tp->tp_start == 0 )
		tp->tp_start = tm;
	PUT_REC_NOLOCK( idx->invfd,  &idx->iarr[idx->index], 
		        sizeof( invt_entry_t ), IDX_HDR_OFFSET(idx->index) );
	return 1;
}




/*----------------------------------------------------------------------*/
/* put_sesstime                                                         */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_put_sesstime( inv_sestoken_t tok, bool_t whichtime)
{
	int rval;
	invt_entry_t ent;
	int fd = tok->sd_invtok->d_invindex_fd;

	INVLOCK( fd, LOCK_EX );

	rval = GET_REC_NOLOCK( fd, &ent, sizeof( invt_entry_t ),
			        tok->sd_invtok->d_invindex_off);
	if ( rval < 0 ) {
		INVLOCK( fd, LOCK_UN );
		return -1;
	}
	ent.ie_timeperiod.tp_end = tok->sd_sesstime;
	
	if ( whichtime == INVT_STARTTIME || ent.ie_timeperiod.tp_start == 0 ) {
		ent.ie_timeperiod.tp_start = tok->sd_sesstime;
	}
#ifdef INVT_DEBUG
	mlog( MLOG_DEBUG | MLOG_INV,"Putsestime: st %ld end %ld\n",
			      ent.ie_timeperiod.tp_start,
			      ent.ie_timeperiod.tp_end );
#endif
	rval = PUT_REC_NOLOCK_SEEKCUR( fd, &ent, sizeof( invt_entry_t ),
				      -(off64_t)(sizeof( invt_entry_t )));
	
#ifdef INVT_DEBUG
	{
		int nindices;
		invt_entry_t *iarr = NULL;
		invt_counter_t *icnt = NULL;
		if ( ( nindices = GET_ALLHDRS_N_CNTS_NOLOCK( fd, 
						     (void **)&iarr, 
						     (void **)&icnt, 
						sizeof( invt_entry_t ),
				sizeof( invt_counter_t ))) < 0 ) {
			return -1;		 
		}
		idx_DEBUG_printinvindices( iarr, (uint) nindices );
		free( iarr );
		free( icnt );
	}
#endif

	INVLOCK( fd, LOCK_UN );
	return rval;
}



/* an inventory index entry keeps track of a single storage object;
   it knows about its name (ie filename) and the timeperiod that the
   it contains dump sessions for.
   note that each file system has its own (set of) inventory indices.
*/

/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_create_entry(  
	inv_idbtoken_t *tok, 
	int invfd, 	/* kept locked EX  by caller */
	bool_t firstentry )
{
	invt_entry_t   	ent;
	int	      	fd;
	off64_t 	hoff;


	memset ( &ent, 0, sizeof( ent ) );
	
	/* initialize the start and end times to be the same */
	ent.ie_timeperiod.tp_start = ent.ie_timeperiod.tp_end = (time32_t)0;
	stobj_makefname( ent.ie_filename );

	if ( firstentry ) {
		invt_counter_t cnt;

		cnt.ic_maxnum = INVT_MAX_INVINDICES;
		cnt.ic_curnum = 1;
		cnt.ic_vernum = INV_VERSION;

		fd = stobj_create( ent.ie_filename );
		if ( fd < 0 ) {
			return -1;
		}

		if ( PUT_REC_NOLOCK( invfd, &cnt, sizeof(cnt), (off64_t)0 ) < 0 )
			return -1;

		hoff = sizeof( invt_counter_t );

		if ( PUT_REC_NOLOCK( invfd, &ent, sizeof( ent ), hoff ) < 0)
			return -1;
	} else {
		invt_counter_t *cnt = NULL;

		if ( GET_COUNTERS( invfd, &cnt )  < 0 ) {
			return -1;
		}
		
		/* XXX check if there are too many indices. if so, create 
		   another and leave a pointer to that in here */
		
		/* create the new storage object */
		fd = stobj_create( ent.ie_filename );
		if ( fd < 0 ) {
			return -1;
		}
		++(cnt->ic_curnum);
		if ( PUT_COUNTERS( invfd, cnt ) < 0 ) {
			return -1;
		}

		/* add the new index entry to the array, at the end */

		hoff = IDX_HDR_OFFSET( cnt->ic_curnum - 1 );
		free (cnt);
#ifdef INVT_DEBUG
		mlog( MLOG_NITTY | MLOG_INV, "new stobj name %s @ offset %d\n",
		      ent.ie_filename,(int)hoff );
#endif
		if (PUT_REC_NOLOCK( invfd, &ent, sizeof( ent ), hoff) < 0 )
			return -1;
		
	}

	*tok = get_token( invfd, fd );
	(*tok)->d_invindex_off = hoff;
	(*tok)->d_update_flag |= NEW_INVINDEX;
	(*tok)->d_oflag = INV_SEARCH_N_MOD;
	return fd;

}






/*----------------------------------------------------------------------*/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/*----------------------------------------------------------------------*/

int
idx_get_stobj( int invfd, inv_oflag_t forwhat, int *index )
{
	invt_entry_t 	*ent = 0;
	int	     	 fd;

	/* if there's space anywhere at all, then it must be in the last
	   entry. get_lastheader() does the locking */
	
	if ((*index = get_lastheader( invfd, (void **)&ent, 
				       sizeof(invt_entry_t),
				       sizeof(invt_counter_t) ) ) < 0 )
		return -1;
	/* at this point we know that there should be at least one invindex
	   entry present */
	assert ( ent != NULL );	
	assert ( ent->ie_filename );

	fd = open( ent->ie_filename, INV_OFLAG(forwhat) );
	if ( fd < 0 )
		INV_PERROR( ent->ie_filename );
	free ( ent );

	return fd;
}


int
idx_DEBUG_printinvindices( invt_entry_t *iarr, uint num )
{
	uint i;
	uint k;

	char s[9];
	printf( "\n ==================================\n"
	        " InvIndex\n # StObjs\t%d\n", num );
#define INV_UUID_STR_LEN	36 /* not exported via uuid.h */
	for ( i = 0; i < num; i++ ) {
		k = strlen( iarr[i].ie_filename );
		strncpy( s, (char *) iarr[i].ie_filename + k -
			( INV_UUID_STR_LEN + strlen(INV_STOBJ_PREFIX)), 8 );
		s[8]= 0;
		printf("%d. %s \t( %d - %d )\n", i, s, 
		       iarr[i].ie_timeperiod.tp_start,
		       iarr[i].ie_timeperiod.tp_end );
	}
#undef INV_UUID_STR_LEN
	printf( "\n ==================================\n");
	return 1;
	
}

int
idx_DEBUG_print ( int fd )
{
	int nindices;
	invt_entry_t *iarr = NULL;
	invt_counter_t *icnt = NULL;
	if ( ( nindices = GET_ALLHDRS_N_CNTS_NOLOCK( fd, 
						    (void **)&iarr, 
						    (void **)&icnt, 
						    sizeof( invt_entry_t ),
				         sizeof( invt_counter_t ))) < 0 ) {
		return -1;		 
	}
	idx_DEBUG_printinvindices( iarr, (uint) nindices );
	free( iarr );
	free( icnt );

	return 1;
}



int
DEBUG_displayallsessions( int fd, invt_seshdr_t *hdr, uint ref,
			  invt_pr_ctx_t *prctx)
{
	inv_session_t *ses;
	if ( stobj_make_invsess( fd, &ses, hdr ) < 1 )
		return -1;

	DEBUG_sessionprint( ses, ref, prctx);
	free( ses->s_streams );
	free( ses );
	
	return 0;
}

