// Copyright (c) 2005-2008 ASCLEPIOS Project, INRIA Sophia-Antipolis (France)
// All rights reserved.
//
// This file is part of the ImageIO Library, and as been adapted for CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/CGAL_ImageIO/include/CGAL/ImageIO/inr_impl.h $
// $Id: include/CGAL/ImageIO/inr_impl.h 08b27d3db14 $
// SPDX-License-Identifier: LGPL-3.0-or-later
//
//
// Author(s)     :  ASCLEPIOS Project (INRIA Sophia-Antipolis), Laurent Rineau

#ifdef CGAL_HEADER_ONLY
#define CGAL_INLINE_FUNCTION inline
#else
#define CGAL_INLINE_FUNCTION
#endif

#include <CGAL/ImageIO/fgetns.h>

#include <string>
#include <sstream>
#include <string.h>

/* Magic header for inrimages v4 */
#define INR4_MAGIC "#INRIMAGE-4#{"


/** Magic header for inrimages */
#define INR_MAGIC "#INR"

typedef struct stringListElementStruct {
  char *string;
  struct stringListElementStruct *next;
} stringListElement;
/* list element with a pointer on a string */

typedef struct {
  stringListElement *begin, *end;
} stringListHead;
/* string list descriptor */

#include <clocale>
class Set_numeric_locale {
  const char * old_locale;
public:
  Set_numeric_locale(const char* locale)
    : old_locale(std::setlocale(LC_NUMERIC, locale))
  {
  }

  ~Set_numeric_locale() {
    std::setlocale(LC_NUMERIC, old_locale);
  }
};

static void addStringElement(stringListHead *strhead,
                             const char *str);
/* add a string element at the tail of given list */

static void concatStringElement(const stringListHead *strhead,
                                const char *str);
/* concat given string at the last element of given list */


/* Writes the given inrimage header in an already opened file.*/
CGAL_INLINE_FUNCTION
int _writeInrimageHeader(const _image *im, ENDIANNESS end) {
  std::size_t pos, i;
  char type[30], endianness[5], buf[257], scale[20];
  std::ostringstream oss;

  Set_numeric_locale num_locale("C");

  if(im->openMode != OM_CLOSE) {
    /* fix word kind */
    switch(im->wordKind) {

    case WK_FLOAT:
      snprintf(type, 30, "float");
      scale[0] = '\0';
      break;

    case WK_FIXED:
      switch(im->sign) {
      case SGN_SIGNED:
        snprintf(type, 30, "signed fixed");
        break;
      case SGN_UNSIGNED:
        snprintf(type, 30, "unsigned fixed");
        break;
      default:
        return -1;
      }
      snprintf(scale, 20, "SCALE=2**0\n");
      break;

    default:
      return -1;
    }

    switch(end) {
    case END_LITTLE:
      snprintf(endianness, 5, "decm");
      break;
    case END_BIG:
      snprintf(endianness, 5, "sun");
      break;
    default:
      /* fix architecture endianness */
      if( _getEndianness() == END_LITTLE)
        snprintf(endianness, 5, "decm");
      else
        snprintf(endianness, 5, "sun");
      break;
    }

    /* write header information */
    oss << INR4_MAGIC << "\n";
    oss << "XDIM=" << im->xdim << "\n";
    oss << "YDIM=" << im->ydim << "\n";
    oss << "ZDIM=" << im->zdim << "\n";
    oss << "VDIM=" << im->vdim << "\n";
    oss << "TYPE=" << type << "\n";
    oss << "PIXSIZE=" << im->wdim*8 <<" bits\n";
    oss << scale << "CPU=" << endianness << "\n";
    oss << "VX=" << im->vx << "\n";
    oss << "VY=" << im->vy << "\n";
    oss << "VZ=" << im->vz << "\n";

    if ( im->cx != 0 )
      oss << "XO="<< im->cx << "\n";
    if ( im->cy != 0 )
      oss << "YO="<< im->cy << "\n";
    if ( im->cz != 0 )
      oss << "ZO="<< im->cz << "\n";
    if ( im->tx != 0.0 )
      oss << "TX="<< im->tx << "\n";
    if ( im->ty != 0.0 )
      oss << "TY="<< im->ty << "\n";
    if ( im->tz != 0.0 )
      oss << "TZ="<< im->tz << "\n";
    if ( im->rx != 0.0 )
      oss << "RX="<< im->rx <<"\n";
    if ( im->ry != 0.0 )
      oss << "RY="<< im->ry << "\n";
    if ( im->rz != 0.0 )
      oss << "RZ=" << im->rz <<"\n";

    pos = oss.str().length();

    if(ImageIO_write(im, oss.str().data(), oss.str().length()) == 0)
      return -1;


    /* write user strings */
    if ( im->user != nullptr ) {
      for(i = 0; i < im->nuser; i++) {
        if ( im->user[i] == nullptr ) continue;
        pos += strlen(im->user[i]) + 2;
        if(ImageIO_write(im, "#", 1) == 0) return -1;
        if(ImageIO_write(im, im->user[i], strlen(im->user[i])) == 0) return -1;
        if(ImageIO_write(im, "\n", 1) == 0) return -1;
      }
    }
    /* write end of header */
    pos = pos % 256;
    if(pos > 252) {
      for(i = pos; i < 256; i++)
        if(ImageIO_write(im, "\n", 1) != 1) return -1;
      pos = 0;
    }
    buf[0] = '\0';
    for(i = pos; i < 252; i++) strcat(buf, "\n");
    strcat(buf, "##}\n");

    if(ImageIO_write(im, buf, strlen(buf)) == 0) return -1;
    else return 1;
  }

  else return -1;
}



