/*****************************************************************************\
**                                                                           **
** PBX4Linux                                                                 **
**                                                                           **
**---------------------------------------------------------------------------**
** Copyright: Andreas Eversberg                                              **
**                                                                           **
** opening and reading tone                                                  **
**                                                                           **
\*****************************************************************************/ 

#include "main.h"

/* 
notes about codecs:

CODEC_OFF is a none accepted value
CODEC_LAW is 8 bit (1 byte) data 8khz
other codecs are 16 bit (2 bytes) data 8khz
the read_tone() will return law or 16bit mono. the read_tone will convert all other formats to 16bit mono.

*/ 


/*
 * open the tone (don't increase fhuse, since it is done after calling this function)
 * NOTE: length and left will be set to the number of samples, NOT bytes
 */
struct fmt {
	unsigned short  stereo; /* 1 = pcm, 2 = adpcm */
	unsigned short  channels; /* number of channels */
	unsigned int   sample_rate; /* sample rate */
	unsigned int   data_rate; /* data rate */
	unsigned short  bytes_sample; /* bytes per sample (all channels) */
	unsigned short  bits_sample; /* bits per sample (one channel) */
};
int open_tone(char *file, int *codec, signed int *length, signed int *left)
{
	int fh;
	char filename[256];        
	char linkname[256];        
	unsigned char buffer[256];
	struct fmt *fmt;
	int channels, bytes;
	unsigned int size, chunk;
	int gotfmt = 0;
	struct stat _stat;
	int linksize;
	int l;
	char *p;


	/* try to open the law file */
	SPRINT(filename, "%s.isdn", file);
	if ((fh = open(filename, O_RDONLY)) >= 0)
	{
		/* stat tone */
		l = 0;
		while(42)
		{
			if (l >= 10)
			{
				close(fh);
				PERROR("Link chain too deep: '%s'\n", filename);
				return(-1);
			}
			if (lstat(filename, &_stat) == -1)
			{
				close(fh);
				PERROR("Cannot stat file: '%s'\n", filename);
				return(-1);
			}
			if (!S_ISLNK(_stat.st_mode))
			{
				break;
			}
			if ((linksize=readlink(filename, linkname, sizeof(linkname))) > 0)
			{
				linkname[linksize] = '\0';
			} else
			{
				close(fh);
				PERROR("Cannot read link information: '%s'\n", filename);
				return(-1);
			}
			if (linkname[0] == '/') /* absolute link */
			{
				SCPY(filename, linkname);
			} else /* relative link */
			{
				/* remove filename */
				p = filename;
				while(strchr(p, '/'))
				{
					p = strchr(p, '/')+1;
				}
				*p = 0;
				/* concat the link */
				SCAT(filename, linkname);
			}
//printf("follow link: %s\n", filename);
			l++;
		}
		if (length)
			*length = _stat.st_size;
		if (left)
			*left = _stat.st_size;
		if (codec)
			*codec = CODEC_LAW;
		return(fh);
	}

	/* try to open the wave file */
	SPRINT(filename, "%s.wav", file);
	if ((fh = open(filename, O_RDONLY)) >= 0)
	{
		/* get wave header */
		read(fh, buffer, 8);
		size=(buffer[4]) + (buffer[5]<<8) + (buffer[6]<<16) + (buffer[7]<<24);
		if (!!strncmp((char *)buffer, "RIFF", 4))
		{
			close(fh);
			errno = 0;
			PERROR("%s is no riff file!\n", filename);
			return(-1);
		}
//		printf("%c%c%c%c size=%ld\n",buffer[0],buffer[1],buffer[2],buffer[3],size);
		read(fh, buffer, 4);
		size -= 4;
		if (!!strncmp((char *)buffer, "WAVE", 4))
		{
			close(fh);
			errno = 0;
			PERROR("%s is no wave file!\n", filename);
			return(-1);
		}
		while(size)
		{
			if (size>0 && size<8)
			{
				close(fh);
				errno = 0;
				PERROR("Remaining file size %ld not large enough for next chunk.\n",size);
				return(-1);
			}
			read(fh, buffer, 8);
			chunk=(buffer[4]) + (buffer[5]<<8) + (buffer[6]<<16) + (buffer[7]<<24);
			size -= (8+chunk);
//			printf("%c%c%c%c length=%d\n",buffer[0],buffer[1],buffer[2],buffer[3],chunk);
			if (size < 0)
			{
				close(fh);
				errno = 0;
				PERROR("Chunk '%c%c%c%c' is larger than remainig file size (length=%ld)\n",buffer[0],buffer[1],buffer[2],buffer[3], chunk);
				return(-1);
			}
			if (!strncmp((char *)buffer, "fmt ", 4))
			{
				if (chunk < 16)
				{
					close(fh);
					errno = 0;
					PERROR("File %s Fmt chunk illegal size.\n", filename);
					return(-1);
				}
				read(fh, buffer, chunk);
				fmt = (struct fmt *)buffer;
				if (fmt->channels<1 || fmt->channels>2)
				{
					close(fh);
					errno = 0;
					PERROR("File %s Only support one or two channels file.\n", filename);
					return(-1);
				}
				channels = fmt->channels;
//				printf("Channels: %d\n", channels);
				if (fmt->sample_rate != 8000)
				{
					PERROR("Warning: File %s has sample rate of %ld.\n", filename, fmt->sample_rate);
				}
//				printf("Sample Rate: %ld\n", fmt->sample_rate);
				if (fmt->bits_sample!=8 && fmt->bits_sample!=16)
				{
					close(fh);
					errno = 0;
					PERROR("File %s has neigher 8 nor 16 bit samples.\n", filename);
					return(-1);
				}
				bytes = (fmt->bits_sample==16)?2:1;
//				printf("Bit-Resolution: %d\n", bytes*16-16);
				gotfmt = 1;
			} else
			if (!strncmp((char *)buffer, "data", 4))
			{
				if (!gotfmt)
				{
					close(fh);
					errno = 0;
					PERROR("File %s No fmt chunk found before data chunk.\n", filename);
					return(-1);
				}
//				printf("Length: %ld samples (%ld.%03ld seconds)\n", chunk/bytes/channels, chunk/bytes/channels/8000, ((chunk/bytes/channels)%8000)*1000/8000);
				if (bytes==2 && channels==1)
				{
					if (codec)
						*codec = CODEC_MONO;
					if (length)
						*length = ((signed int)chunk)>>1;
					if (left)
						*left = ((signed int)chunk)>>1;
//					printf("left=%d\n",*left);
				} else
				if (bytes==2 && channels==2)
				{
					if (codec)
						*codec = CODEC_STEREO;
					if (length)
						*length = ((signed int)chunk)>>2;
					if (left)
						*left = ((signed int)chunk)>>2;
				} else
				if (bytes==1 && channels==1)
				{
					if (codec)
						*codec = CODEC_8BIT;
					if (length)
						*length = (signed int)chunk;
					if (left)
						*left = (signed int)chunk;
				} else
				{
					close(fh);
					errno = 0;
					PERROR("File %s Is not MONO8, MONO16 nor STEREO16.\n", filename);
					return(-1);
				}
				return(fh);
			} else
			{
//				PDEBUG(DEBUG_PORT, "Unknown chunk '%c%c%c%c'\n",buffer[0],buffer[1],buffer[2],buffer[3]);
				while(chunk > sizeof(buffer))
				{
					read(fh, buffer, sizeof(buffer));
					chunk -=  sizeof(buffer);
				}
				if (chunk)
					read(fh, buffer, chunk);
			}
			
		}
		if (!gotfmt)
		{
			close(fh);
			errno = 0;
			PERROR("File %s No fmt chunk found in file.\n", filename);
			return(-1);
		}
		close(fh);
		errno = 0;
		PERROR("File %s No data chunk found in file.\n", filename);
		return(-1);
	}

	return(-1);
}


