/*********************************************************************
 *     file.c - dsp patch manager library - patch file reading functions
 * 
 *      Copyright (C) 2000 Rui Sousa 
 ********************************************************************* 
 *     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., 675 Mass Ave, Cambridge, MA 02139, 
 *     USA. 
 ********************************************************************* 
*/

#include <stdio.h>
#include "../include/dsp.h"
#include "../include/list.h"
#include <stdlib.h>
#include "../include/internal.h"

extern __u32 addr_change[0x400];


#define FILE_HEAD "emu10k1-dsp-file"
#define FILE_FORMAT_VERSION 1

/* gives an increasing id to patches with the same name */
static int set_patch_id(struct dsp_patch_manager *mgr, struct dsp_patch *patch)
{
	struct dsp_patch *patch1;
	struct list_head *entry;
	int table[255];

	patch->id = 0;


	list_for_each(entry, &mgr->ipatch_list) {
		patch1 = list_entry(entry, struct dsp_patch, list);

		if (!strcmp(patch1->name, patch->name)) {
			table[patch1->id] = 1;
			while (table[patch->id] == 1) {
				patch->id++;
				if (patch->id == 255)
					return -1;
			}
		}
		}

	
	list_for_each(entry, &mgr->opatch_list) {
		patch1 = list_entry(entry, struct dsp_patch, list);

		if (!strcmp(patch1->name, patch->name)) {
			table[patch1->id] = 1;
			while (table[patch->id] == 1) {
				patch->id++;
				if (patch->id == 255)
					return -1;
			}
		}
		}

	return 0;
}



static int read_patch_code(struct dsp_patch_manager *mgr, struct dsp_patch *patch, FILE * fp)
{
	int i;
	__u32 new_op, operand;

	patch->code = (__u32 *) malloc(patch->code_size * sizeof(__u32));

	if (patch->code == NULL) {
		fprintf(stderr, "read_patch_code(): no memory available\n");
		return -1;
	}

	fread(patch->code, sizeof(__u32), patch->code_size, fp);

	if(IS_EMU10K1)
		return 0;

	// for audigy cards we need to translate the instructions
	for(i=0;i<patch->code_size;i++){
		new_op=(patch->code[i]&0x00f00000)<<4;
		new_op|=(operand=(patch->code[i]&0x3ff))&0x40?operand+0x80:operand;
		patch->code[i]=new_op|((operand=(patch->code[i]&0xffc00)<<2)&0x40000?operand+0x80000:operand);
	}
	return 0;
}

/* FIXME */
/* Still missing the ALIGN and CLEAR bits */
static inline void read_tram_lines(__u32 tramb_start, __u32 * traml_addr,
				   __u8 * traml_size, __u16 base, __u32 type, FILE * fp)
{
	int i;
	__u8 lines;
	__u8 data_addr;
	__u32 addr_val;

	fread(&lines, sizeof(__u8), 1, fp);
	for (i = 0; i < lines; i++) {
		fread(&data_addr, sizeof(__u8), 1, fp);

		addr_change[TRAML_IDATA_BASE - GPR_BASE + data_addr] = base + *traml_size;

		addr_change[TRAML_IADDR_BASE - GPR_BASE + data_addr] =
		    base + (TRAML_IADDR_BASE - TRAML_IDATA_BASE) + *traml_size;

		fread(&addr_val, sizeof(__u32), 1, fp);
		
		traml_addr[*traml_size] = ((tramb_start + addr_val) & 0xfffff) | (type << 20);
		(*traml_size)++;
	}
}

