/*
    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>
#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include "timidity.h"
#include "arc.h"

#define boolean int

#ifndef FALSE
# define FALSE 0
#endif

#ifndef TRUE
# define TRUE 1
#endif

#define DELIM ('/')
#define DELIM2 (0xff)
#define LZHEADER_STRAGE 4096
#define FILENAME_LENGTH 1024

#define I_HEADER_SIZE			0
#define I_HEADER_CHECKSUM		1
#define I_METHOD			2
#define I_PACKED_SIZE			7
#define I_ORIGINAL_SIZE			11
#define I_LAST_MODIFIED_STAMP		15
#define I_ATTRIBUTE			19
#define I_HEADER_LEVEL			20
#define I_NAME_LENGTH			21
#define I_NAME				22

#define EXTEND_GENERIC		0
#define EXTEND_UNIX		'U'
#define EXTEND_MSDOS		'M'
#define EXTEND_MACOS		'm'
#define EXTEND_OS9		'9'
#define EXTEND_OS2		'2'
#define EXTEND_OS68K		'K'
#define EXTEND_OS386		'3'		/* OS-9000??? */
#define EXTEND_HUMAN		'H'
#define EXTEND_CPM		'C'
#define EXTEND_FLEX		'F'
#define EXTEND_RUNSER		'R'
/*	this OS type is not official */
#define EXTEND_TOWNSOS		'T'
#define EXTEND_XOSK		'X'

#define setup_get(PTR)		(c->lzh_get_ptr = (PTR))
#define get_byte()		(*((c->lzh_get_ptr)++) & 0xff)
#define skip_byte()		(c->lzh_get_ptr)++

static unsigned short get_word(struct timiditycontext_t *c)
{
    int b0, b1;

    b0 = get_byte();
    b1 = get_byte();
    return (b1 << 8) + b0;
}

static long get_longword(struct timiditycontext_t *c)
{
    long b0, b1, b2, b3;

    b0 = get_byte();
    b1 = get_byte();
    b2 = get_byte();
    b3 = get_byte();
    return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
}

static void msdos_to_unix_filename(char *name, int len)
{
    int i;

#ifdef MULTIBYTE_CHAR
    for(i = 0; i < len; i ++)
    {
	int c1, c2;
	c1 = (int)(unsigned char)name[i];
	c2 = (int)(unsigned char)name[i+1];
	if(MULTIBYTE_FIRST_P(c1) && MULTIBYTE_SECOND_P(c2))
	    i++;
	else if(c1 == '\\')
	    name[i] = '/';
	else if(isupper(c1))
	    name[i] = tolower(c1);
    }
#else
    for(i = 0; i < len; i ++)
    {
	int c;
	c = (int)(unsigned char)name[i];
	if(c == '\\')
	    name[i] = '/';
	else if(isupper(c))
	    name[i] = tolower(c);
    }
#endif
}

static void generic_to_unix_filename(char *name, int len)
{
    register int i;
    boolean lower_case_used = FALSE;

#ifdef MULTIBYTE_CHAR
    for(i = 0; i < len; i ++)
    {
	int c1, c2;
	c1 = (int)(unsigned char)name[i];
	c2 = (int)(unsigned char)name[i+1];
	if(MULTIBYTE_FIRST_P(c1) && MULTIBYTE_SECOND_P(c2))
	    i ++;
	else if(islower(c1))
	{
	    lower_case_used = TRUE;
	    break;
	}
    }
    for(i = 0; i < len; i ++)
    {
	int c1, c2;
	c1 = (int)(unsigned char)name[i];
	c2 = (int)(unsigned char)name[i+1];
	if(MULTIBYTE_FIRST_P(c1) && MULTIBYTE_SECOND_P(c2))
	    i++;
	else if(c1 == '\\')
	    name[i] = '/';
	else if(!lower_case_used && isupper(c1))
	    name[i] = tolower(c1);
    }
#else
    for(i = 0; i < len; i ++)
    {
	int c;
	c = (int)(unsigned char)name[i];
	if(islower(c))
	{
	    lower_case_used = TRUE;
	    break;
	}
    }
    for(i = 0; i < len; i ++)
    {
	int c;
	c = (int)(unsigned char)name[i];
	if(c == '\\')
	    name[i] = '/';
	else if(!lower_case_used && isupper(c))
	    name[i] = tolower(c);
    }
#endif
}

static void macos_to_unix_filename(char *name, int len)
{
    register int i;

    for(i = 0; i < len; i ++)
    {
	if(name[i] == ':')
	    name[i] = '/';
	else if(name[i] == '/')
	    name[i] = ':';
    }
}