/*
 * read from tone, check size
 * the len must be the number of samples, NOT for the bytes to read!!
 * the data returned is law-code
 */
int read_tone(int fh, unsigned char *buffer, int codec, int len, signed int size, signed int *left, int speed)
{
	int l;
	int offset;
	signed short buffer16[len], *buf16 = buffer16;
	signed short buffer32[len<<1], *buf32 = buffer32;
	unsigned char buffer8[len], *buf8 = buffer8;
	signed int sample;
	int i = 0;
//printf("left=%ld\n",*left);

	/* if no *left is given (law has unknown length) */
	if (!left)
		goto unknown_length;

	if (speed!=1)
	{
		offset = ((len&(~4)) * (speed-1));
		lseek(fh, offset, SEEK_CUR); /* step fowards, backwards (len must be round to 4 bytes, to be sure, that 16bit stereo will not drift out of sync)*/
		*left -= offset; /* correct the current bytes left */
		if (*left < 0)
		{
			/* eof */
			*left = 0;
			return(0);
		}
		if (*left >= size)
		{
			/* eof */
			*left = size;
			return(0);
		}
	}

	if (*left == 0)
		return(0);

	if (*left < len)
		len = *left;
	unknown_length:
	switch(codec)
	{
		case CODEC_LAW:
		l = read(fh, buffer, len); /* as is */
		break;

		case CODEC_MONO:
			l = read(fh, buf16, len<<1);
			if (l>0)
			{
				l = l>>1;
				while(i < l)
				{
					sample = *buf16++;
					if (sample < -32767)
						sample = -32767;
					if (sample > 32767)
						sample = 32767;
					*buffer++ = audio_s16_to_law[sample & 0xffff];
					i++;
				}
			}
		break;

		case CODEC_STEREO:
		{
			l = read(fh, buf32, len<<2);
			if (l>0)
			{
				l = l>>2;
				while(i < l)
				{
					sample = (*buf32++);
					sample += (*buf32++);
					if (sample < -32767)
						sample = -32767;
					if (sample > 32767)
						sample = 32767;
					*buffer++ = audio_s16_to_law[sample & 0xffff];
					i++;
				}
			}
		}
		break;

		case CODEC_8BIT:
		{
			l = read(fh, buf8, len);
			if (l>0)
			{
				while(i < l)
				{
					*buffer++ = audio_s16_to_law[(((*buf8++)<<8)-0x8000) & 0xffff];
					i++;
				}
			}
		}
		break;

		default:
		FATAL("codec %d is not supported.\n", codec);
	}

	if (l>0 && left)
		*left -= l;
	return(l);
}