/* Writes the given image body in an already opened file.*/
CGAL_INLINE_FUNCTION
bool _writeInrimageData(const _image *im) {
  std::size_t size, nbv, nwrt, i, v;
  unsigned char **vp;

  if(im->openMode != OM_CLOSE) {

    /* scalar or interlaced vectors */
    if(im->vectMode != VM_NON_INTERLACED) {
      size = im->xdim * im->ydim * im->zdim * im->vdim * im->wdim;
      nwrt = ImageIO_write(im, im->data, size);
      return nwrt == size;
    }

    /* non interlaced vectors: interlace for saving */
    else {
      nbv = im->xdim * im->ydim * im->zdim;
      size = im->xdim * im->ydim * im->zdim * im->wdim;
      vp = (unsigned char **) ImageIO_alloc(im->vdim * sizeof(unsigned char *));
      for(v = 0; v < im->vdim; v++)
        vp[v] = (unsigned char *) im->data + v * size;
      for(i = 0; i < nbv; i++)
        for(v = 0; v < im->vdim; v++) {
          if(ImageIO_write(im, (const void *) vp[v], im->wdim) != im->wdim)
            return false;
          vp[v] += im->wdim;
        }
      ImageIO_free(vp);
      return true;
    }
  }
  else return false;
}




/* read header of an opened inrimage */
CGAL_INLINE_FUNCTION
int readInrimageHeader(const char *,_image *im) {
  char str[257];
  int n, nusr;
  stringListHead strl = { nullptr, nullptr };
  stringListElement *oel, *el;

  Set_numeric_locale num_locale("C");

  if(im->openMode != OM_CLOSE) {
    /* read image magic number */
    if(!fgetns(str, 257, im )) return -1;
    if(strcmp(str, INR4_MAGIC)) return -1;


    /* while read line does not begin with '#' or '\n', read line
       and decode field */
    if(!fgetns(str, 257, im)) return -1;

    while(str[0] != '#' && str[0] != '\0') {

      if(!strncmp(str, "XDIM=", 5)) {
        std::istringstream iss(str+5);
        if(!(iss >> im->xdim)) return -1;
      }
      else if(!strncmp(str, "YDIM=", 5)) {
        std::istringstream iss(str+5);
        if(!(iss >> im->ydim)) return -1;
      }
      else if(!strncmp(str, "ZDIM=", 5)) {
        std::istringstream iss(str+5);
        if(!(iss >> im->zdim)) return -1;
      }
      else if(!strncmp(str, "VDIM=", 5)) {
        std::istringstream iss(str+5);
        if(!(iss >> im->vdim)) return -1;
        if(im->vdim == 1) im->vectMode = VM_SCALAR;
        else im->vectMode = VM_INTERLACED;
      }
      else if(!strncmp(str, "VX=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->vx)) return -1;
      }
      else if(!strncmp(str, "VY=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->vy)) return -1;
      }
      else if(!strncmp(str, "VZ=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->vz)) return -1;
      }
      else if(!strncmp(str, "TYPE=", 5)) {
        if(!strncmp(str+5, "float", 5)) im->wordKind = WK_FLOAT;
        else {
          if(!strncmp(str+5, "signed fixed", 12)) {
            im->wordKind = WK_FIXED;
            im->sign = SGN_SIGNED;
          }
          else if(!strncmp(str+5, "unsigned fixed", 14)) {
            im->wordKind = WK_FIXED;
            im->sign = SGN_UNSIGNED;
          }
          else return -1;
        }
      }
      /* before "sscanf(str+8, "%i %n", &im->wdim, &n) != 1"
         was used.
         However the man said
         ...
         n      Nothing is expected; instead, the number of charac­
              ters consumed thus far from  the  input  is  stored
              through  the  next pointer, which must be a pointer
              to int.  This is not a conversion, although it  can
              be  suppressed  with  the  *  flag.  The C standard
              says: `Execution of a %n directive does not  incre­
              ment  the  assignment count returned at the comple­
              tion of execution' but  the  Corrigendum  seems  to
              contradict  this.  Probably  it is wise not to make
              any assumptions on the effect of %n conversions  on
              the return value.
         ...
         Thus I change it. It was yielding a RETURN_FAILURE with
         insight (GM).
      */
      else if(!strncmp(str, "PIXSIZE=", 8)) {
        std::istringstream iss(str+8);
        if(!(iss >> im->wdim)) return -1;
        if(im->wdim != 8 && im->wdim != 16 && im->wdim != 32 &&
           im->wdim != 64) return -1;

        if ( im->wdim <= 9 ) {
          if(strncmp(str+8+1, " bits", 5)) return -1;
        }
        else if ( im->wdim <= 99 ) {
          if(strncmp(str+8+2, " bits", 5)) return -1;
        }
        else {
          return -1;
        }

        im->wdim >>= 3;
      }
      else if(!strncmp(str, "SCALE=", 6)) ;
      else if(!strncmp(str, "CPU=", 4)) {
        if(!strncmp(str+4, "decm", 4)) im->endianness = END_LITTLE;
        else if(!strncmp(str+4, "alpha", 5)) im->endianness = END_LITTLE;
        else if(!strncmp(str+4, "pc", 2)) im->endianness = END_LITTLE;
        else if(!strncmp(str+4, "sun", 3)) im->endianness = END_BIG;
        else if(!strncmp(str+4, "sgi", 3)) im->endianness = END_BIG;
        else return -1;
      }

      else if(!strncmp(str, "XO=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->cx)) return -1;
      }
      else if(!strncmp(str, "YO=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->cy)) return -1;
      }
      else if(!strncmp(str, "ZO=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->cz)) return -1;
      }

      else if(!strncmp(str, "TX=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->tx)) return -1;
      }
      else if(!strncmp(str, "TY=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->ty)) return -1;
      }
      else if(!strncmp(str, "TZ=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->tz)) return -1;
      }
      else if(!strncmp(str, "RX=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->rx)) return -1;
      }
      else if(!strncmp(str, "RY=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->ry)) return -1;
      }
      else if(!strncmp(str, "RZ=", 3)) {
        std::istringstream iss(str+3);
        if(!(iss >> im->rz)) return -1;
      }

      if(!fgetns(str, 257, im)) return -1;
    }

    /* parse user strings */
    im->nuser = nusr = 0;
    while(str[0] == '#' && strncmp(str, "##}", 3)) {
      addStringElement(&strl, str + 1);
      while(strlen(str) == 256) {
        if(!fgetns(str, 257, im)) return -1;
        concatStringElement(&strl, str);
      }
      nusr++;
      if(!fgetns(str, 257, im)) return -1;
    }

    /* go to end of header */
    while(strncmp(str, "##}", 3)) {
      if(!fgetns(str, 257, im)) return -1;
    }


    /* check header validity */
    if(im->xdim > 0 && im->ydim > 0 && im->zdim > 0 && im->vdim > 0 &&
       im->vx > 0.0 && im->vy > 0.0 && im->vz > 0.0 &&
       (im->wordKind == WK_FLOAT || (im->wordKind == WK_FIXED &&
                                     im->sign != SGN_UNKNOWN)) &&
       im->endianness != END_UNKNOWN) {
      if(nusr > 0) {
        im->nuser = nusr;
        im->user = (char **) ImageIO_alloc(im->nuser * sizeof(char *));
        oel = nullptr;
        for(el = strl.begin, n = 0; el != nullptr; el = oel, n++) {
          im->user[n] = el->string;
          oel = el->next;
          ImageIO_free(el);
        }
      }
      return 0;
    }
    else return -1;

  }
  else return -1;
}



/* add a string element at the tail of given list */
static void addStringElement(stringListHead *strhead, const char *str) {
  stringListElement *el;

  el = (stringListElement *) ImageIO_alloc(sizeof(stringListElement));
  /* was strdup(str); */
  el->string = (char*)ImageIO_alloc( strlen(str)+1);
  memcpy(el->string, str,  strlen(str)+1);
  el->next = nullptr;
  if(strhead->begin == nullptr)
    strhead->begin = strhead->end = el;
  else {
    strhead->end->next = el;
    strhead->end = el;
  }
}


/* concat given string at the last element of given list */
static void concatStringElement(const stringListHead *strhead,
                                const char *str) {
  stringListElement *el;

  el = strhead->end;
  el->string = (char *) realloc(el->string,
                                strlen(el->string) + strlen(str) + 1);
  strcat(el->string, str);
}

CGAL_INLINE_FUNCTION
int testInrimageHeader(char *magic,const char *) {
  if (!strcmp(magic, INR_MAGIC))
    return 0;
  else
    return -1;
}
CGAL_INLINE_FUNCTION
int writeInrimage(char *name,_image *im) {
  int res;

  _openWriteImage( im, name );

  if(!im->fd) {
    fprintf(stderr, "writeInrimage: error: unable to open file \'%s\'\n", name );
    return ImageIO_OPENING;
  }

  res = _writeInrimageHeader(im, END_UNKNOWN);
  if (res < 0) {
    fprintf(stderr, "writeInrimage: error: unable to write header of \'%s\'\n",
            name);
    ImageIO_close( im );
    im->fd = nullptr;
    im->openMode = OM_CLOSE;
    return( res );
  }

  if (!_writeInrimageData(im)) {
    fprintf(stderr, "writeInrimage: error: unable to write data of \'%s\'\n",
            name);
    ImageIO_close( im );
    im->fd = nullptr;
    im->openMode = OM_CLOSE;
    return -1;
  }

  ImageIO_close( im );
  im->fd = nullptr;
  im->openMode = OM_CLOSE;

  return ( res );
}

CGAL_INLINE_FUNCTION
PTRIMAGE_FORMAT createInrimageFormat() {
  PTRIMAGE_FORMAT f=(PTRIMAGE_FORMAT) ImageIO_alloc(sizeof(IMAGE_FORMAT));

  f->testImageFormat=&testInrimageHeader;
  f->readImageHeader=&readInrimageHeader;
  f->writeImage=&writeInrimage;
  strcpy(f->fileExtension,".inr,.inr.gz,.gradient,.gradient.gz,.gradient_direction,.gradient_direction.gz");
  strcpy(f->realName,"Inrimage");
  return f;
}
