#include "sffisico.h"
#include "errores.h"

#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

/* Dependencias del sistema */

#ifdef __MSDOS__

#include <io.h>
#include <sys/stat.h>
#define PERMISOS_DISCO (S_IREAD|S_IWRITE)
#define MODO (O_RDWR|O_BINARY)

#else

#include <unistd.h>
#define PERMISOS_DISCO  0644
#define MODO O_RDWR

#endif


enum Errores Error_SF;

/* Variables internas del módulo */

static int Disco = -1;
static WORD TamBloqueFisico = 0;
static DWORD NumBloquesFisicos = 0;


void historial_inicia(int ventana);
void historial_termina();
void historial_accede (DWORD nbloque);


/* Instalación de un disco */


int FabricaDisco (const char* nombre_fichero,WORD tam_bloque,DWORD num_bloques)
{
	struct Superbloque bl;
	DWORD tam_total = tam_bloque*num_bloques;
	int err;
	int fd;

	if (    tam_total>MAX_TAMDISCO || tam_bloque>MAX_TAMBLOQUE
	     || tam_total<num_bloques )
	{
	  Error_SF = ERR_RANGO;
	  return -1;
	}

#ifdef __MSDOS__
	_fmode=O_BINARY;
#endif

	/* Crea el fichero */
	fd = creat (nombre_fichero,PERMISOS_DISCO);

	if ( fd==-1 )
	{
	  switch (errno)
	  {
	    case EACCES:
	      Error_SF = ERR_PERMISOS;
	      return -1;
	    default:
	      Error_SF = ERR_DISCO;
	      return -1;
	  }
	}

#ifdef __MSDOS__
	err=chsize (fd,tam_total);
#else
	err=ftruncate (fd,tam_total);
#endif

	if (err==-1)
	{
	  Error_SF=ERR_DISCO;
	  return -1;
	}

	/* Escribe los datos del superbloque */
	strcpy(bl.magico,MAGICO);
	bl.tam_bloque_fisico = tam_bloque;
	bl.num_bloques_fisicos = num_bloques;
	write(fd,&bl,TAM_SUPERBLOQUE);


	close(fd);
	Error_SF = ERR_SINERROR;
	return 0;

}  /* FabricaDisco */



/* Monta un disco para su uso */
/* se ignora el parámetro nbloques_cache */


int MontaDisco ( const char* nombre_fichero, int nbloques_cache )
{
	struct Superbloque bl;
	int ok;

	if (Disco!=-1) close(Disco);

	Disco = open(nombre_fichero,MODO);

	if ( Disco==-1 )
	{
	  switch (errno)
	  {
	    case EACCES:
	      Error_SF=ERR_PERMISOS;
	    case ENOENT:
	      Error_SF=ERR_NOEXISTE;
	    default:
	      Error_SF=ERR_DISCO;
	  }
	  return -1;
	}

	/* Lee información del bloque cero */
	ok=LeeDatosDisco (&TamBloqueFisico,&NumBloquesFisicos);
	if (ok==-1) return -1;

	/* Pone a cero el contador de uso */
	historial_inicia(nbloques_cache);

	Error_SF=ERR_SINERROR;
	return 0;

}  /* MontaDisco */





int DesmontaDisco (void)
{
	int ok;
	if (Disco==-1)
	{
	  Error_SF=ERR_INACTIVO;
	  return -1;
	}
	ok = close(Disco);

	/* Visualiza el contador de accesos */

	historial_termina();

	if (ok==-1)
	 { Error_SF=ERR_DISCO; return -1; }
	else
	 { Error_SF=ERR_SINERROR; return 0; }
}




/* Acceso a un bloque físico (lectura o escritura) */

enum { LECTURA, ESCRITURA };