struct toneset *toneset_first = NULL;

/*
 * free fetched tones
 */
void free_tones(void)
{
	struct toneset *toneset_temp;
	struct tonesettone *tonesettone_temp;
	void *temp;

	toneset_temp = toneset_first;
	while(toneset_temp)
	{
		tonesettone_temp = toneset_temp->first;
		while(tonesettone_temp)
		{
			temp = tonesettone_temp;
			tonesettone_temp = tonesettone_temp->next;
			FREE(temp, sizeof(struct tonesettone));
			memuse--;
		}
		temp = toneset_temp;
		toneset_temp = toneset_temp->next;
		FREE(temp, sizeof(struct toneset));
		memuse--;
	}
	toneset_first = NULL;
}

/*
 * fetch tones as specified in options.conf
 */
int fetch_tones(void)
{
	DIR *dir;
	struct dirent *dirent;
	struct toneset **toneset_nextpointer;
	struct tonesettone **tonesettone_nextpointer;
	char *p, *p_next;
	char path[256];
	char filename[256], name[256];
	int fh;
	int tone_codec;
	signed int tone_size, tone_left;
	unsigned int memory = 0;
	int samples = 0;

	/* if disabled */
	if (!options.fetch_tones)
		return(1);

	toneset_nextpointer = &toneset_first;
	p = options.fetch_tones;
	if (*p == '\0')
		return(1);

	while (*p)
	{
		p_next = p;
		while(*p_next)
		{
			if (*p_next == ',')
			{
				*p_next = '\0';
				p_next++;
				break;
			}
			p_next++;
		}

		/* remove trailing / */
		if (*p) if (p[strlen(p)-1] == '/')
			p[strlen(p)-1] = '\0';

		printf("PBX: Fetching tones '%s'\n", p);
		PDEBUG(DEBUG_PORT, "fetching tones directory '%s'\n", p);

		*toneset_nextpointer = (struct toneset *)MALLOC(sizeof(struct toneset));
		memuse++;
		memory += sizeof(struct toneset);
		SCPY((*toneset_nextpointer)->directory, p);
		tonesettone_nextpointer = &(*toneset_nextpointer)->first;

		SPRINT(path, "%s/%s", SHARE_DATA, p);
		dir = opendir(path);
		if (dir == NULL)
		{
			PERROR("Tone set not found: '%s'\n", path);
			return(0);
		}

		while((dirent=readdir(dir)))
		{
			SPRINT(name, "%s", dirent->d_name);

			/* remove .isdn and .wave */
			if (strlen(name) >= 4)
			{
				if (!strcmp(name+strlen(name)-4, ".wav"))
					name[strlen(name)-4] = '\0';
			}
			if (strlen(name) >= 5)
			{
				if (!strcmp(name+strlen(name)-5, ".isdn"))
					name[strlen(name)-5] = '\0';
			}

			SPRINT(filename, "%s/%s", path, name);

			/* skip . / .. */
			if (!strcmp(dirent->d_name, "."))
				continue;
			if (!strcmp(dirent->d_name, ".."))
				continue;

			/* open file */
			fh = open_tone(filename, &tone_codec, &tone_size, &tone_left);
			if (fh < 0)
			{
				PERROR("Cannot open file: '%s'\n", filename);
				continue;
			}
			fduse++;

			if (tone_size < 0)
			{
				PERROR("File has 0-length: '%s'\n", filename);
				close(fh);
				fduse--;
				continue;
			}

			/* Allocate tone */
			*tonesettone_nextpointer = (struct tonesettone *)MALLOC(sizeof(struct tonesettone)+tone_size);
			memuse++;
//printf("tone:%s, %ld bytes\n", name, tone_size);
			memory += sizeof(struct tonesettone)+tone_size;
			samples ++;

			/* load tone */
			read_tone(fh, (*tonesettone_nextpointer)->data, tone_codec, tone_size, tone_size, &tone_left, 1);
			(*tonesettone_nextpointer)->size = tone_size;
			(*tonesettone_nextpointer)->codec = (tone_codec==CODEC_LAW)?CODEC_LAW:CODEC_MONO;
			SCPY((*tonesettone_nextpointer)->name, name);

			close(fh);
			fduse--;
				 
			tonesettone_nextpointer = &((*tonesettone_nextpointer)->next);
		}

		toneset_nextpointer = &((*toneset_nextpointer)->next);
		p = p_next;
	}

	printf("PBX: Memory used for tones: %d bytes (%d samples)\n", memory, samples);
	PDEBUG(DEBUG_PORT, "Memory used for tones: %ld bytes (%d samples)\n", memory, samples);

	return(1);
} 


