/*
 *  EMU10k1 loader lib
 *  Copyright (c) 2003,2004 by Peter Zubaj
 *
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

#include "ld10k1_error.h"
#include "ld10k1_fnc.h"
#include "comm.h"
#include "liblo10k1.h"
#include "liblo10k1ef.h"

#define AS10K1_FILE_SIGNATURE_ALSA "EMU10K1 FX8010 1"
#define AS10K1_FILE_SIGNATURE_EMU "emu10k1-dsp-file"
#define AS10K1_FILE_FORMAT_VERSION_EMU 1

liblo10k1_emu_patch_t *liblo10k1_emu_new_patch()
{
	liblo10k1_emu_patch_t *tmp = (liblo10k1_emu_patch_t *)malloc(sizeof(liblo10k1_emu_patch_t));
	
	if (!tmp)
		return NULL;
	memset(tmp, 0, sizeof(liblo10k1_emu_patch_t));
	
	return tmp;
}

void liblo10k1_emu_free_patch(liblo10k1_emu_patch_t *p)
{
	liblo10k1_emu_patch_set_in_count(p, 0);
	liblo10k1_emu_patch_set_out_count(p, 0);
	liblo10k1_emu_patch_set_dyn_count(p, 0);
	liblo10k1_emu_patch_set_sta_count(p, 0);
	liblo10k1_emu_patch_set_ctl_count(p, 0);
	liblo10k1_emu_patch_set_con_count(p, 0);
	liblo10k1_emu_patch_set_lookup_count(p, 0);
	liblo10k1_emu_patch_set_delay_count(p, 0);
	liblo10k1_emu_patch_set_instr_count(p, 0);
	
	free(p);
}

int liblo10k1_emu_patch_set_in_count(liblo10k1_emu_patch_t *p, int count)
{
	unsigned int *tmp = NULL;
	
	if (count > 0) {
		tmp = (unsigned int *)malloc(sizeof(unsigned int)*count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(unsigned int) * count);
	}
	
	p->in_count = count;
	if (p->ins)
		free(p->ins);
	p->ins = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_out_count(liblo10k1_emu_patch_t *p, int count)
{
	unsigned int *tmp = NULL;
	
	if (count > 0) {
		tmp = (unsigned int *)malloc(sizeof(unsigned int)*count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(unsigned int) * count);
	}
	
	p->out_count = count;
	if (p->outs)
		free(p->outs);
	p->outs = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_dyn_count(liblo10k1_emu_patch_t *p, int count)
{
	unsigned int *tmp = NULL;
	
	if (count > 0) {
		tmp = (unsigned int *)malloc(sizeof(unsigned int)*count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(unsigned int) * count);
	}
	
	p->dyn_count = count;
	if (p->dyns)
		free(p->dyns);
	p->dyns = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_sta_count(liblo10k1_emu_patch_t *p, int count)
{
	liblo10k1_emu_sc_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_sc_t *)malloc(sizeof(liblo10k1_emu_sc_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_sc_t)*count);
	}
	
	p->sta_count = count;
	if (p->stas)
		free(p->stas);
	p->stas = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_ctl_count(liblo10k1_emu_patch_t *p, int count)
{
	liblo10k1_emu_ctl_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_ctl_t *)malloc(sizeof(liblo10k1_emu_ctl_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_ctl_t)*count);
	}
	
	p->ctl_count = count;
	if (p->ctls)
		free(p->ctls);
	p->ctls = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_con_count(liblo10k1_emu_patch_t *p, int count)
{
	liblo10k1_emu_sc_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_sc_t *)malloc(sizeof(liblo10k1_emu_sc_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_sc_t)*count);
	}
	
	p->con_count = count;
	if (p->cons)
		free(p->cons);
	p->cons = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_line_count(liblo10k1_emu_tram_t *t, int write, int count)
{
	liblo10k1_emu_tram_line_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_tram_line_t *)malloc(sizeof(liblo10k1_emu_tram_line_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_tram_line_t)*count);
	}
	
	if (write) {
		t->write_line_count = count;
		if (t->write_lines)
			free(t->write_lines);
		t->write_lines = tmp;
	} else {
		t->read_line_count = count;
		if (t->read_lines)
			free(t->read_lines);
		t->read_lines = tmp;
	}
	return 0;
}

int liblo10k1_emu_patch_set_lookup_count(liblo10k1_emu_patch_t *p, int count)
{
	liblo10k1_emu_tram_t *tmp = NULL;
	int i;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_tram_t *)malloc(sizeof(liblo10k1_emu_tram_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_tram_t)*count);
	}
	
	if (p->tram_lookups) {
		for (i = 0; i < p->tram_lookup_count; i++) {
			liblo10k1_emu_patch_set_line_count(&(p->tram_lookups[i]), 0, 0);
			liblo10k1_emu_patch_set_line_count(&(p->tram_lookups[i]), 1, 0);
		}
		free(p->tram_lookups);
	}
	p->tram_lookup_count = count;
	p->tram_lookups = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_delay_count(liblo10k1_emu_patch_t *p, int count)
{
	liblo10k1_emu_tram_t *tmp = NULL;
	int i;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_tram_t *)malloc(sizeof(liblo10k1_emu_tram_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_tram_t)*count);
	}
	
	if (p->tram_delays) {
		for (i = 0; i < p->tram_delay_count; i++) {
			liblo10k1_emu_patch_set_line_count(&(p->tram_delays[i]), 0, 0);
			liblo10k1_emu_patch_set_line_count(&(p->tram_delays[i]), 1, 0);
		}
		free(p->tram_delays);
	}
	p->tram_delay_count = count;
	p->tram_delays = tmp;
	return 0;
}

int liblo10k1_emu_patch_set_instr_count(liblo10k1_emu_patch_t *p, int count)
{
	liblo10k1_emu_instr_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_emu_instr_t *)malloc(sizeof(liblo10k1_emu_instr_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_emu_instr_t)*count);
	}
	
	p->instr_count = count;
	if (p->instrs)
		free(p->instrs);
	p->instrs = tmp;
	return 0;
}

static int read_byte(char *patch_data, int size, int *pos, unsigned char *out)
{
	if (*pos < size) {
		*out = patch_data[*pos];
		(*pos)++;
		return 0;
	} else
		return LD10K1_EF_ERR_FORMAT;
}

static int read_ushort(char *patch_data, int size, int *pos, unsigned short *out)
{
	if (*pos + 1 < size) {
		*out = *((unsigned short *)(patch_data + *pos));
		*(pos) += 2;
		return 0;
	} else
		return LD10K1_EF_ERR_FORMAT;
}

static int read_uint(char *patch_data, int size, int *pos, unsigned int *out)
{
	if (*pos + 3 < size) {
		*out = *((unsigned int *)(patch_data + *pos));
		(*pos) += 4;
		return 0;
	} else
		return LD10K1_EF_ERR_FORMAT;
}

static int read_string(char *patch_data, int size, int *pos, char *out, int ssize)
{
	if (*pos + ssize - 1 < size) {
		strncpy(out, &(patch_data[*pos]), ssize - 1);
		out[ssize - 1] = '\0';
		(*pos) += ssize;
		return 0;
	} else
		return LD10K1_EF_ERR_FORMAT;
}

int liblo10k1_emu_load_patch(char *file_name, liblo10k1_emu_patch_t **p)
{
	struct stat patch_stat;
	char *patch_data;
	FILE *patch_file;
	liblo10k1_emu_patch_t *new_patch = NULL;
	unsigned int i, j, z, k;
	int en = 0;

	int file_pos = 0;
	int file_size = 0;
	
	unsigned char byte_tmp;
	unsigned short ushort_tmp;
	
	liblo10k1_emu_tram_t *tmp_tram;
	liblo10k1_emu_tram_line_t *tmp_tram_lines;
	
	unsigned int part1, part2;

	if (!(patch_file = fopen(file_name, "r")))
		return LD10K1_EF_ERR_OPEN;

	/* first load patch to mem */
	if (stat(file_name, &patch_stat))
		return LD10K1_EF_ERR_STAT;

	/* minimal patch len is 57 */
	if (patch_stat.st_size < 57 || patch_stat.st_size > 1000000)
		return LD10K1_EF_ERR_SIZE;

	file_size = patch_stat.st_size;

	patch_data = (char *)malloc(patch_stat.st_size);
	if (!patch_data)
		return LD10K1_ERR_NO_MEM;

	if (fread(patch_data, patch_stat.st_size, 1, patch_file) != 1) {
		fclose(patch_file);
		en = LD10K1_EF_ERR_READ;
		goto err;
	} else
		fclose(patch_file);

	int file_sig = 0;
		
	/* signature checks - two kinds of as10k1 files, one from alsa-tools, other from emu-tools. */
	if(strncmp(patch_data, AS10K1_FILE_SIGNATURE_ALSA, 16) != 0)
	{
	  if((strncmp(patch_data, AS10K1_FILE_SIGNATURE_EMU, 16) == 0) && (*((unsigned short *)&patch_data[17]) == AS10K1_FILE_FORMAT_VERSION_EMU))
	    file_sig = 3;
		else	
		{
	    en = LD10K1_EF_ERR_SIGNATURE;
	    goto err;
		}
	}
	
	new_patch = liblo10k1_emu_new_patch();
	if (!new_patch) {
		en = LD10K1_ERR_NO_MEM;
		goto err;
	}
	
	/* next patch name */
	strncpy(new_patch->patch_name, &(patch_data[16 + file_sig]), 31);
	new_patch->patch_name[31] = '\0';
	/* registers */
	file_pos = 32+16 + file_sig; 
	
	/* in count */
	if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
		  goto err;
	
	if (byte_tmp >= 32) {
		en = LD10K1_EF_ERR_FORMAT;
		goto err;
	}
	
	if ((en = liblo10k1_emu_patch_set_in_count(new_patch, byte_tmp)) < 0 ||
		(en = liblo10k1_emu_patch_set_out_count(new_patch, byte_tmp)) < 0)
		goto err;
	
	/* read in gprs */
	for (i = 0; i < new_patch->in_count; i++) {
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
		new_patch->ins[i] = byte_tmp;
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
		new_patch->outs[i] = byte_tmp;
	}
	
	/* read dyn gprs */
	if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
		goto err;
		
	if ((en = liblo10k1_emu_patch_set_dyn_count(new_patch, byte_tmp)) < 0)
		goto err;
	
	for (i = 0; i < new_patch->dyn_count; i++) {
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
		new_patch->dyns[i] = byte_tmp;
	}
	
	/* read sta gprs */
	if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
		goto err;
		
	if ((en = liblo10k1_emu_patch_set_sta_count(new_patch, byte_tmp)) < 0)
		goto err;
	
	for (i = 0; i < new_patch->sta_count; i++) {
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
		new_patch->stas[i].sc = byte_tmp;
		if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->stas[i].sc_val))) < 0)
			goto err;
	}
	
	/* read ctl gprs */
	if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
		goto err;
		
	if ((en = liblo10k1_emu_patch_set_ctl_count(new_patch, byte_tmp)) < 0)
		goto err;
	
	for (i = 0; i < new_patch->ctl_count; i++) {
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
		new_patch->ctls[i].ctl = byte_tmp;
		if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->ctls[i].ctl_val))) < 0)
			goto err;
		if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->ctls[i].ctl_val_min))) < 0)
			goto err;
		if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->ctls[i].ctl_val_max))) < 0)
			goto err;
		if ((en = read_string(patch_data, file_size, &file_pos, new_patch->ctls[i].ctl_name, 32)) < 0)
			goto err;
	}
	
	/* read const gprs */
	if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
		goto err;
		
	if ((en = liblo10k1_emu_patch_set_con_count(new_patch, byte_tmp)) < 0)
		goto err;
	
	for (i = 0; i < new_patch->con_count; i++) {
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
		new_patch->cons[i].sc = byte_tmp;
		if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->cons[i].sc_val))) < 0)
			goto err;
	}
	
	/* read tram lookup gprs */
	for (z = 0; z < 2; z++) {
		if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
			goto err;
	
		if (z) {
			if ((en = liblo10k1_emu_patch_set_delay_count(new_patch, byte_tmp)) < 0)
				goto err;
		} else {		
			if ((en = liblo10k1_emu_patch_set_lookup_count(new_patch, byte_tmp)) < 0)
				goto err;
		}
	
		for (i = 0; i < (z ? new_patch->tram_delay_count : new_patch->tram_lookup_count); i++) {
			/* size */
			if (z) {
				if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->tram_delays[i].size))) < 0)
					goto err;
				tmp_tram = new_patch->tram_delays + i;
			} else {
				if ((en = read_uint(patch_data, file_size, &file_pos, &(new_patch->tram_lookups[i].size))) < 0)
					goto err;
				tmp_tram = new_patch->tram_lookups + i;
			}
			
			for (k = 0; k < 2; k++) {
				/* read lines */
				if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
					goto err;
				
				if ((en = liblo10k1_emu_patch_set_line_count(tmp_tram, k, byte_tmp)) < 0)
					goto err;
				
				if (k)
					tmp_tram_lines = tmp_tram->write_lines;
				else
					tmp_tram_lines = tmp_tram->read_lines;
				
				for (j = 0; j < (k ? tmp_tram->write_line_count : tmp_tram->read_line_count); j++) {
					if ((en = read_byte(patch_data, file_size, &file_pos, &byte_tmp)) < 0)
						goto err;
						
					tmp_tram_lines[j].line = byte_tmp;
				
					if ((en = read_uint(patch_data, file_size, &file_pos, &(tmp_tram_lines[j].line_size))) < 0)
						goto err;
				}
			}
		}
	}
	
	/* instruction lines */
	if ((en = read_ushort(patch_data, file_size, &file_pos, &ushort_tmp)) < 0)
		goto err;
		
	if(file_sig)
	  ushort_tmp >>= 1;
		
	if ((en = liblo10k1_emu_patch_set_instr_count(new_patch, ushort_tmp)) < 0)
		goto err;
	
	for (i = 0; i < new_patch->instr_count; i++) {
		if ((en = read_uint(patch_data, file_size, &file_pos, &part1)) < 0)
			goto err;
		if ((en = read_uint(patch_data, file_size, &file_pos, &part2)) < 0)
			goto err;
			
		/* fill instr */
		new_patch->instrs[i].arg[2] = part1 >> 10;
		new_patch->instrs[i].arg[3] = part1 & 0x3FF;
		new_patch->instrs[i].op = part2 >> 20;
		new_patch->instrs[i].arg[0] = (part2 >> 10) & 0x3FF;
		new_patch->instrs[i].arg[1] = part2 & 0x3FF;
	}
	
	*p = new_patch;
	
	if (patch_data)
		free(patch_data);
	return 0;
