/*
Autor: davex
e-mail: efengeler@gmail.com

Implementacion de funciones sobre una lista circular para lograr el efecto de "rebobinado"
*/

#ifdef SAVE_STATE


#include "rewind.h"
#include "psp/psp.h"

int num_rwnd_states = 0;
int rwnd_state_size_nzc = 0;
int rwnd_zc_state_size = 100*1024; // 100KB Tam. max. estado comprimido
int total_rewind_memory = 0;
unsigned char *save_state_nzc = NULL;

struct rewind_state{
	int zc_ssize;
	int have_data;
	unsigned char *data;
	struct rewind_state *next;
	struct rewind_state *prev;
};


struct rewind_state *ptr_rewind_states, *prev_state, *next_state;



int zero_compress(unsigned char *src, int size, unsigned char *dest){

	int is, id, zcnt, zwritten;
	zwritten = 0;
	zcnt = 0;
	id = 0;
	for(is = 0; is < size; is++){
		
		if( src[is] ){ //diferente de 0
			if( zcnt ){
				dest[id++] = (zcnt & 0xff);
				zwritten = 0;
				zcnt = 0;
			}else if(zwritten){
				dest[id++] = 0;
				zwritten = 0;
			}
			
			dest[id++] = src[is];
	
		}else{ //igual a cero

			if( zwritten ){
				zcnt++;
				if( zcnt >= 255){
					dest[id++] = zcnt;
					zwritten = 0;
					zcnt = 0;
				}
			}else{
				dest[id++] = 0;
				zwritten = 1;
			}
		}
	}
	if( zcnt )
		dest[id++] = (zcnt & 0xff);

	return id;
}


int zero_decompress(unsigned char *src_c, int size_src_c, unsigned char *dest_u){
	
	int isc, idu;
	int prev_zero;
	int zcnt;
	unsigned char uchar;
	int i;

	idu = 0;
	prev_zero = 0;
	for( isc = 0; isc < size_src_c; isc++){
		uchar = src_c[isc];
		if( uchar ){
			if( prev_zero ){ //se escriben
				zcnt = (unsigned int)uchar;
				for(i=0; i< zcnt; i++)
					dest_u[idu++] = 0;

				if( zcnt == 0)
					idu++;

				prev_zero = 0;
			}else
				dest_u[idu++] = uchar;
			
		}else{
			if( prev_zero == 0){
				dest_u[idu++] = 0;
				prev_zero = 1;
			}else
				prev_zero = 0;
			
			
		}//if( src_c[isc] ){
	}//for
	return idu;
}






int get_psp_max_free_memory(void){
	unsigned char *mem;
	const int MEM_CHUNK_SIZE = 500*1024; //bloques de KB
	const int MAX_CHUNKS = 64;
	unsigned char* mem_reserv[MAX_CHUNKS];
	int total_mem = 0;
	int i = 0;
	
	//se inicializa
	for( i = 0; i< MAX_CHUNKS; i++){
		mem_reserv[i] = NULL;
	}
	
	//se reserva
	for( i=0; i<MAX_CHUNKS; i++){
		mem = (unsigned char *)malloc(MEM_CHUNK_SIZE);
		if( mem != NULL){
			total_mem += MEM_CHUNK_SIZE;
			mem_reserv[i] = mem;
		}else{
			break;
		}
	}
	
	//se libera
	
	for( i=0; i<MAX_CHUNKS; i++){
		if( mem_reserv[i] != NULL)
			free( mem_reserv[i] );
	}
	
	return total_mem;
}