/*
 * opens the fetched tone (if available)
 */
void *open_tone_fetched(char *dir, char *file, int *codec, signed int *length, signed int *left)
{
	struct toneset *toneset;
	struct tonesettone *tonesettone;

	/* if anything fetched */
	if (!toneset_first)
		return(NULL);

	/* find set */
	toneset = toneset_first;
	while(toneset)
	{
//printf("1. comparing '%s' with '%s'\n", toneset->directory, dir);
		if (!strcmp(toneset->directory, dir))
			break;
		toneset = toneset->next;
	}
	if (!toneset)
		return(NULL);

	/* find tone */
	tonesettone = toneset->first;
	while(tonesettone)
	{
//printf("2. comparing '%s' with '%s'\n", tonesettone->name, file);
		if (!strcmp(tonesettone->name, file))
			break;
		tonesettone = tonesettone->next;
	}
	if (!tonesettone)
		return(NULL);

	/* return information */
	if (length)
		*length = tonesettone->size;
	if (left)
		*left = tonesettone->size;
	if (codec)
		*codec = tonesettone->codec;
//printf("size=%ld, data=%08x\n", tonesettone->size, tonesettone->data);
	return(tonesettone->data);
}


/*
 * read from fetched tone, check size
 * the len must be the number of samples, NOT for the bytes to read!!
 */
int read_tone_fetched(void **fetched, void *buffer, int len, signed int size, signed int *left, int speed)
{
	int l;
//printf("left=%ld\n",*left);

	/* if no *left is given (law has unknown length) */
	if (!left)
		return(0);

	if (*left == 0)
		return(0);

	if (*left < len)
		len = *left;

	memcpy(buffer, *fetched, len);
	*((char **)fetched) += len;
	l = len;

	if (l>0 && left)
		*left -= l;
	return(l);
}

