/*
Copyright (c) 1998-2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/



/**	@file	tape.c	The tape program.
*/




/* ********************************************************************* */
/* *                                                                   * */
/* * tape sn <setfile>                                                 * */
/* * tape sc <setfile> [<comments>]                                    * */
/* * tape sl <setfile>                                                 * */
/* *                                                                   * */
/* * tape tn <tapefile>                                                * */
/* * tape tc <tapefile> [comments]                                     * */
/* * tape tl <tapefile>                                                * */
/* *                                                                   * */
/* ********************************************************************* */

#line 53 "tape.ctr"




/**	Backup set data.
*/
typedef struct {
  char line[2048];	/**< Text line. */
  char *setNames[1024];	/**< All the set names. */
  int used;		/**< Currently used set. */
  int confirmed;	/**< Last confirmed set. */
} SetInfo;



/**	Backup tape data.
*/
typedef struct {	
  unsigned used;	/**< Number of uses. */
  char comment[32];	/**< Comment for last backup to this tape. */
} TapeInfo;



/**	Backup tapes data.
*/
typedef struct {
  TapeInfo tapeInfo[10];	/**< Information for all ten tapes. */
  int tapeDay;			/**< Number of current backup. */
  int confirmed;		/**< Number of last confirmed backup. */
} TapeSetInfo;



#include <dk.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#if DK_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#include <dk.h>
#include <dksf.h>


#line 100 "tape.ctr"




/**	Initialize tape set information.
	@param	info	Tape set information to initialize.
*/
static
void
tapeSetInit DK_P1(TapeSetInfo *,info)
{
  TapeInfo *ti;
  int i;

  info->tapeDay = info->confirmed = 0;
  ti = &(info->tapeInfo[0]);
  for(i = 0; i < 10; i++) {
    strcpy(ti->comment, "-------------------");
    ti->used = 0;
    ti++;
  }
}



/**	Read tape set information from file.
	@param	info	Tape set information.
	@param	filename	File name.
	@return	1 on success, 0 on error.
*/
static
int
tapeSetRead DK_P2(TapeSetInfo *,info, char *,filename)
{
  int back = 0;
  FILE *f;
  char line[1024];
  int i;
  TapeInfo *ti;

  tapeSetInit(info);
  if((f = dksf_fopen(filename, "r")) != NULL) {
    if(fgets(line, 1024, f)) {
      switch(sscanf(line, "%d %d", &(info->tapeDay), &(info->confirmed))) {
        case 0: {
          info->tapeDay = 0;
	}
	case 1: {
          info->confirmed = 0;
	} break;
      }
      i = 0; ti = &(info->tapeInfo[0]); back = 1;
      while(i < 10) {
        if(fgets(line, 1024, f)) {
          sscanf(line, "%s %u", &(ti->comment[0]), &(ti->used));
	  i++; ti++;
	} else {
          i = 10; back = 0;
	}
      }
    } else {

    }
  } else {
    back = 1;
  }
  return back;
}



/**	Write tape set information to file.
	@param	info	Tape set to write.
	@param	filename	File name.
	@return	1 on success, 0 on error.
*/
static
int
tapeSetWrite DK_P2(TapeSetInfo *,info, char *,filename)
{
  int back = 0, i = 0;
  FILE *f;
  TapeInfo *ti;

  if((f = dksf_fopen(filename, "w")) != NULL) {
    back = 1;
    fprintf(f, "%d %d\n", info->tapeDay, info->confirmed);
    ti = &(info->tapeInfo[0]);
    for(i = 0; i < 10; i++) {
      fprintf(f, "%s %u\n", &(ti->comment[0]), ti->used);
      ti++;
    }
    fclose(f);
  } else {
    fprintf(stderr,
    "ERROR: tape - Failed to write tape information file %s",
    filename
    );
    fflush(stderr);
  }
  return back;
}