err:
	if (patch_data)
		free(patch_data);
	if (new_patch)
		liblo10k1_emu_free_patch(new_patch);
	return en;
}

typedef struct
{
	unsigned int gpr;
	unsigned int ld_gpr;
} used_gpr_t;


static int check_if_used(used_gpr_t *gprs, int count, unsigned int gpr)
{
	int i;
	for (i = 0; i < count; i++) {
		if (gprs[i].gpr == gpr)
			return i;
	}
	return -1;
}

static char *default_in_names[] =
{
	"IL",
	"IR",
	"IRL",
	"IRR",
	"IC",
	"ILFE"
};

static char *default_out_names[] =
{
	"OL",
	"OR",
	"ORL",
	"ORR",
	"OC",
	"OLFE"
};

int liblo10k1_patch_find_ctl_by_name(liblo10k1_dsp_patch_t *p, char *ctl_name)
{
	int i;
	for (i = 0; i < p->ctl_count; i++)
		if (strcmp(p->ctl[i].name, ctl_name) == 0)
			return i;
	return -1;
}

int liblo10k1_patch_ctl_set_trans(liblo10k1_dsp_patch_t *p, int idx, int trans) 
{
	int i;
	switch (trans) {
		case EMU10K1_GPR_TRANSLATION_NONE:
			break;
		case EMU10K1_GPR_TRANSLATION_TABLE100:
			if (p->ctl[idx].min != 0 && p->ctl[idx].max != 100)
				return LD10K1_EF_ERR_TRANSFORM_TRANS;
			break;
		case EMU10K1_GPR_TRANSLATION_BASS:
		case EMU10K1_GPR_TRANSLATION_TREBLE:
			if (p->ctl[idx].min != 0 && p->ctl[idx].max != 0xFFFFFFFF)
				return LD10K1_EF_ERR_TRANSFORM_TRANS;
			if (p->ctl[idx].count != 5)
				return LD10K1_EF_ERR_TRANSFORM_TRANS;
			break;
		case EMU10K1_GPR_TRANSLATION_ONOFF:
			if (p->ctl[idx].min != 0 && p->ctl[idx].max != 1)
				return LD10K1_EF_ERR_TRANSFORM_TRANS;
			break;
	}
	for (i = 0; i < p->ctl[idx].count; i++)
		if (p->ctl[idx].value[i] < p->ctl[idx].min || p->ctl[idx].value[i] > p->ctl[idx].max)
			return LD10K1_EF_ERR_TRANSFORM_TRANS;
	p->ctl[idx].translation = trans;
	return 0;
}