#ifdef MULTIBYTE_CHAR
#define iskanji(c) (c & 0x80)
#endif /* MULTIBYTE_CHAR */
static unsigned char *convdelim(unsigned char *path, unsigned char delim)
{
    unsigned char c;
    unsigned char *p;
#ifdef MULTIBYTE_CHAR
    int kflg;

    kflg = 0;
#endif
    for(p = path; (c = *p) != 0; p++)
    {
#ifdef MULTIBYTE_CHAR
	if(kflg)
	{
	    kflg = 0;
	}
	else if(iskanji(c))
	{
	    kflg = 1;
	}
	else
#endif
	    if(c == '\\' || c == DELIM || c == DELIM2)
	    {
		*p = delim;
		path = p + 1;
	    }
    }
    return path;
}

ArchiveEntryNode *next_lzh_entry(struct timiditycontext_t *c)
{
    ArchiveEntryNode *entry;
    URL url;
    int header_size;
    char data[LZHEADER_STRAGE];
    char dirname[FILENAME_LENGTH];
    char filename[FILENAME_LENGTH];
    int dir_length, name_length;
    int i;
    char *ptr;
    int header_level;
    char method_id[5];
    long compsize, origsize;
    int extend_type;
    int hdrsiz;
    int macbin_check;
    extern char *lzh_methods[];

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

  retry_read:
    dir_length = 0;
    name_length = 0;
#if 0
    if((header_size = url_getc(url)) == EOF)
	return NULL;
    if(header_size == 0)
    {
	if(macbin_check)
	{
	    macbin_check = 0;
	    url_skip(c, url, 128-1);
	    if(c->arc_handler.isfile)
		c->arc_handler.pos += 128;
	    goto retry_read;
	}
	return NULL;
    }

    macbin_check = 0;
    if(url_read(c, url, data + I_HEADER_CHECKSUM,
		header_size - 1) < header_size - 1)
	return NULL;
#else	/* a little cleverer lzh check */
	if(macbin_check){
/*	for(i=0;i<LZHEADER_STRAGE;i++){ */
	for(i=0;i<1024;i++){
		if((header_size = url_getc(url)) == EOF)
			return NULL;
		*(data + i) = header_size;
		if(i >= 6){
			if(*(data + i - 4) == '-'
				&& *(data + i - 3) == 'l'
				&& *(data + i - 2) == 'h'
				&& *(data + i - 0) == '-')
			{
				int j;
				if(c->arc_handler.isfile)
					c->arc_handler.pos += i - 6;
				for(j = 0; j<= 6; j++)
					*(data + j) = *(data + i - 6 + j);
				header_size = (int)(unsigned char)(*(data + i - 6));
				if(header_size == 0)
					return NULL;
				if(url_read(c, url, data + 7, header_size - 7) < header_size - 7)
					return NULL;
				break;
			}
		}
	}
	if(i >= LZHEADER_STRAGE)
		return NULL;
	} else {
	    if((header_size = url_getc(url)) == EOF)
			return NULL;
		if(url_read(c, url, data + I_HEADER_CHECKSUM,
			header_size - 1) < header_size - 1)
		return NULL;
	}
    macbin_check = 0;
#endif
    hdrsiz = header_size;
    setup_get(data + I_HEADER_LEVEL);
    header_level = get_byte();

    if(header_level != 2)
    {
	if(url_read(c, url, data + header_size, 2) < 2)
	    return NULL;
	hdrsiz += 2;
    }

    setup_get(data + I_HEADER_CHECKSUM);
    skip_byte(); /* checksum */

    memcpy(method_id, data + I_METHOD, sizeof(method_id));

    setup_get(data + I_PACKED_SIZE);
    compsize = get_longword(c);
    origsize = get_longword(c);
    get_longword(c); /* last_modified_stamp */
    skip_byte(); /* attribute */

    if((header_level = get_byte()) != 2)
    {
	name_length = get_byte();
	for(i = 0; i < name_length; i ++)
	    filename[i] =(char)get_byte();
	filename[name_length] = '\0';
    }

    if(header_size - name_length >= 24)
    {				/* EXTEND FORMAT */
	get_word(c); /* crc */
	extend_type = get_byte();
    }
    else if(header_size - name_length == 22)
    {				/* Generic with CRC */
	get_word(c); /* crc */
	extend_type = EXTEND_GENERIC;
    }
    else if(header_size - name_length == 20)
    {				/* Generic no CRC */
	extend_type = EXTEND_GENERIC;
    }
    else
	return NULL;

    if(extend_type == EXTEND_UNIX && header_level == 0)
    {
	skip_byte();		/* minor_version */
	get_longword(c);		/* unix_last_modified_stamp */
	get_word(c);		/* unix_mode */
	get_word(c);		/* unix_uid */
	get_word(c);		/* unix_gid */
	goto parse_ok;
    }

    if(header_level > 0)
    {
	/* Extend Header */
	if(header_level != 2)
	    setup_get(data + header_size);
	ptr = c->lzh_get_ptr;
	while((header_size = get_word(c)) != 0)
	{
	    if(header_level != 2)
	    {
		if(data + LZHEADER_STRAGE - c->lzh_get_ptr < header_size)
		    return NULL;
		if(url_read(c, url, c->lzh_get_ptr, header_size) < header_size)
		    return NULL;
		hdrsiz += header_size;
	    }

	    switch(get_byte())
	    {
	      case 0:
		/*
		 * header crc
		 */
		setup_get(c->lzh_get_ptr + header_size - 3);
		break;
	      case 1:
		/*
		 * filename
		 */
		name_length = header_size - 3;
		if(name_length >= sizeof(filename) - 1)
		    return NULL;
		for(i = 0; i < name_length; i++)
		    filename[i] =(char)get_byte ();
		filename[name_length] = '\0';
		break;
	      case 2:
		/*
		 * directory
		 */
		dir_length = header_size - 3;
		if(dir_length >=  sizeof(dirname) - 1)
		    return NULL;
		for(i = 0; i < dir_length; i++)
		    dirname[i] = (char)get_byte ();
		dirname[dir_length] = '\0';
		convdelim((unsigned char *)dirname, DELIM);
		break;
	      case 0x40:
		/*
		 * MS-DOS attribute
		 */
		if(extend_type == EXTEND_MSDOS ||
		    extend_type == EXTEND_HUMAN ||
		    extend_type == EXTEND_GENERIC)
		    get_word(c); /* attribute */
		break;
	      case 0x50:
		/*
		 * UNIX permission
		 */
		if(extend_type == EXTEND_UNIX)
		    get_word(c); /* unix_mode */
		break;
	      case 0x51:
		/*
		 * UNIX gid and uid
		 */
		if(extend_type == EXTEND_UNIX)
		{
		    get_word(c); /* unix_gid */
		    get_word(c); /* unix_uid */
		}
		break;
	      case 0x52:
		/*
		 * UNIX group name
		 */
		setup_get(c->lzh_get_ptr + header_size - 3);
		break;
	      case 0x53:
		/*
		 * UNIX user name
		 */
		setup_get(c->lzh_get_ptr + header_size - 3);
		break;
	      case 0x54:
		/*
		 * UNIX last modified time
		 */
		if(extend_type == EXTEND_UNIX)
		    get_longword(c); /* unix_last_modified_stamp */
		break;
	      default:
		/*
		 * other headers
		 */
		setup_get(c->lzh_get_ptr + header_size - 3);
		break;
	    }
	}
	if(header_level != 2 && c->lzh_get_ptr - ptr != 2)
	{
	    compsize -= c->lzh_get_ptr - ptr - 2;
	    header_size += c->lzh_get_ptr - ptr - 2;
	}
    }

    if(dir_length)
    {
	name_length += dir_length;
	if(name_length >= sizeof(filename) - 1)
	    return NULL;
	strcat(dirname, filename);
	strcpy(filename, dirname);
    }

    switch(extend_type)
    {
      case EXTEND_MSDOS:
	msdos_to_unix_filename(filename, name_length);
      case EXTEND_HUMAN:
	; /* ignored */
	break;

#ifdef OSK
      case EXTEND_OS68K:
      case EXTEND_XOSK:
#endif
      case EXTEND_UNIX:
	break;

      case EXTEND_MACOS:
	macos_to_unix_filename(filename, name_length);
	break;

      default:
	generic_to_unix_filename(filename, name_length);
    }

  parse_ok:
    if(strncmp("-lhd-", method_id, 5) == 0)
    {
	if(c->arc_handler.isfile)
	    c->arc_handler.pos += hdrsiz;
	goto retry_read; /* Skip directory entry */
    }

    for(i = 0; lzh_methods[i]; i++)
	if(strncmp(method_id, lzh_methods[i], sizeof(method_id)) == 0)
	    break;
    if(!lzh_methods[i])
	return NULL;
    entry = new_entry_node(filename, name_length);
    if(entry == NULL)
	return NULL;
    entry->comptype = i + ARCHIVEC_LZHED + 1;
    entry->compsize = compsize;
    entry->origsize = origsize;

    if(c->arc_handler.isfile)
    {
	c->arc_handler.pos += hdrsiz;
	entry->start = c->arc_handler.pos;
	entry->cache = NULL;
	url_skip(c, url, compsize);
	c->arc_handler.pos += compsize;
    }
    else
    {
      long n;
      entry->start = 0;
      entry->cache = url_dump(c, url, compsize, &n);
      if(n != compsize)
	{
	  free_entry_node(entry);
	  return NULL;
	}
    }
    return entry;
}