/**	Read tape set information from file.
	@param	set		Tape set information record.
	@param	filename	Name of file to read.
	@return	1 on success, 0 on error.
*/
static
int
setRead DK_P2(SetInfo *,set, char *,filename)
{
  int back = 0;
  FILE *f;
  char myline[1024];
  char **ptr, *xptr;
  int lastWasText = 0, i = 0;
  
  f = dksf_fopen(filename, "r");
  if(f) { 
    if(fgets(&(set->line[0]), 2047, f)) { 
      back = 1; set->used = set->confirmed = 0;
      if(fgets(myline, 1024, f)) { 
        switch(sscanf(myline, "%d %d", &(set->used), &(set->confirmed))) {
          case 0: {
            set->confirmed = set->used = 0;
	  } break;
	  case 1: {
            set->confirmed = 0;
	  } break;
	} 
      } else { 
        set->confirmed = 0;
	set->used = 0;
      } 
      ptr = &(set->setNames[0]);
      for(i = 0; i < 1024; i++) ptr[i] = NULL;
      lastWasText = 0;
      xptr = &(set->line[0]); 
      while(*xptr) {
        if((*xptr == ' ') || (*xptr == '\t') || (*xptr == '\n')) {
          if(lastWasText) {
            *xptr = '\0';
	  }
	  lastWasText = 0;
	} else {
          if(lastWasText == 0) {
            *(ptr++) = xptr;
	  }
	  lastWasText = 1;
	}
	xptr++;
      } 
    }
    fclose(f);
  } 
  return back;
}



/**	Write new tape set info to file.
	@param	set	Tape set information.
	@param	filename	File name to write.
	@return	1 on success, 0 on error.
*/
static
int
setWrite DK_P2(SetInfo *,set, char *,filename)
{
  int back = 0;
  FILE *f;
  char **ptr;
  
  if((f = dksf_fopen(filename, "w")) != NULL) {
    ptr = &(set->setNames[0]);
    while(*ptr) fprintf(f, "%s ", *(ptr++));
    fprintf(f, "\n%d %d\n", set->used, set->confirmed);
    fflush(f);
    fclose(f);
    back = 1;
  } 
  return back;
}



/**	Get name of current tape set.
	@param	filename	File name containing tape set information.
	@return	1 on success, 0 on error.
*/
static
int
tapeSetName DK_P1(char *,filename)
{
  int back = 0, newtape = 0, rewrite = 0;
  SetInfo set;
  int setaz;
  char **ptr;
  
  if(setRead(&set, filename)) {
    ptr = &(set.setNames[0]); setaz = 0;
    while(*ptr) {
      setaz++; 
      ptr++;
    }
    if(setaz > 0) { 
      newtape = set.used;
      if(set.confirmed) { 
        newtape++; rewrite = 1; set.confirmed = 0; 
      }
      newtape = newtape % setaz;
      if(rewrite) set.used = newtape;
      ptr = &(set.setNames[0]);
      if(rewrite) { 
        if(setWrite(&set, filename)) {
          printf("%s\n", ptr[newtape]);
	  fflush(stdout);
	} else { 
          fprintf(stderr,
	    "ERROR: tape - Failed to write to %s!\n", filename
	  );
	  fflush(stdout);
	}
      } else { 
        printf("%s\n", ptr[newtape]);
	fflush(stdout);
      }
    } else {
      fprintf(stderr, "ERROR: tape - No set names defined!\n");
      fflush(stderr);
    }
  } else {
    fprintf(stderr, 
      "ERROR: tape - Failed to read tape set file %s!\n",
      filename
    );
    fflush(stdout);
  } 
  return back;
}



/**	Confirm backup to current tape set.
	@param	filename	Name of file containing the tape information.
	@return	1 on success, 0 on error.
*/
static
int
tapeSetConfirm DK_P1(char *,filename)
{
  int back = 0;
  SetInfo set;
  
  if(setRead(&set, filename)) {
    set.confirmed = 1;
    if(setWrite(&set, filename) == 0) {
      fprintf(stderr,
        "ERROR: tape - Failed to write tape set file %s!\n",
        filename
      );
      fflush(stderr);
    }
  } else {
    fprintf(stderr, 
      "ERROR: tape - Failed to read tape set file %s!\n",
      filename
    );
    fflush(stderr);
  }
  return back;
}



/**	List the tape sets.
	@param	filename	File name containing information about the
	tape sets.
	@return	1 on success, 0 on error.
*/
static
int
tapeSetList DK_P1(char *,filename)
{
  int back = 0, i = 0;
  char **ptr; int setaz;
  SetInfo set;
  if(setRead(&set, filename)) {
    ptr = &(set.setNames[0]); setaz = 0;
    while(*ptr++) setaz++;
    ptr = &(set.setNames[0]);
    for(i = 0; i < setaz; i++) {
      if(i == set.used) {
        if(set.confirmed) {
          printf("+ ");
	} else {
          printf("- ");
	}
      } else {
        printf("  ");
      }
      printf("%s\n", ptr[i]);
    }
  } else {
    fprintf(stderr, "ERROR: tape - Failed to read file %s!\n",
      filename
    );
    fflush(stderr);
  }
  return back;
}