/* FIXME restore tramb_?start on failure */
static int read_tram_block(struct dsp_patch_manager *mgr, struct dsp_patch *patch, __u32 * traml_iaddr,
			   __u32 * traml_eaddr, FILE * fp)
{
	int i;
	__u8 blocks;
	__u32 size;
	__u8 tmp;
	__u32 type;
	/* tram blocks and lines */
	fread(&blocks, sizeof(__u8), 1, fp);
	for (i = 0; i < blocks; i++) {
		fread(&size, sizeof(__u32), 1, fp);

		type  = 0x30000000 & size;
		size &= 0x001fffff;

		if (size == 0) {
			fprintf(stderr, "tram block of 0 size\n");
			return -1;
		}

		if ( (size <= TRAMB_ISIZE - mgr->tramb_ifree_start ) && (type!=TRAM_EXTERNAL)) {
			tmp = patch->traml_isize;

			/* write lines */

			read_tram_lines(patch->tramb_isize,
					traml_iaddr, &patch->traml_isize, TRAML_IDATA_BASE, TRAML_TYPE_WRITE, fp);

			/* read lines */
			read_tram_lines(patch->tramb_isize,
					traml_iaddr, &patch->traml_isize, TRAML_IDATA_BASE, TRAML_TYPE_READ, fp);

			/* a block with no tram lines */
			if (tmp == patch->traml_isize) {
				fprintf(stderr, "tram block with no tram lines\n");
				return -1;
			}

			/* no free tram space */
			if (mgr->traml_ifree_start + patch->traml_isize > TRAML_ISIZE) {
				fprintf(stderr, "no free internal tram lines\n");
				return -1;
			}

			patch->tramb_isize += size;
			mgr->tramb_ifree_start += size;

		} else if ((size <= TRAMB_ESIZE - mgr->tramb_efree_start)&&(type!=TRAM_INTERNAL)) {
			tmp = patch->traml_esize;

			/* write lines */

			read_tram_lines(patch->tramb_esize, traml_eaddr,
					&patch->traml_esize, TRAML_EDATA_BASE, TRAML_TYPE_WRITE, fp);

			/* read lines */

			read_tram_lines(patch->tramb_esize, traml_eaddr,
					&patch->traml_esize, TRAML_EDATA_BASE, TRAML_TYPE_READ, fp);

			/* a block with no tram lines */
			if (tmp == patch->traml_esize) {
				fprintf(stderr, "tram block with no tram lines\n");
				return -1;
			}

			/* no free tram space */
			if (mgr->traml_efree_start + patch->traml_esize > TRAML_ESIZE) {
				fprintf(stderr, "no free external tram lines\n");
				return -1;
			}
			patch->tramb_esize += size;
			mgr->tramb_efree_start += size;

		} else {
			fprintf(stderr, "no free tram blocks\n");
			return -1;
		}
	}

	mgr->traml_ifree_start += patch->traml_isize;
	mgr->traml_efree_start += patch->traml_esize;

	return 0;
}

