/*         ______   ___    ___

 *        /\  _  \ /\_ \  /\_ \

 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___

 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\

 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \

 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/

 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/

 *                                           /\____/

 *                                           \_/__/

 *

 *      File Slices - treat a subset of a random access file 

 *                    as its own file

 *

 *      See LICENSE.txt for copyright information.

 */



#include "allegro5/allegro.h"



typedef struct SLICE_DATA SLICE_DATA;



enum {

   SLICE_READ = 1,

   SLICE_WRITE = 2,

   SLICE_EXPANDABLE = 4,

   SLICE_SEEK_END = 8

};



struct SLICE_DATA

{

   ALLEGRO_FILE *fp; /* parent file handle */

   size_t anchor;    /* beginning position relative to parent */

   size_t pos;       /* position relative to anchor */

   size_t size;      /* size of slice relative to anchor */

   int mode;

};



static bool slice_fclose(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   bool ret;



   /* seek to end of slice */

   if (slice->mode & SLICE_SEEK_END)

       ret = al_fseek(slice->fp, slice->anchor + slice->size, ALLEGRO_SEEK_SET);

   else

       ret = true;



   al_free(slice);



   return ret;

}



static size_t slice_fread(ALLEGRO_FILE *f, void *ptr, size_t size)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   

   if (!(slice->mode & SLICE_READ)) {

      /* no read permissions */

      return 0;

   }

   

   if (!(slice->mode & SLICE_EXPANDABLE) && slice->pos + size > slice->size) {

      /* don't read past the buffer size if not expandable */

      size = slice->size - slice->pos;

   }

   

   if (!size) {

      return 0;

   }

   else {

      /* unbuffered, read directly from parent file */

      size_t b = al_fread(slice->fp, ptr, size);

      slice->pos += b;

   

      if (slice->pos > slice->size)

         slice->size = slice->pos;

      

      return b;

   }

}



static size_t slice_fwrite(ALLEGRO_FILE *f, const void *ptr, size_t size)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   

   if (!(slice->mode & SLICE_WRITE)) {

      /* no write permissions */

      return 0;

   }

   

   if (!(slice->mode & SLICE_EXPANDABLE) && slice->pos + size > slice->size) {

      /* don't write past the buffer size if not expandable */

      size = slice->size - slice->pos;

   }

   

   if (!size) {

      return 0;

   }

   else {

      /* unbuffered, write directly to parent file */

      size_t b = al_fwrite(slice->fp, ptr, size);

      slice->pos += b;

   

      if (slice->pos > slice->size)

         slice->size = slice->pos;

      

      return b;

   }

}



static bool slice_fflush(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   

   return al_fflush(slice->fp);

}



static int64_t slice_ftell(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   return slice->pos;

}



static bool slice_fseek(ALLEGRO_FILE *f, int64_t offset, int whence)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   

   if (whence == ALLEGRO_SEEK_SET) {

      offset = slice->anchor + offset;

   }

   else if (whence == ALLEGRO_SEEK_CUR) {

      offset = slice->anchor + slice->pos + offset;

   }

   else if (whence == ALLEGRO_SEEK_END) {

      offset = slice->anchor + slice->size + offset;

   }

   else {

      return false;

   }

   

   if ((size_t) offset < slice->anchor) {

      offset = slice->anchor;

   }

   else if ((size_t) offset > slice->anchor + slice->size) {

      if (!(slice->mode & SLICE_EXPANDABLE)) {

         offset = slice->anchor + slice->size;

      }

   }

   

   if (al_fseek(slice->fp, offset, ALLEGRO_SEEK_SET)) {

      slice->pos = offset - slice->anchor;

      if (slice->pos > slice->size)

         slice->size = slice->pos;

      return true;

   }

   

   return false;

}



static bool slice_feof(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   return slice->pos >= slice->size;

}



static int slice_ferror(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   return al_ferror(slice->fp);

}



static const char *slice_ferrmsg(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   return al_ferrmsg(slice->fp);

}



static void slice_fclearerr(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   al_fclearerr(slice->fp);

}



static off_t slice_fsize(ALLEGRO_FILE *f)

{

   SLICE_DATA *slice = al_get_file_userdata(f);

   return slice->size;

}



static const ALLEGRO_FILE_INTERFACE fi =

{

   NULL,

   slice_fclose,

   slice_fread,

   slice_fwrite,

   slice_fflush,

   slice_ftell,

   slice_fseek,

   slice_feof,

   slice_ferror,

   slice_ferrmsg,

   slice_fclearerr,

   NULL,

   slice_fsize

};



/* Function: al_fopen_slice

 */

ALLEGRO_FILE *al_fopen_slice(ALLEGRO_FILE *fp, size_t initial_size, const char *mode)

{

   SLICE_DATA *userdata = al_calloc(1, sizeof(*userdata));

   int ch;

   

   if (!userdata) {

      return NULL;

   }

   

   // OBSOLETE_V5 code should go away when the API can be changed.

#ifndef OBSOLETE_V5

   userdata->mode |= SLICE_SEEK_END;

#endif



   for ( ; (ch = *mode); ++mode) {

       if (ch == 'r' || ch == 'R')

          userdata->mode |= SLICE_READ;

       else if (ch == 'w' || ch == 'W')

          userdata->mode |= SLICE_WRITE;

       else if (ch == 'e' || ch == 'E')

          userdata->mode |= SLICE_EXPANDABLE;

       else if (ch == 's' || ch == 'S')

          userdata->mode |= SLICE_SEEK_END;

#ifndef OBSOLETE_V5

       else if (ch == 'n' || ch == 'N')

          userdata->mode &= ~SLICE_SEEK_END;

#endif

   }



   userdata->fp = fp;

   userdata->anchor = al_ftell(fp);

   userdata->size = initial_size;

   

   return al_create_file_handle(&fi, userdata);

}