/*
 *   Copyright 1995, University Corporation for Atmospheric Research
 *   See top level COPYRIGHT file for copying and redistribution conditions.
 */
/* Id */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "ncio.h" 
#ifndef ENOERR
#define ENOERR 0
#endif


static void
usage(const char *av0)
{
  (void)fprintf(stderr,
    "Usage: %s [options] fname\t\nOptions:\n", av0);
  (void)fprintf(stderr,
    "\t-v   Verbose\n") ;
  (void)fprintf(stderr,
    "\t-w   Open Read/Write, default is read only\n") ;
  (void)fprintf(stderr,
    "\t-c   Create, clobber existing\n") ;
  (void)fprintf(stderr,
    "\t-n   Create, error if it already exists\n") ;
  (void)fprintf(stderr,
    "\t-L   Use locking if available\n") ;
  (void)fprintf(stderr,
    "\t-S   Share updates (turn off caching)\n") ;
  (void)fprintf(stderr,
    "\t-U   Delete (unlink) on close\n") ;
  (void)fprintf(stderr,
    "\t-o igeto Initial get offset\n") ;
  (void)fprintf(stderr,
    "\t-i igetsz  Initial get size\n") ;
  (void)fprintf(stderr,
    "\t-I initialsz Initial file size for create\n") ;
  (void)fprintf(stderr,
    "\t-s sizehint  Buffer size\n") ;
  exit(EXIT_FAILURE);
}


static long
argscale(const char *arg, const char *tag)
{
  long value = 0;
    /* last character */
  const char *cp = arg + strlen(arg) -1;
  value = atol(arg);
  if(isalpha(*cp))
  {
    switch(*cp) {
    case 'k':
    case 'K':
      value *= 1024;
      break;
    case 'm':
    case 'M':
      value *= (1024*1024);
      break;
    default:
      value = 0; /* trigger error below */
      break;
    }
  }
  if(value == 0)
  {
    fprintf(stderr,
       "Illegal %s \"%s\", ignored\n", tag, arg);
  }
  return value;
}


static void
modify_ex(off_t offset, size_t extent, void *vp)
{
  unsigned char *obuf = vp;
  const unsigned char *const end = &obuf[extent];
  unsigned char *cp = obuf;

  if(cp >= end) return;
  *cp++ = (unsigned char)( offset               >> 24);
  if(cp >= end) return;
  *cp++ = (unsigned char)((offset & 0x00ff0000) >> 16);
  if(cp >= end) return;
  *cp++ = (unsigned char)((offset & 0x0000ff00) >>  8);
  if(cp >= end) return;
  *cp++ = (unsigned char)( offset & 0x000000ff);
  if(cp >= end) return;
  *cp++ = (unsigned char)( extent               >> 24);
  if(cp >= end) return;
  *cp++ = (unsigned char)((extent & 0x00ff0000) >> 16);
  if(cp >= end) return;
  *cp++ = (unsigned char)((extent & 0x0000ff00) >>  8);
  if(cp >= end) return;
  *cp++   = (unsigned char)( extent & 0x000000ff);

  while(cp < end)
  {
    *cp++ = (unsigned char) (cp - obuf); 
  }
}


typedef struct riu {
  struct riu *next;
  struct riu *prev;
  off_t offset;
  size_t extent;
  void *vp;
} riu;

static void
free_riu(riu *riup)
{
  if(riup == NULL)
    return;
  free(riup);
}

static riu *
new_riu(off_t offset, size_t extent, void *vp)
{
  riu *riup = (riu *)malloc(sizeof(riu));
  if(riup == NULL)
  {
    fprintf(stderr,
      "new_riu: malloc failed\n");
    exit(EXIT_FAILURE);
  }
  riup->next = NULL;
  riup->prev = NULL;
  riup->offset = offset;
  riup->extent = extent;
  riup->vp = vp;
  return riup;
}

static riu *stack = NULL;

static void
riu_push(off_t offset, size_t extent, void *vp)
{
  riu *riup = new_riu(offset, extent, vp);
  /* assert(riup != NULL); */
  riup->next = stack;
  if(stack != NULL)
    stack->prev = riup;
  stack = riup;
}

static int
riu_pop(off_t offset, int modify)
{
  riu *riup = stack;
  while(riup != NULL)
  {
    if(riup->offset == offset)
    {
      if(modify)
      {
        modify_ex(riup->offset, riup->extent, riup->vp);
      }
      if(riup->next != NULL)
      {
        riup->next->prev = riup->prev;
      }
      if(riup == stack)
      {
        stack = riup->next;
      }
      else
      {
        assert(riup->prev != NULL);
        riup->prev->next = riup->next;
      }
      free_riu(riup);
      return 1;
    }
    riup = riup->next;
  }
  /* else, not found */
  return 0;
}