/* FIXME */
/* only single input patches supported */
static int read_patch_header(struct dsp_patch_manager *mgr, struct dsp_patch *patch, FILE * fp, char *name, int num_in, int num_out)
{
	char tmp[sizeof(FILE_HEAD)];
	__u8 count, addr;
	__u32 traml_iaddr[TRAML_ISIZE];
	__u32 traml_eaddr[TRAML_ESIZE];
	int ret, i, j;
	__u8 input[DSP_NUM_INPUTS],output[DSP_NUM_OUTPUTS];


	//Read file format info:
	fread(tmp, sizeof(char), sizeof(FILE_HEAD), fp);
	if(strcmp(tmp,FILE_HEAD)){
		fprintf(stderr,"Error: File is not a proper dsp file\n" );
		return -1;
	}
	//Read format version	
	i=0;
	fread(&i, sizeof(__u16),1 , fp);
	
	if( i < FILE_FORMAT_VERSION)
		fprintf(stderr,"Warning: file was created with a newer version of the emu-tools\n");
	   

	   
	/* patch name */
	fread(patch->name, sizeof(char), DSP_PATCH_NAME_SIZE, fp);

	//If user specifies a different patch name, we change it now:
	if(name[0]!='\0'){
		strncpy(patch->name,name, DSP_LINE_NAME_SIZE);
		patch->name[DSP_LINE_NAME_SIZE - 1] = '\0';
	}else
		//we copy back the name in the patch
		strncpy(name, patch->name, DSP_LINE_NAME_SIZE);
		
	/* input/output gprs */
	fread(&count, sizeof(__u8), 1, fp);

	// FIXME:
	// We'll need to change the file format to support non-symetric I/O
	if(num_in!=count){
		fprintf(stderr,"Patch requires %d inputs, %d were specified\n",count,num_in);
		goto err0;
	}

	
	for (i = 0; i < count; i++) {
		fread(&(input[i]), sizeof(__u8), 1, fp);
		fread(&(output[i]), sizeof(__u8), 1, fp);
	}

	j=0;
	for(i=0;i<DSP_NUM_INPUTS;i++){
		if(patch_uses_input(patch,i)){
			addr_change[input[j]] = get_io_gpr(mgr, i , GPR_INPUT, patch->gpr_used, patch->gpr_input);
			j++;
		}
	}

	j=0;
	for(i=0;i<DSP_NUM_OUTPUTS;i++){
		if(patch_uses_output(patch,i)){
			addr_change[output[j]] = get_io_gpr(mgr, i , GPR_OUTPUT, patch->gpr_used, patch->gpr_input);
			j++;
		}
	}

	/* dynamic gprs */
	fread(&count, sizeof(__u8), 1, fp);
	for (i = 0; i < count; i++) {
		fread(&addr, sizeof(__u8), 1, fp);
		addr_change[addr] = get_dynamic_gpr(mgr, patch->gpr_used);
	}

	/* static gprs */
	fread(&count, sizeof(__u8), 1, fp);
	for (i = 0; i < count; i++) {
		__s32 value;
		fread(&addr, sizeof(__u8), 1, fp);
		fread(&value, sizeof(__s32), 1, fp);
		addr_change[addr] = get_static_gpr(mgr, value, patch->gpr_used);
	}

	/* control gprs */
	fread(&count, sizeof(__u8), 1, fp);
	for (i = 0; i < count; i++) {
		__s32 value, min, max;
		char name[DSP_GPR_NAME_SIZE];

		fread(&addr, sizeof(__u8), 1, fp);
		fread(&value, sizeof(__s32), 1, fp);
		fread(&min, sizeof(__s32), 1, fp);
		fread(&max, sizeof(__s32), 1, fp);

		if (min >= max) {
			fprintf(stderr, "invalid range for control gpr %x %x\n", min, max);
			return -1;
		}

		if (value > max)
			value = max;
		else if (value < min)
			value = min;

		fread(name, sizeof(char), DSP_GPR_NAME_SIZE, fp);

		addr_change[addr] = get_control_gpr(mgr, value, min, max, name, patch->gpr_used, patch->name);
	}

	/* constant gprs */
	fread(&count, sizeof(__u8), 1, fp);
	for (i = 0; i < count; i++) {
		__s32 value;

		fread(&addr, sizeof(__u8), 1, fp);
		fread(&value, sizeof(__s32), 1, fp);
		addr_change[addr] = get_constant_gpr(mgr, value, patch->gpr_used);
	}
	/* here lines and blocks, internal and external, all start from 0 */
	/* this is fixed later on in set_tram_addr() */

	patch->tramb_istart = 0;
	patch->tramb_isize = 0;
	patch->tramb_estart = 0;
	patch->tramb_esize = 0;

	patch->traml_istart = 0;
	patch->traml_isize = 0;

	patch->traml_estart = 0;
	patch->traml_esize = 0;

	/* tram tablelookup blocks and lines */
	ret = read_tram_block(mgr, patch, traml_iaddr, traml_eaddr, fp);
	if (ret < 0)
		return ret;

	/* tram delaylines blocks and lines */
	ret = read_tram_block(mgr, patch, traml_iaddr, traml_eaddr, fp);
	if (ret < 0)
		return ret;

	if (patch->traml_isize != 0) {
		patch->traml_iaddr = (__u32 *) malloc(patch->traml_isize * sizeof(__u32));
		if (patch->traml_iaddr == NULL) {
			fprintf(stderr, "read_patch_header(): no memory available\n");
			return -1;
		}
		memcpy(patch->traml_iaddr, traml_iaddr, patch->traml_isize * sizeof(__u32));
	}

	if (patch->traml_esize != 0) {
		patch->traml_eaddr = (__u32 *) malloc(patch->traml_esize * sizeof(__u32));
		if (patch->traml_eaddr == NULL) {
			fprintf(stderr, "read_patch_header(): no memory available\n");
			goto err0;
		}
		memcpy(patch->traml_eaddr, traml_eaddr, patch->traml_esize * sizeof(__u32));
	}

	fread(&patch->code_size, sizeof(__u16), 1, fp);

	if (patch->code_size == 0) {
		fprintf(stderr, "patch with zero code size\n");
		goto err1;
	}

	mgr->code_free_start += patch->code_size;

	if (mgr->code_free_start > DSP_CODE_SIZE) {
		fprintf(stderr, "no free dsp code memory\n");
		goto err2;
	}

	return 0;

      err2:
	mgr->code_free_start -= patch->code_size;

      err1:
	if (patch->traml_esize != 0)
		free(patch->traml_eaddr);

      err0:
	if (patch->traml_isize != 0)
		free(patch->traml_iaddr);

	return -1;
}