int AccedeBloqueFisico ( DWORD nbloque, void* bufer,enum ModoAccesoFisico modo )
{
	Error_SF=ERR_SINERROR;
#ifdef DEBUG
	printf("[%s:%d] ",modo==ACCESO_FISICO_LECTURA?"R":"W",nbloque);
#endif
	if ( Disco==-1 )
	{ Error_SF=ERR_INACTIVO; return -1; }
	if ( nbloque>=NumBloquesFisicos )
	{ Error_SF=ERR_RANGO; return -1; }

	/* Contador de accesos */

	historial_accede(nbloque);

	lseek (Disco,nbloque*TamBloqueFisico,SEEK_SET);
	switch (modo)
	{
	   case ACCESO_FISICO_ESCRITURA:
	     return write ( Disco,bufer,TamBloqueFisico ) ==TamBloqueFisico;
	   case ACCESO_FISICO_LECTURA:
	     return read ( Disco,bufer,TamBloqueFisico ) ==TamBloqueFisico;
	   default:
	     Error_SF=ERR_CORRUPTO;
	     return -1;
	}
}


int LeeBloqueFisico ( DWORD nbloque, void* bufer )
{ return AccedeBloqueFisico(nbloque,bufer,LECTURA); }


int EscribeBloqueFisico ( DWORD nbloque, const void* bufer )
{ return AccedeBloqueFisico(nbloque,(void*)bufer,ESCRITURA); }



/* Acceso al superbloque (datos del disco) */


int LeeDatosDisco ( WORD* tam_bloque, DWORD* num_bloques )
{
   struct Superbloque sb;

   if (Disco==-1)
    { Error_SF=ERR_INACTIVO; return -1; }

   /* Recoge el superbloque */
   lseek(Disco,0,SEEK_SET);
   read(Disco,&sb,TAM_SUPERBLOQUE);

   /* Verifica que el disco tiene la "palabra mágica" */
   if ( strcmp(sb.magico,MAGICO) )
   {
     Error_SF=ERR_FORMATO;
     return -1;
   }

   /* Verifica que el tamaño de bloque físico es correcto */
   if (    sb.tam_bloque_fisico<MIN_TAMBLOQUE
	|| sb.tam_bloque_fisico>MAX_TAMBLOQUE
	|| sb.num_bloques_fisicos==0
      )
   {
     Error_SF = ERR_FORMATO;
     return -1;
   }

   *tam_bloque = sb.tam_bloque_fisico;
   *num_bloques = sb.num_bloques_fisicos;
   Error_SF = ERR_SINERROR;
   return 0;
}




/* Contadores de referencias y localidad */

static unsigned long nbloques_accedidos=0;
static long* historia = 0;
static long* cuenta = 0;
static long nrefs;
static double localidad = 0.0;
static int ventana_historial=25;



void historial_inicia(int ventana)
{
  int i;
  nbloques_accedidos=0;
  nrefs=0;
  localidad=0.0;
  if (ventana>0) ventana_historial=ventana;
  historia = (long*)malloc(sizeof(long)*ventana_historial);
  cuenta = (long*)malloc(sizeof(long)*NumBloquesFisicos);
  for (i=0;i<NumBloquesFisicos;i++) 
    cuenta[i]=0;
}



void historial_termina()
{
  FILE* log = fopen("sffisico.log","a");
  if (nbloques_accedidos==0) { fclose(log); return; }
  fprintf(log,
	"Bloques=%u\tLocalidad=%3.2lf%%\n",
	nbloques_accedidos,
	100*(1-localidad/(nbloques_accedidos*ventana_historial)));
  fclose(log);
  free(historia);
  free(cuenta);
}



void historial_accede (DWORD nbloque)
{
  if (nbloques_accedidos<ventana_historial)
  {
    historia[nbloques_accedidos] = nbloque;
    cuenta[nbloque]++;
    if (cuenta[nbloque]==1) nrefs++;
  }
  else
  {
    int i;
    DWORD antiguo = historia[0];
    cuenta[antiguo]--;
    if (cuenta[antiguo]==0) nrefs--;
    for (i=0;i<ventana_historial-1;i++)
      historia[i] = historia[i+1];
    historia[ventana_historial-1] = nbloque;
    cuenta[nbloque]++;
    if (cuenta[nbloque]==1) nrefs++;
    localidad += nrefs;
  }

  nbloques_accedidos++;
}