void allocate_rewind_states(void){
	struct rewind_state *created_state, *first_state;
	int i;
	
	total_rewind_memory = (int)( (float)(get_psp_max_free_memory()) * 0.90 );
	rwnd_state_size_nzc = get_save_state_size(); //declarado en "common/state.h"	
	total_rewind_memory = total_rewind_memory - rwnd_state_size_nzc;
	
	num_rwnd_states = (int) ( (float)total_rewind_memory / (float) rwnd_zc_state_size );
	
	//se reserva el buffer de descompresion
	save_state_nzc = (unsigned char *) malloc(rwnd_state_size_nzc);
	
	//se reserva el primer estado en memoria
	created_state =  (struct rewind_state *)malloc( sizeof(struct rewind_state) );
	created_state->zc_ssize = 0;
	created_state->have_data = 0;
	created_state->data = (unsigned char *) malloc( rwnd_zc_state_size);
	first_state = created_state;
	prev_state = first_state;
	
	//se reservan los demas estados
	for( i = 1; i< num_rwnd_states; i++){
		created_state  = (struct rewind_state *)malloc( sizeof(struct rewind_state) );
		created_state->zc_ssize = 0;
		created_state->have_data = 0;
		created_state->data = (unsigned char *) malloc( rwnd_zc_state_size);
		created_state ->prev = prev_state;
		prev_state->next = created_state;
		prev_state = created_state;
	}
	
	//para formar la lista circular se ajustan los apuntadores del primer y ultimo estado
	created_state->next = first_state; //con esto da la vuelta del final al principio
	first_state->prev = created_state; //con esto da la vuelta del principio al final
	ptr_rewind_states = first_state;
	
}


void free_rewind_states(void){
	
	struct rewind_state *now_state; 
	
	now_state = ptr_rewind_states;
	prev_state = now_state->prev;
	prev_state->next = NULL;
	
	//se libera la memoria de todos los estados hasta llegar
	//al primero que apunta NULL
	while(1){
		if ( now_state == NULL)
			break;
		next_state = now_state->next;
		free(now_state->data );
		free(now_state);
		now_state = next_state;
	}
}


void save_rewind_state(void){
	//se guarda el estado completo sin comprimir
	save_state_to_mem(save_state_nzc);
	//se comprime ahora en su posicion normal
	ptr_rewind_states->zc_ssize = zero_compress(save_state_nzc, 
						    rwnd_state_size_nzc, 
						    ptr_rewind_states->data);
	
	//save_state_to_mem(ptr_rewind_states->data);//"common/state.h"
	ptr_rewind_states->have_data = 1;
	ptr_rewind_states = ptr_rewind_states->next;
}

int read_rewind_state(void){
	
	int ret_val = -999;
	prev_state = ptr_rewind_states->prev;

	if (prev_state->have_data > 0 ){
		//se descomprime el estado comprimido
		zero_decompress( prev_state->data,prev_state->zc_ssize,save_state_nzc);
		
		//ahora si se restaura
		load_state_from_mem(save_state_nzc); //"common/state.h"
		prev_state->have_data = 0;
		ptr_rewind_states = ptr_rewind_states->prev;
		ret_val = 1;
	}
	return ret_val;
}

//funcion especial para determinar a partir del archivo "SAVENF.TXT"
//cada cuantos frames hay que guardar un estado ( minimo 1, maximo 9999)
int get_save_period(void)
{
	const int DEFAULT_PERIOD = 15;
	char path[MAX_PATH];
	char text_line[5] = {0};
	int save_period = 0; 
	int numBytes = 0;
	FILE *fp;
	
	strcpy(path, launchDir);
	strcat(path, "/SAVENF.TXT");
	
	
	fp = fopen(path, "r");
	if(fp == NULL){//no existe el archivo, se crea uno por default
		fp = fopen(path, "w");
		if(fp != NULL){
			fwrite( "15", 2, 1, fp);
			fclose(fp);
		}	
		
		return DEFAULT_PERIOD; 
	}
	
	numBytes = fread(text_line, 4, 1, fp);
	fclose(fp);
	
	if( numBytes <= 0)
		return DEFAULT_PERIOD; 
	
	save_period = (unsigned long) atoi(text_line);
	
	if( save_period <= 0 || save_period > 9999 )
		return DEFAULT_PERIOD;
	else		
		return save_period;

}


#endif