/* FIXME */
/* support precise patch positioning in a line (patches in general don't commute) */
	
int dsp_read_patch(struct dsp_patch_manager *mgr, const char *file, int input[DSP_NUM_INPUTS],
		   int output[DSP_NUM_OUTPUTS], int num_in, int num_out, int io, char *name, int placement)
{
	struct dsp_patch *patch;
	struct list_head *list;
	FILE *fp;
	int i, j;

	if(num_in!=num_out)
		fprintf(stderr,"Error: Patches with an unequal number of ins and outs are not yet supported\n");

	if (!mgr->init)
		dsp_init(mgr);

	patch = (struct dsp_patch *) malloc(sizeof(struct dsp_patch));
	if (patch == NULL) {
		perror("patch");
		goto err0;
	}
	memset(patch, 0, sizeof(struct dsp_patch));

	
	if ((fp = fopen(file, "r")) == NULL) {
		perror(file);
		goto err1;
	}

	if(io==1){
		list= &mgr->ipatch_list;
		/* only load a patch if there is a route connecting it */
		for(i=0;i<num_in;i++){
			set_bit(input[i], &patch->input);
			set_bit(output[i], &patch->output);
			
			for (j = 0; j < DSP_NUM_OUTPUTS; j++)
				if ( test_bit(input[i], &mgr->rpatch.route_v[j]) || test_bit(input[i], &mgr->rpatch.route[j])  ) 
					goto match1;
			goto err2;
match1:
		}
		goto match;
	}else{
		list=&mgr->opatch_list;
		/* only load a patch if there is a route connecting it */	
		for(i=0;i<num_out;i++){
			set_bit(input[i], &patch->input);
			set_bit(output[i], &patch->output);
			if ( !( mgr->rpatch.route_v[input[i]] || mgr->rpatch.route[input[i]]))
				goto err2;


			
		}
		goto match;
	}
	

	
      err2:
	fclose(fp);
      err1:
	free(patch);
      err0:
	return -1;

      match:

	if (read_patch_header(mgr, patch, fp, name, num_in, num_out) < 0)
		goto err2;

	read_patch_code(mgr, patch, fp);
	search_and_replace(mgr, patch->code, patch->code_size, addr_change, GPR_BASE, TRAML_EADDR_BASE + TRAML_ESIZE);

	set_patch_id(mgr, patch);
	determine_io(mgr, patch);

	if(placement==0)
		list_add(&patch->list, list);
	else
		list_add_tail(&patch->list, list);

		
	fclose(fp);
	return 0;
}