/**	Tape numbers in order of usage.
	This implements the 10 tape rotation scheme.
	At every time we will have backups of different ages.
	During 200 backups each of the 10 tapes is used 20 times.
*/
static int tapeNumbers[] = {
  1, 2, 3, 4, 5,   1, 2, 3, 4, 6,   1, 2, 3, 4, 7,   1, 2, 3, 4, 8,
  2, 3, 4, 5, 6,   2, 3, 4, 5, 7,   2, 3, 4, 5, 8,   2, 3, 4, 5, 9,
  3, 4, 5, 6, 7,   3, 4, 5, 6, 8,   3, 4, 5, 6, 9,   3, 4, 5, 6, 10,
  4, 5, 6, 7, 8,   4, 5, 6, 7, 9,   4, 5, 6, 7, 10,  4, 5, 6, 7, 1,
  5, 6, 7, 8, 9,   5, 6, 7, 8, 10,  5, 6, 7, 8, 1,   5, 6, 7, 8, 2,
  6, 7, 8, 9, 10,  6, 7, 8, 9, 1,   6, 7, 8, 9, 2,   6, 7, 8, 9, 3,
  7, 8, 9, 10, 1,  7, 8, 9, 10, 2,  7, 8, 9, 10, 3,  7, 8, 9, 10, 4,
  8, 9, 10, 1, 2,  8, 9, 10, 1, 3,  8, 9, 10, 1, 4,  8, 9, 10, 1, 5,
  9, 10, 1, 2, 3,  9, 10, 1, 2, 4,  9, 10, 1, 2, 5,  9, 10, 1, 2, 6,
  10, 1, 2, 3, 4,  10, 1, 2, 3, 5,  10, 1, 2, 3, 6,  10, 1, 2, 3, 7
} ;



/**	Number of elements in \a tapeNumbers array.
*/
static int tapeNoAz = (int)(sizeof(tapeNumbers) / sizeof(int));



/**	Get tape number for current backup.
	@param	filename	File name for tape information.
	@return	1 on success, 0 on error.
*/
static
int
tapeNumber DK_P1(char *,filename)
{
  int back = 0;
  TapeSetInfo tsi;

  if(tapeSetRead(&tsi, filename)) {
    if(tsi.confirmed) {
      tsi.tapeDay += 1;
      tsi.tapeDay = tsi.tapeDay % tapeNoAz;
      tsi.confirmed = 0;
      if(tapeSetWrite(&tsi, filename)) {
        printf("%d\n", tapeNumbers[(tsi.tapeDay % tapeNoAz)]);
	back = 1;
      } else {
        fprintf(stderr,
	"ERROR: tape - Failed to write tape information file %s!\n",
	filename);
	fflush(stderr);
      }
    } else {
      printf("%d\n", tapeNumbers[(tsi.tapeDay % tapeNoAz)]);
      back = 1;
    }
  } else {
    fprintf(stderr,
    "ERROR: tape - Failed to read tape information file %s!\n",
    filename
    );
    fflush(stderr);
  }
  return back;
}