main(int ac, char *av[])
{
  char *path = "";
  ncio *nciop;
  char linebuf[128];
  off_t offset;
  size_t extent;
  void *vp;
  int status = ENOERR;
  int verbose = 0;
  int flags = 0;
  int create = 0;
  off_t igeto = 0;
  size_t igetsz = 0;
  size_t initialsz = 0;
  int doUnlink = 0;
  size_t sizehint = NC_SIZEHINT_DEFAULT;

  {
  extern int optind;
  extern int opterr;
  extern char *optarg;
  int ch;

  opterr = 1;

  while ((ch = getopt(ac, av, "vwcnLSUo:i:I:s:")) != EOF)
    switch (ch) {
    case 'v':
      verbose = 1;
      break;
    case 'w':
      flags |= NC_WRITE;
      break;
    case 'c':
      create = 1;
      break;
    case 'n':
      create = 1;
      flags |= NC_NOCLOBBER;
      break;
    case 'L':
      flags |= NC_LOCK;
      break;
    case 'S':
      flags |= NC_SHARE;
      break;
    case 'U':
      doUnlink = 1;
      break;
    case 'o':
      igeto = argscale(optarg, "igeto");
      break;
    case 'i':
      igetsz = argscale(optarg, "igetsz");
      break;
    case 'I':
      initialsz = argscale(optarg, "initialsz");
      break;
    case 's':
      sizehint = argscale(optarg, "sizehint");
      break;
    case '?':
      usage(av[0]);
      break;
    }

  /* last arg, the file name, is required */
  if(ac - optind <= 0)
    usage(av[0]) ;
  path = av[optind];


  }
  
  if(!create)
  {
    status = ncio_open(path, flags,
        igeto, igetsz, &sizehint,
        &nciop, &vp);
    if(status != ENOERR)
    {
      fprintf(stderr, "ncio_open: %s: %s\n",
        path, strerror(status));
      return(EXIT_FAILURE);
    }
  } else {
    status = ncio_create(path, flags, initialsz,
      igeto, igetsz, &sizehint,
      &nciop, &vp);
    if(status != ENOERR)
    {
      fprintf(stderr, "ncio_create: %s: %s\n",
        path, strerror(status));
      return(EXIT_FAILURE);
    }
  }

  while(fgets(linebuf, sizeof(linebuf), stdin) != NULL)
  {
    offset = 0;
    extent = 0;

    if(*linebuf == '#')
      continue; /* comment */
    if(sscanf(linebuf, "rel 0x%lx", &offset) == 1
      || sscanf(linebuf, "rel %ld", &offset) == 1)
    {
      if(verbose)
        printf("- rel  %8ld\n", offset);
      if(!riu_pop(offset, 0))
        continue;
      status = nciop->rel(nciop, offset, 0);
      if(status)
      {
        fprintf(stderr, "- rel  error: %s\n",
          strerror(status));
        continue;
      }
    }
    else if(sscanf(linebuf, "relm 0x%lx", &offset) == 1
      || sscanf(linebuf, "relm %ld", &offset) == 1)
    {
      if(verbose)
        printf("- relm %8ld\n", offset);
      if(!riu_pop(offset, 1))
        continue;
      status = nciop->rel(nciop, offset, RGN_MODIFIED);
      if(status)
      {
        fprintf(stderr, "- relm %8ld error: %s\n",
          offset, strerror(status));
        continue;
      }
    }
    else if(sscanf(linebuf, "get 0x%lx %ld", &offset, &extent) == 2
      || sscanf(linebuf, "get %ld %ld", &offset, &extent) == 2)
    {
      if(verbose)
        printf("- get  %10ld %8ld\n", offset, extent);
      status = nciop->get(nciop, offset, extent, 0, &vp);
      if(status)
      {
        fprintf(stderr, "- get  error: %s\n",
          strerror(status));
        continue;
      }
      riu_push(offset, extent, vp);
    }
    else if(sscanf(linebuf, "getw 0x%lx %ld", &offset, &extent) == 2
      || sscanf(linebuf, "getw %ld %ld", &offset, &extent) == 2)
    {
      if(verbose)
        printf("- getw %10ld %8ld\n", offset, extent);
      status = nciop->get(nciop, offset, extent, RGN_WRITE, &vp);
      if(status)
      {
        fprintf(stderr, "- getw  error: %s\n",
          strerror(status));
        continue;
      }
      riu_push(offset, extent, vp);
    }
    else if(strchr(linebuf, 'q') != NULL)
      break;
    else
      printf("???\n");
  }

  status = ncio_close(nciop, doUnlink);
  if(status != ENOERR)
  {
    fprintf(stderr, "ncio_close(%s): %s: %s\n",
      doUnlink ? "doUnlink" : "",
      path, strerror(status));
    return(EXIT_FAILURE);
  }

  return(EXIT_SUCCESS);
}