int liblo10k1_patch_ctl_set_vcount(liblo10k1_dsp_patch_t *p, int idx, int vc) 
{
	if (p->ctl[idx].count < vc)
		return LD10K1_EF_ERR_TRANSFORM_CTL;
	p->ctl[idx].vcount = vc;
	return 0;
}

int liblo10k1_patch_ctl_set_index(liblo10k1_dsp_patch_t *p, int idx, int i) 
{
	p->ctl[idx].index = i;
	return 0;
}

int liblo10k1_patch_ctl_set_value(liblo10k1_dsp_patch_t *p, int idx, int vi, int val) 
{
	if (p->ctl[idx].count < vi)
		return LD10K1_EF_ERR_TRANSFORM_CTL;
	if (val < p->ctl[idx].min || val > p->ctl[idx].max)
			return LD10K1_EF_ERR_TRANSFORM_CTL;
	p->ctl[idx].value[vi] = val;
	return 0;
}

int liblo10k1_emu_transform_patch(liblo10k1_emu_patch_t *ep,  liblo10k1_ctl_transform_t *tctl, int tctl_count, liblo10k1_dsp_patch_t **lp)
{
	used_gpr_t used_gpr[0x400];
	int used_gpr_count = 0;
	int i, j, k;
	int gpr;
	int tmp_cnt;
	int idx;
	int en = 0;
	int io_name_map[6];
	int transformed;
	liblo10k1_dsp_ctl_t *tmp_nctl;
	liblo10k1_emu_ctl_t *tmp_octl;
	int const_count, hw_count;	/*int ctl_transform_map[256];*/
	liblo10k1_dsp_patch_t *np = NULL;
	liblo10k1_emu_tram_t *tram_tmp;
	liblo10k1_dsp_tram_acc_t *tram_nacc;
	liblo10k1_emu_tram_line_t *tram_line;
	int val, addhw, addconst;
	
	/* for all instruction get used gpr list */
	for (i = 0; i < ep->instr_count; i++) {
		for (j = 0; j < 4; j++) {
			gpr = ep->instrs[i].arg[j];
			for (k = 0; k < used_gpr_count; k++)
				if (gpr == used_gpr[k].gpr)
					break;
					
			if (k < used_gpr_count)
				continue;
			
			used_gpr[used_gpr_count].gpr = gpr;
			used_gpr[used_gpr_count].ld_gpr = 0;
			used_gpr_count++;
		}
	}
	
	np = liblo10k1_patch_alloc(0, 0, 0, 0, 0, 0, 0, 0, 0, ep->instr_count);
	if (!np)
		return LD10K1_ERR_NO_MEM;
		
	/* set patch name */
	strcpy(np->patch_name, ep->patch_name);
	
	/* in gprs */
	tmp_cnt = 0;
	for (i = 0; i < ep->in_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->ins[i] + 0x100);
		if (idx >= 0) {
			used_gpr[idx].ld_gpr = EMU10K1_PREG_IN(tmp_cnt);
			if (i < 6)
				io_name_map[i] = tmp_cnt;
			tmp_cnt++;
		} else {
			if (i < 6)
				io_name_map[i] = -1;
		}
	}
		
	if ((en = liblo10k1_patch_set_in_count(np, tmp_cnt)) < 0)
		goto err;
	
	/* set in name */
	if (ep->in_count == 2 || /* stereo */
		ep->in_count == 4 || /* 4 channel */
		ep->in_count == 6) { /* 5.1 */
		for (i = 0; i < ep->in_count; i++) {
			if (io_name_map[i] >= 0)
				strcpy(np->ins[io_name_map[i]].name, default_in_names[i]);
		}
	}
		
	/* out gprs */
	tmp_cnt = 0;
	for (i = 0; i < ep->out_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->outs[i] + 0x100);
		if (idx >= 0) {
			used_gpr[idx].ld_gpr = EMU10K1_PREG_OUT(tmp_cnt);
			if (i < 6)
				io_name_map[i] = tmp_cnt;
			tmp_cnt++;
		} else {
			if (i < 6)
				io_name_map[i] = -1;
		}
	}
		
	if ((en = liblo10k1_patch_set_out_count(np, tmp_cnt)) < 0)
		goto err;
	
	/* set out name */
	if (ep->out_count == 2 || /* stereo */
		ep->out_count == 4 || /* 4 channel */
		ep->out_count == 6) { /* 5.1 */
		for (i = 0; i < ep->out_count; i++) {
			if (io_name_map[i] >= 0)
				strcpy(np->outs[io_name_map[i]].name, default_out_names[i]);
		}
	}
	
	/* dyn regs */
	tmp_cnt = 0;
	for (i = 0; i < ep->dyn_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->dyns[i] + 0x100);
		if (idx >= 0) {
			used_gpr[idx].ld_gpr = EMU10K1_PREG_DYN(tmp_cnt);
			tmp_cnt++;
		}
	}
	
	if ((en = liblo10k1_patch_set_dyn_count(np, tmp_cnt)) < 0)
		goto err;
	
	/* sta regs */
	tmp_cnt = 0;
	for (i = 0; i < ep->sta_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->stas[i].sc + 0x100);
		if (idx >= 0) {
			used_gpr[idx].ld_gpr = EMU10K1_PREG_STA(tmp_cnt);
			tmp_cnt++;
		}
	}
	
	if ((en = liblo10k1_patch_set_sta_count(np, tmp_cnt)) < 0)
		goto err;
		
	/* sta regs - initilization */
	tmp_cnt = 0;
	for (i = 0; i < ep->sta_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->stas[i].sc + 0x100);
		if (idx >= 0) {
			np->stas[tmp_cnt].const_val = ep->stas[i].sc_val;
			tmp_cnt++;
		}
	}
	
	/* FIXME - ak je niektoty ctl pouzity v tctl viackrat tak to zblbne */
	/* ctls regs */
	/* first get count of ctls */
	tmp_cnt = 0;
	for (i = 0; i < tctl_count; i++)
		tmp_cnt += tctl[i].emu_ctl_count;
	
	if ((en = liblo10k1_patch_set_ctl_count(np, ep->ctl_count - tmp_cnt + tctl_count)) < 0)
		goto err;
		
	tmp_cnt = 0;
	for (i = 0; i < ep->ctl_count; i++) {
		tmp_octl = &(ep->ctls[i]);
		/* find if transformed */
		transformed = 0;
		for (j = 0; j < tctl_count; j++) {
			for (k = 0; k < tctl[j].emu_ctl_count; k++) {
				if (i == tctl[j].emu_ctls[k]) {
					/* it is transformed */
					tmp_nctl = &(np->ctl[j]);
					if (strcmp(tmp_nctl->name, "") != 0) {
						/* initialized - check min, max */
						if (tmp_octl->ctl_val_min != tmp_nctl->min ||
							tmp_octl->ctl_val_max != tmp_nctl->max) {
							en = LD10K1_EF_ERR_TRANSFORM_CTL;
							goto err;
						}
						
						tmp_nctl->value[k] = tmp_octl->ctl_val;
					} else {
						/* initialize it */
						strcpy(tmp_nctl->name, tctl[j].ctl_name);
						tmp_nctl->index = -1;
						tmp_nctl->vcount = tctl[j].emu_ctl_count;/*1;*/
						tmp_nctl->count = tctl[j].emu_ctl_count;/*1;*/
						tmp_nctl->value[k] = tmp_octl->ctl_val;
						tmp_nctl->min = tmp_octl->ctl_val_min;
						tmp_nctl->max = tmp_octl->ctl_val_max;
						tmp_nctl->translation = EMU10K1_GPR_TRANSLATION_NONE;
					}
					
					idx = check_if_used(used_gpr, used_gpr_count, tmp_octl->ctl + 0x100);
					if (idx >= 0)
						used_gpr[idx].ld_gpr = EMU10K1_PREG_CTL(j, k);
					
					transformed = 1;
					break;
				}
			}
			if (transformed)
				break;
		}
		
		if (transformed)
			continue;
		/* not transformed */
		tmp_nctl = &(np->ctl[tmp_cnt + tctl_count]);
		
		strcpy(tmp_nctl->name, tmp_octl->ctl_name);
		tmp_nctl->index = -1;
		tmp_nctl->vcount = 1;
		tmp_nctl->count = 1;
		tmp_nctl->value[0] = tmp_octl->ctl_val;
		tmp_nctl->min = tmp_octl->ctl_val_min;
		tmp_nctl->max = tmp_octl->ctl_val_max;
		tmp_nctl->translation = EMU10K1_GPR_TRANSLATION_NONE;

		idx = check_if_used(used_gpr, used_gpr_count, tmp_octl->ctl + 0x100);
		if (idx >= 0)
			used_gpr[idx].ld_gpr = EMU10K1_PREG_CTL(tmp_cnt + tctl_count, 0);
		
		tmp_cnt++;
		
		if (tmp_cnt > np->ctl_count) {
			en = LD10K1_EF_ERR_TRANSFORM_CTL;
			goto err;
		}
	}
	
	if (tmp_cnt + tctl_count < np->ctl_count) {
		en = LD10K1_EF_ERR_TRANSFORM_CTL;
		goto err;
	}
	
	/* tram */
	if ((en = liblo10k1_patch_set_tram_count(np, ep->tram_lookup_count + ep->tram_delay_count)) < 0)
		goto err;
	
	tmp_cnt = 0;
	for (i = 0; i < ep->tram_lookup_count + ep->tram_delay_count; i++) {
		if (i < ep->tram_lookup_count)
			tram_tmp = &(ep->tram_lookups[i]);
		else
			tram_tmp = &(ep->tram_delays[i - ep->tram_lookup_count]);
			
		tmp_cnt += tram_tmp->read_line_count + tram_tmp->write_line_count;
		np->tram[i].grp_type = i < ep->tram_lookup_count ? TRAM_GRP_TABLE : TRAM_GRP_DELAY;
		np->tram[i].grp_size = tram_tmp->size;
		np->tram[i].grp_pos = TRAM_POS_AUTO;
	}
	
	/* tram acc */
	if ((en = liblo10k1_patch_set_tram_acc_count(np, tmp_cnt)) < 0)
		goto err;
		
	tmp_cnt = 0;
	for (i = 0; i < ep->tram_lookup_count + ep->tram_delay_count; i++) {
		if (i < ep->tram_lookup_count)
			tram_tmp = &(ep->tram_lookups[i]);
		else
			tram_tmp = &(ep->tram_delays[i - ep->tram_lookup_count]);
			
		for (k = 0; k < 2; k++) {
			for (j = 0; j < (k ? tram_tmp->write_line_count : tram_tmp->read_line_count); j++) {
				if (k)
					tram_line = &(tram_tmp->write_lines[j]);
				else
					tram_line = &(tram_tmp->read_lines[j]);
					
				tram_nacc = &(np->tram_acc[tmp_cnt]);
					
				tram_nacc->grp = i;
				tram_nacc->acc_offset = tram_line->line_size;
				tram_nacc->acc_type = k ? TRAM_ACC_WRITE : TRAM_ACC_READ;
				
				idx = check_if_used(used_gpr, used_gpr_count, tram_line->line + 0x200);
				if (idx >= 0)
					used_gpr[idx].ld_gpr = EMU10K1_PREG_TRAM_DATA(tmp_cnt);
				
				idx = check_if_used(used_gpr, used_gpr_count, tram_line->line + 0x300);
				if (idx >= 0)
					used_gpr[idx].ld_gpr = EMU10K1_PREG_TRAM_ADDR(tmp_cnt);
			
				tmp_cnt++;
			}
		}
	}
	
	/* const and hw */
	const_count = 0;
	hw_count = 0;
	
	for (i = 0; i < used_gpr_count; i++) {
		gpr = used_gpr[i].gpr;
		
		if (gpr < 0x40)
			hw_count++;
		else if (gpr < 0x56)
			const_count++;
		else if (gpr < 0x5C)
			hw_count++;
	}
	
	/* const regs */
	tmp_cnt = 0;
	for (i = 0; i < ep->con_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->cons[i].sc + 0x100);
		if (idx >= 0) {
			used_gpr[idx].ld_gpr = EMU10K1_PREG_CONST(tmp_cnt);
			tmp_cnt++;
		}
	}
	
	/* consts */
	if ((en = liblo10k1_patch_set_const_count(np, const_count + tmp_cnt)) < 0)
		goto err;
		
	const_count = tmp_cnt;
	
	/* const regs - initilization */
	tmp_cnt = 0;
	for (i = 0; i < ep->con_count; i++) {
		idx = check_if_used(used_gpr, used_gpr_count, ep->cons[i].sc + 0x100);
		if (idx >= 0) {
			np->consts[tmp_cnt].const_val = ep->cons[i].sc_val;
			tmp_cnt++;
		}
	}
	
	/* hw */
	if ((en = liblo10k1_patch_set_hw_count(np, hw_count)) < 0)
		goto err;
		
	hw_count = 0;
	for (i = 0; i < used_gpr_count; i++) {
		gpr = used_gpr[i].gpr;
		addconst = 0;
		addhw = 0;		
		
		if (gpr < 0x40) {
			/* fx */
			addhw = 1;
			if (gpr < 0x10)
				val = EMU10K1_REG_FX(gpr);
			else if (gpr < 0x20)
				val = EMU10K1_REG_IN(gpr - 0x10);
			else
				val = EMU10K1_REG_OUT(gpr - 0x20);
		} else {
			switch (gpr) {
				case 0x40:
					addconst = 1; val = 0x00000000;
					break;
				case 0x41:
					addconst = 1; val = 0x00000001;
					break;
				case 0x42:
					addconst = 1; val = 0x00000002;
					break;
				case 0x43:
					addconst = 1; val = 0x00000003;
					break;
				case 0x44:
					addconst = 1; val = 0x00000004;
					break;
				case 0x45:
					addconst = 1; val = 0x00000008;
					break;
				case 0x46:
					addconst = 1; val = 0x00000010;
					break;
				case 0x47:
					addconst = 1; val = 0x00000020;
					break;
				case 0x48:
					addconst = 1; val = 0x00000100;
					break;
				case 0x49:
					addconst = 1; val = 0x00010000;
					break;
				case 0x4A:
					addconst = 1; val = 0x00080000;
					break;
				case 0x4B:
					addconst = 1; val = 0x10000000;
					break;
				case 0x4C:
					addconst = 1; val = 0x20000000;
					break;
				case 0x4D:
					addconst = 1; val = 0x40000000;
					break;
				case 0x4E:
					addconst = 1; val = 0x80000000;
					break;
				case 0x4F:
					addconst = 1; val = 0x7FFFFFFF;
					break;
				case 0x50:
					addconst = 1; val = 0xFFFFFFFF;
					break;
				case 0x51:
					addconst = 1; val = 0xFFFFFFFE;
					break;
				case 0x52:
					addconst = 1; val = 0xC0000000;
					break;
				case 0x53:
					addconst = 1; val = 0x4F1BBCDC;
					break;
				case 0x54:
					addconst = 1; val = 0x5A7EF9DB;
					break;
				case 0x55:
					addconst = 1; val = 0x00100000;
					break;
				case 0x56:
					addhw = 1; val = EMU10K1_NREG_HW_ACCUM;
					break;
				case 0x57:
					addhw = 1; val = EMU10K1_NREG_HW_CCR;
					break;
				case 0x58:
					addhw = 1; val = EMU10K1_NREG_HW_NOISE1;
					break;
				case 0x59:
					addhw = 1; val = EMU10K1_NREG_HW_NOISE2;
					break;
				case 0x5A:
					addhw = 1; val = EMU10K1_NREG_HW_IRQ;
					break;
				case 0x5B:
					addhw = 1; val = EMU10K1_NREG_HW_DBAC;
					break;
				default:
					if (gpr < 0x100) {
						en = LD10K1_EF_ERR_TRANSFORM;
						goto err;
					}
					break;
			}
		}
		
		if (addhw) {
			np->hws[hw_count].hw_val = val;
			used_gpr[i].ld_gpr = EMU10K1_PREG_HW(hw_count);
			hw_count++;
		} else if (addconst) {
			np->consts[const_count].const_val = val;
			used_gpr[i].ld_gpr = EMU10K1_PREG_CONST(const_count);
			const_count++;
		}	
	}	

	/* instrs */
	if ((en = liblo10k1_patch_set_instr_count(np, ep->instr_count)) < 0)
		goto err;
	
	for (i = 0; i < ep->instr_count; i++) {
		np->instr[i].op_code = ep->instrs[i].op;
		for (j = 0; j < 4; j++) {
			gpr = ep->instrs[i].arg[j];
			
			idx = check_if_used(used_gpr, used_gpr_count, gpr);
			if (!used_gpr[idx].ld_gpr) {
				en = LD10K1_EF_ERR_TRANSFORM;
				goto err;
			}
				
			np->instr[i].arg[j] = used_gpr[idx].ld_gpr;
		}
	}
	
	*lp = np;
	return 0;
err:
	if (np)
		liblo10k1_patch_free(np);
	return en;
}