/**	Confirm one tape.
	@param	filename	File name with tape information.
	@return	1 on success, 0 on error.
*/
static
int
tapeConfirm DK_P1(char *,filename)
{
  int back = 0;
  int nr = 0;
  TapeSetInfo tsi;
  TapeInfo *ti;

  time_t timer;
  struct tm *tmx;

  if(tapeSetRead(&tsi, filename)) {
    nr = tapeNumbers[(tsi.tapeDay % tapeNoAz)] - 1;
    ti = &(tsi.tapeInfo[nr]);
    tsi.confirmed = 1;
    time(&timer);
    tmx = localtime(&timer);
    sprintf(
      &(ti->comment[0]),
       "%04d-%02d-%02d_%02d:%02d:%02d",
	(tmx->tm_year + 1900), (1 + tmx->tm_mon), tmx->tm_mday,
        tmx->tm_hour, tmx->tm_min, tmx->tm_sec
    );
    ti->used += 1;
    /*
    tsi.confirmed = 1;
    time(&timer);
    tmx = localtime(&timer);
    nr = tapeNumbers[(tsi.tapeDay % tapeNoAz)];
    if(tmx) {
      sprintf(&((tsi.tapeInfo[nr]).comment[0]),
        "%04d-%02d-%02d %02d:%02d:%02d",
	(tmx->tm_year + 1900), (1 + tmx->tm_mon), tmx->tm_mday,
        tmx->tm_hour, tmx->tm_min, tmx->tm_sec
      );
    }
    (tsi.tapeInfo[nr]).used += 1;
    */
    if(tapeSetWrite(&tsi, filename)) {
      back = 1;
    } else {
      fprintf(stderr,
      "ERROR: tape - Failed to write tape information file %s!\n",
      filename
      );
      fflush(stderr);
    }
  } else {
    fprintf(stderr,
    "ERROR: tape - Failed to read tape information file %s!\n",
    filename);
    fflush(stderr);
  }
  return back;
}



/**	List tapes.
	@param	filename	File name containing tape information.
	@return	1 on success, 0 on error.
*/
static
int
tapeList DK_P1(char *,filename)
{
  int back = 0;
  int i = 0;
  TapeInfo *ti;
  TapeSetInfo tsi;

  ti = &(tsi.tapeInfo[0]);
  if(tapeSetRead(&tsi, filename)) {
    back = 1;
    for(i = 0; i < 10; i++) {
      if(i == (tapeNumbers[(tsi.tapeDay % tapeNoAz)] - 1)) {
        if(tsi.confirmed) {
          printf("+ ");
	} else {
          printf("- ");
	}
      } else {
        printf("  ");
      }
      printf("%s (%d) %5u\n", &(ti->comment[0]), (i+1), ti->used);
      ti++;
    }
  } else {
    fprintf(stderr,
    "ERROR: tape - Failed to read tape information file %s!\n",
    filename);
    fflush(stderr);
  }
  return back;
}



/**	Help text.
*/
static char *help_text[] = {
"",
"tape <command> <file> [<comments>]",
"==================================",
"tape sn <setfile>                  retrieves the name of the next tape set.",
"tape sc <setfile> [<comments>]     confirms the current tape set.",
"tape sl <setfile>                  lists the tape sets.",
"tape tn <tapefile>                 retrieves the number of the next tape.",
"tape tc <tapefile> [<comments>]    confirms writing to the current tape.",
"type tl <tapefile>                 lists the tapes.",
"",
NULL
};



/**	Print short help text.
*/
static
void
usage DK_P0()
{
  char **ptr;
  ptr = help_text;
  while(*ptr) {
    fputs(*(ptr++), stdout); fputc('\n', stdout);
  }
}



/**	The main() function of the tape program.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0 on success, any other value indicates an error.
*/
#if DK_HAVE_PROTOTYPES
int main(int argc, char *argv[])
#else
int main(argc, argv) int argc; char *argv[];
#endif
{
  int setOrTape = 0;
  int action = 0;
  int exval = 0;
  char *ptr;
  
#line 636 "tape.ctr"

  if(argc >= 3) {
    ptr = argv[1];
    while(*ptr != '\0') {
      switch(*ptr) {
        case 't':
	case 'T': {
          setOrTape = 1;
	} break;
	case 's':
	case 'S': {
          setOrTape = 0;
	} break;
	case 'n':
	case 'N': {
          action = 0;
	} break;
	case 'c':
	case 'C': {
          action = 1;
	} break;
	case 'l':
	case 'L': {
          action = 2;
	} break;
      }
      ptr++;
    }
    switch((3 * setOrTape) + action) {
      case 0: {
        exval = tapeSetName(argv[2]);
      } break;
      case 1: {
        exval = tapeSetConfirm(argv[2]);
      } break;
      case 2: {
        exval = tapeSetList(argv[2]);
      } break;
      case 3: {
        exval = tapeNumber(argv[2]);
      } break;
      case 4: {
        exval = tapeConfirm(argv[2]);
      } break;
      case 5: {
        exval = tapeList(argv[2]);
      } break;
    }
  } else {
    usage();
  }
  exval = (exval ? 0 : 1); 
#line 687 "tape.ctr"

  exit(exval); return exval;
}



