/*
  data source for libisoburn.

  Copyright 2007 Vreixo Formoso Lopes <metalpain2002@yahoo.es>
*/

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

#include <stdio.h>


#ifndef Xorriso_standalonE

#include <libburn/libburn.h>

#include <libisofs/libisofs.h>

#else /* ! Xorriso_standalonE */

#include "../libisofs/libisofs.h"
#include "../libburn/libburn.h"

#endif /* Xorriso_standalonE */


#include "isoburn.h"


/* Powers of 2 only ! Less than 16 makes not much sense. */
#define Libisoburn_tile_blockS 32

/* Undef to get to older single tile version
*/
#define Libisoburn_cache_tileS 32

/* Debugging only: This reports cache loads on stderr.
#define Libisoburn_read_cache_reporT 1
*/


/* Cached reading of image tree data */

#ifdef Libisoburn_cache_tileS

/* Multi tile:  32 * 64 kB */

struct isoburn_cache_tile {
 char cache_data[Libisoburn_tile_blockS * 2048];
 uint32_t cache_lba;
 uint32_t last_error_lba;
 uint32_t last_aligned_error_lba;
 int cache_hits;
 int age;
};

struct isoburn_cached_drive {
 struct burn_drive *drive;
 struct isoburn_cache_tile tiles[Libisoburn_cache_tileS];
 int current_age;
};

#define Libisoburn_max_agE 2000000000

static int ds_inc_age(struct isoburn_cached_drive *icd, int idx, int flag);


int ds_read_block(IsoDataSource *src, uint32_t lba, uint8_t *buffer)
{
 int ret, i, oldest, oldest_age;
 struct burn_drive *d;
 off_t count;
 uint32_t aligned_lba;
 char msg[80];
 struct isoburn_cache_tile *tiles;
 struct isoburn_cached_drive *icd;

 if(src == NULL || buffer == NULL)
   return -1;

 icd = (struct isoburn_cached_drive *) src->data;
 d = (struct burn_drive*) icd->drive;
 tiles = (struct isoburn_cache_tile *) icd->tiles;

 aligned_lba= lba & ~(Libisoburn_tile_blockS - 1);

 for(i=0; i<Libisoburn_cache_tileS; i++) {
   if(aligned_lba == tiles[i].cache_lba && tiles[i].cache_lba != 0xffffffff) {
     (tiles[i].cache_hits)++;
     memcpy(buffer, tiles[i].cache_data + (lba - aligned_lba) * 2048, 2048);
     count= 2048;
     ds_inc_age(icd, i, 0);
     return 1;
   }
 }

 /* find oldest tile */
 oldest_age= Libisoburn_max_agE;
 oldest= 0;
 for(i= 0; i<Libisoburn_cache_tileS; i++) {
   if(tiles[i].cache_lba == 0xffffffff) {
     oldest= i;
 break;
   }
   if(tiles[i].age<oldest_age) {
     oldest_age= tiles[i].age;
     oldest= i;
   }
 }

 tiles[oldest].cache_lba= 0xffffffff; /* invalidate cache */
 if(tiles[oldest].last_aligned_error_lba == aligned_lba) {
   ret = 0;
 } else {
   ret = burn_read_data(d, (off_t) aligned_lba * (off_t) 2048,
                        (char *) tiles[oldest].cache_data,
                        Libisoburn_tile_blockS * 2048, &count, 2);
 }
 if (ret <= 0 ) {
   tiles[oldest].last_aligned_error_lba = aligned_lba;

   /* Read-ahead failure ? Try to read 2048 directly. */
   if(tiles[oldest].last_error_lba == lba)
     ret = 0;
   else
     ret = burn_read_data(d, (off_t) lba * (off_t) 2048, (char *) buffer,
                        2048, &count, 0);
   if (ret > 0)
     return 1;
   tiles[oldest].last_error_lba = lba;
   sprintf(msg, "ds_read_block(%lu) returns -1", (unsigned long) lba);
   burn_msgs_submit(0x00060000, msg, 0, "DEBUG", NULL);
   return -1;
 }

#ifdef Libisoburn_read_cache_reporT
 fprintf(stderr, "Tile %2.2d : After %3d hits, new load from %8x , count= %d\n",
         oldest, tiles[oldest].cache_hits, aligned_lba, (int) count);
#endif

 tiles[oldest].cache_lba= aligned_lba;
 tiles[oldest].cache_hits= 1;
 ds_inc_age(icd, oldest, 0);

 memcpy(buffer, tiles[oldest].cache_data + (lba - aligned_lba) * 2048, 2048);
 count= 2048;

 return 1;
}


static int ds_open(IsoDataSource *src)
{
 /* nothing to do, device is always grabbed */
 return 1;
}

static int ds_close(IsoDataSource *src)
{
 /* nothing to do, device is always grabbed */
 return 1;
}

static void ds_free_data(IsoDataSource *src)
{
 /* nothing to do */;
 if(src->data != NULL)
   free(src->data);
 src->data= NULL;
}

