/*
    TiMidity++ -- MIDI to WAVE converter and player
    Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
    Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include "timidity.h"
#include "mblock.h"
#include "zip.h"
#include "arc.h"

#define TARBLKSIZ 512
#define TARHDRSIZ 512

static long octal_value(char *s, int len);
static int tar_checksum(char *hdr);

ArchiveEntryNode *next_tar_entry(struct timiditycontext_t *c)
{
    char hdr[TARHDRSIZ];
    long size, sizeb;
    ArchiveEntryNode *entry;
    URL url;
    int flen;
    int macbin_check;

    url = c->arc_handler.url;
    macbin_check = (c->arc_handler.counter == 0);

  retry_read:
    if(!macbin_check)
      {
	if(url_read(c, url, hdr, TARHDRSIZ) != TARHDRSIZ)
	  return NULL;
      }
    else
      {
	int ch = url_getc(url);
	if(ch == 0)
	  {
	    url_skip(c, url, 127);
	    if(c->arc_handler.isfile)
	      c->arc_handler.pos += 128;
	    if(url_read(c, url, hdr, TARHDRSIZ) != TARHDRSIZ)
	      return NULL;
	  }
	else
	  {
	    hdr[0] = ch;
	    if(url_read(c, url, hdr+1, TARHDRSIZ-1) != TARHDRSIZ-1)
	      return NULL;
	  }
      }

    macbin_check = 0;

    if(hdr[0] == '\0')
      return NULL;
    if(!tar_checksum(hdr))
      return NULL;
    size = octal_value(hdr + 124, 12);
    flen = strlen(hdr);
    if(size == 0 && flen > 0 && hdr[flen - 1] == '/')
    {
	if(c->arc_handler.isfile)
	    c->arc_handler.pos += TARHDRSIZ;
	goto retry_read;
    }

    entry = new_entry_node(hdr, flen);
    if(entry == NULL)
	return NULL;
    sizeb = (((size) + (TARBLKSIZ-1)) & ~(TARBLKSIZ-1));

    if((c->arc_handler.isfile) || (size > MAX_SAFE_MALLOC_SIZE))
    {
	c->arc_handler.pos += TARHDRSIZ;
	entry->comptype = ARCHIVEC_STORED;
	entry->compsize = entry->origsize = size;
	entry->start = c->arc_handler.pos;
	url_skip(c, url, sizeb);
	c->arc_handler.pos += sizeb;
    }
    else
    {
	void *data;
	long n;

	data = url_dump(c, url, size, &n);
	if(size != n)
	{
	    if(data != NULL)
		free(data);
	    free_entry_node(entry);
	    return NULL;
	}
	entry->cache = arc_compress(c, data, size, ARC_DEFLATE_LEVEL,
				    &entry->compsize);
	free(data);
	entry->comptype = ARCHIVEC_DEFLATED;
	entry->origsize = size;
	entry->start = 0;
	url_skip(c, url, sizeb - size);
    }

    return entry;
}

static long octal_value(char *s, int len)
{
    long val;

    while(len > 0 && !isdigit((int)(unsigned char)*s))
    {
	s++;
	len--;
    }

    val = 0;
    while(len > 0 && isdigit((int)(unsigned char)*s))
    {
	val = ((val<<3) | (*s - '0'));
	s++;
	len--;
    }
    return val;
}

static int tar_checksum(char *hdr)
{
    int i;

    long recorded_sum;
    long unsigned_sum;		/* the POSIX one :-) */
    long signed_sum;		/* the Sun one :-( */

    recorded_sum = octal_value(hdr + 148, 8);
    unsigned_sum = 0;
    signed_sum = 0;
    for(i = 0; i < TARBLKSIZ; i++)
    {
	unsigned_sum += 0xFF & hdr[i];
	signed_sum   += hdr[i];
    }

    /* Adjust checksum to count the "chksum" field as blanks.  */
    for(i = 0; i < 8; i++)
    {
	unsigned_sum -= 0xFF & hdr[148 + i];
	signed_sum   -= hdr[i];
    }
    unsigned_sum += ' ' * 8;
    signed_sum   += ' ' * 8;

    return unsigned_sum == recorded_sum || signed_sum == recorded_sum;
}