IsoDataSource *isoburn_data_source_new(struct burn_drive *d)
{
 IsoDataSource *ret;
 struct isoburn_cached_drive *icd= NULL;
 int i;

 if (d==NULL)
   return NULL;
 ret = malloc(sizeof(IsoDataSource));
 icd = calloc(1,sizeof(struct isoburn_cached_drive));
 if (ret == NULL || icd == NULL)
   return NULL;
 ret->refcount = 1;
 ret->read_block = ds_read_block;
 ret->open = ds_open;
 ret->close = ds_close;
 ret->free_data = ds_free_data;
 ret->data = icd;
 icd->drive = d;
 icd->current_age= 0;
 for(i= 0; i<Libisoburn_cache_tileS; i++) {
   icd->tiles[i].cache_lba = 0xffffffff;
   icd->tiles[i].cache_hits = 0;
   icd->tiles[i].last_error_lba = 0xffffffff;
   icd->tiles[i].last_aligned_error_lba = 0xffffffff;
   icd->tiles[i].age= 0;
 }
 return ret;
}


static int ds_inc_age(struct isoburn_cached_drive *icd, int idx, int flag)
{
 int i;

 (icd->current_age)++;
 if(icd->current_age>=Libisoburn_max_agE) { /* reset all ages (allow waste) */
   for(i= 0; i<Libisoburn_cache_tileS; i++)
     (icd->tiles)[i].age= 0;
   icd->current_age= 1;
 }
 (icd->tiles)[idx].age= icd->current_age;
 return(1);
}


#else /* Libisoburn_cache_tileS */


/* Single tile 128 kB */


struct isoburn_cached_drive {
 struct burn_drive *drive;

 char cache_data[Libisoburn_tile_blockS * 2048];
 uint32_t cache_lba;
 uint32_t last_error_lba;
 uint32_t last_aligned_error_lba;
 int cache_hits;
};


/* Debugging only: This reports cache loads on stderr.
#define Libisoburn_read_cache_reporT 1
*/

int 
ds_read_block(IsoDataSource *src, uint32_t lba, uint8_t *buffer)
{
 int ret;
 struct burn_drive *d;
 off_t count;
 uint32_t aligned_lba;
 char msg[80];

 struct isoburn_cached_drive *icd;

 if(src == NULL || buffer == NULL)
   return -1;

 icd = (struct isoburn_cached_drive *) src->data;
 d = (struct burn_drive*) icd->drive;

 aligned_lba= lba & ~(Libisoburn_tile_blockS - 1);
 if(aligned_lba == icd->cache_lba && icd->cache_lba != 0xffffffff) {
   (icd->cache_hits)++;
   memcpy(buffer, icd->cache_data + (lba - aligned_lba) * 2048, 2048);
   count= 2048;
   return 1;
 } 

 icd->cache_lba= 0xffffffff; /* invalidate cache */
 if(icd->last_aligned_error_lba == aligned_lba) {
   ret = 0;
 } else {
   ret = burn_read_data(d, (off_t) aligned_lba * (off_t) 2048,
                        (char *) icd->cache_data,
                        Libisoburn_tile_blockS * 2048, &count, 0);
 }
 if (ret <= 0 ) {
   icd->last_aligned_error_lba = aligned_lba;

   /* Read-ahead failure ? Try to read 2048 directly. */
   if(icd->last_error_lba == lba)
     ret = 0;
   else
     ret = burn_read_data(d, (off_t) lba * (off_t) 2048, (char *) buffer,
                        2048, &count, 0);
   if (ret > 0)
     return 1;
   icd->last_error_lba = lba;
   sprintf(msg, "ds_read_block(%lu) returns -1", (unsigned long) lba);
   burn_msgs_submit(0x00060000, msg, 0, "DEBUG", NULL);
   return -1;
 }

#ifdef Libisoburn_read_cache_reporT
 fprintf(stderr, "After %3d hits, new load from %8x , count= %d\n",
         icd->cache_hits, aligned_lba, (int) count);
#endif

 icd->cache_lba= aligned_lba;
 icd->cache_hits= 1;
 memcpy(buffer, icd->cache_data + (lba - aligned_lba) * 2048, 2048);
 count= 2048;

 return 1;
}


static int
ds_open(IsoDataSource *src)
{
 /* nothing to do, device is always grabbed */
 return 1;
}

static int
ds_close(IsoDataSource *src)
{
 /* nothing to do, device is always grabbed */
 return 1;
}

static void 
ds_free_data(IsoDataSource *src)
{
 /* nothing to do */;
 if(src->data != NULL)
   free(src->data);
 src->data= NULL;
}

IsoDataSource *
isoburn_data_source_new(struct burn_drive *d)
{
 IsoDataSource *ret;
 struct isoburn_cached_drive *icd= NULL;

 if (d==NULL)
   return NULL;
 ret = malloc(sizeof(IsoDataSource));
 icd = calloc(1,sizeof(struct isoburn_cached_drive));
 if (ret == NULL || icd == NULL)
   return NULL;
 ret->refcount = 1;
 ret->read_block = ds_read_block;
 ret->open = ds_open;
 ret->close = ds_close;
 ret->free_data = ds_free_data;
 ret->data = icd;
 icd->drive = d;
 icd->cache_lba = 0xffffffff;
 icd->cache_hits = 0;
 icd->last_error_lba = 0xffffffff;
 icd->last_aligned_error_lba = 0xffffffff;
 return ret;
}

#endif /* ! Libisoburn_cache_tileS */

