/*
 *  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 <string.h>
#include "comm.h"
#include "ld10k1_fnc.h"
#include "ld10k1_error.h"
#include "liblo10k1.h"
#include "liblo10k1ef.h"
#include "liblo10k1lf.h"
#include "ld10k1_debug.h"
#include "config.h"

int send_request_check(int conn_num, int op, void *data, int data_size)
{
	int opr, sizer;
	int err;

	if ((err = send_request(conn_num, op, data, data_size)) < 0)
		return err;

	if ((err = receive_response(conn_num, &opr, &sizer)) < 0)
		return err;
		
	return 0;
}

int send_msg_data_check(int conn_num, void *data, int data_size)
{
	int opr, sizer;
	int err;

	if ((err = send_msg_data(conn_num, data, data_size)) < 0)
		return err;

	if ((err = receive_response(conn_num, &opr, &sizer)) < 0)
		return err;
		
	return 0;
}

int receive_response_data_check(int conn_num, void *data, int idata_size)
{
	int err;
	int op, data_size;

	if ((err = receive_response(conn_num, &op, &data_size)) < 0)
		return err;
		
	if (op == FNC_ERR)
		return LD10K1_ERR_PROTOCOL;
	if (data_size != idata_size)
		return LD10K1_ERR_PROTOCOL;
		
	if ((err = receive_msg_data(conn_num, data, data_size)) < 0)
		return err;
		
	/* receive check */
	if ((err = receive_response(conn_num, &op, &data_size)) < 0)
		return err;
	
	return 0;
}

void liblo10k1_connection_init(liblo10k1_connection_t *conn)
{
	*conn = 0;
}

int liblo10k1_connect(liblo10k1_param *param, liblo10k1_connection_t *conn)
{
	int err;

	param->server = 0;
	*conn = setup_comm(param);
	if (*conn < 0)
		return LD10K1_ERR_COMM_CONN;
	if ((err = connect_comm(*conn, param)) < 0) {
		free_comm(*conn);
		*conn = 0;
		return err;
	}
	return 0;
}

int liblo10k1_disconnect(liblo10k1_connection_t *conn)
{
	send_request(*conn, FNC_CLOSE_CONN, NULL, 0);
	free_comm(*conn);
	*conn = 0;
	return 0;
}

int liblo10k1_is_open(liblo10k1_connection_t *conn)
{
	return *conn != 0;
}

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

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

int liblo10k1_patch_set_const_count(liblo10k1_dsp_patch_t *p, int count)
{
	liblo10k1_dsp_cs_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_dsp_cs_t *)malloc(sizeof(liblo10k1_dsp_cs_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_dsp_cs_t) * count);
	}
	
	p->const_count = count;
	if (p->consts)
		free(p->consts);
	p->consts = tmp;
	return 0;
}

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

int liblo10k1_patch_set_dyn_count(liblo10k1_dsp_patch_t *p, int count)
{
	p->dyn_count = count;
	return 0;
}

int liblo10k1_patch_set_hw_count(liblo10k1_dsp_patch_t *p, int count)
{
	liblo10k1_dsp_hw_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_dsp_hw_t *)malloc(sizeof(liblo10k1_dsp_hw_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_dsp_hw_t) * count);
	}
	
	p->hw_count = count;
	if (p->hws)
		free(p->hws);
	p->hws = tmp;
	return 0;
}

int liblo10k1_patch_set_tram_count(liblo10k1_dsp_patch_t *p, int count)
{
	liblo10k1_dsp_tram_grp_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_dsp_tram_grp_t *)malloc(sizeof(liblo10k1_dsp_tram_grp_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_dsp_tram_grp_t) * count);
	}
	
	p->tram_count = count;
	if (p->tram)
		free(p->tram);
	p->tram = tmp;
	return 0;
}

int liblo10k1_patch_set_tram_acc_count(liblo10k1_dsp_patch_t *p, int count)
{
	liblo10k1_dsp_tram_acc_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_dsp_tram_acc_t *)malloc(sizeof(liblo10k1_dsp_tram_acc_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_dsp_tram_acc_t) * count);
	}
	
	p->tram_acc_count = count;
	if (p->tram_acc)
		free(p->tram_acc);
	p->tram_acc = tmp;
	return 0;
}

int liblo10k1_patch_set_ctl_count(liblo10k1_dsp_patch_t *p, int count)
{
	liblo10k1_dsp_ctl_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_dsp_ctl_t *)malloc(sizeof(liblo10k1_dsp_ctl_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_dsp_ctl_t) * count);
	}
	
	p->ctl_count = count;
	if (p->ctl)
		free(p->ctl);
	p->ctl = tmp;
	return 0;
}

int liblo10k1_patch_set_instr_count(liblo10k1_dsp_patch_t *p, int count)
{
	liblo10k1_dsp_instr_t *tmp = NULL;
	
	if (count > 0) {
		tmp = (liblo10k1_dsp_instr_t *)malloc(sizeof(liblo10k1_dsp_instr_t) * count);
		if (!tmp)
			return LD10K1_ERR_NO_MEM;
		memset(tmp, 0, sizeof(liblo10k1_dsp_instr_t) * count);
	}
	
	p->instr_count = count;
	if (p->instr)
		free(p->instr);
	p->instr = tmp;
	return 0;
}

liblo10k1_dsp_patch_t *liblo10k1_patch_alloc(int in_count, int out_count, int const_count, int sta_count, int dyn_count, int hw_count, int tram_count, int tram_acc_count, int ctl_count, int instr_count)
{
	liblo10k1_dsp_patch_t *np;
	int en = 0;

	np = (liblo10k1_dsp_patch_t *)malloc(sizeof(liblo10k1_dsp_patch_t));
	if (!np)
		return NULL;

	np->patch_name[0] ='\0';
	np->ins = NULL;
	np->outs = NULL;
	np->consts = NULL;
	np->stas = NULL;
	np->hws = NULL;
	np->tram = NULL;
	np->tram_acc = NULL;
	np->ctl = NULL;
	np->instr = NULL;

	if ((en = liblo10k1_patch_set_dyn_count(np, dyn_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_in_count(np, in_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_out_count(np, out_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_const_count(np, const_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_sta_count(np, sta_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_hw_count(np, hw_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_tram_count(np, tram_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_tram_acc_count(np, tram_acc_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_ctl_count(np, ctl_count)) < 0)
		goto error;
	if ((en = liblo10k1_patch_set_instr_count(np, instr_count)) < 0)
		goto error;
	return np;
error:
    	liblo10k1_patch_free(np);
	return NULL;
}

void liblo10k1_patch_free(liblo10k1_dsp_patch_t *patch)
{
	liblo10k1_patch_set_in_count(patch, 0);
	liblo10k1_patch_set_out_count(patch, 0);
	liblo10k1_patch_set_const_count(patch, 0);
	liblo10k1_patch_set_dyn_count(patch, 0);
	liblo10k1_patch_set_sta_count(patch, 0);
	liblo10k1_patch_set_hw_count(patch, 0);
	liblo10k1_patch_set_tram_count(patch, 0);
	liblo10k1_patch_set_tram_acc_count(patch, 0);
	liblo10k1_patch_set_ctl_count(patch, 0);
	liblo10k1_patch_set_instr_count(patch, 0);
		
	free(patch);
}

int liblo10k1_patch_load(liblo10k1_connection_t *conn, liblo10k1_dsp_patch_t *patch, int before, int *loaded, int *loaded_id)
{
	int err;
	ld10k1_fnc_patch_add_t patch_fnc;
	int tmpres[2];

	strncpy(patch_fnc.patch.patch_name, patch->patch_name, sizeof(patch_fnc.patch.patch_name) - 1);
	patch_fnc.patch.patch_name[sizeof(patch_fnc.patch.patch_name) - 1] = '\0';
	
	patch_fnc.patch.in_count = patch->in_count;
	patch_fnc.patch.out_count = patch->out_count;
	patch_fnc.patch.const_count = patch->const_count;
	patch_fnc.patch.static_count = patch->sta_count;
	patch_fnc.patch.dynamic_count = patch->dyn_count;
	patch_fnc.patch.hw_count = patch->hw_count;
	patch_fnc.patch.tram_count = patch->tram_count;
	patch_fnc.patch.tram_acc_count = patch->tram_acc_count;
	patch_fnc.patch.ctl_count = patch->ctl_count;
	patch_fnc.patch.instr_count = patch->instr_count;

	/* patch */
	/* add */
	patch_fnc.where = before;
	if ((err = send_request_check(*conn, FNC_PATCH_ADD, &patch_fnc, sizeof(ld10k1_fnc_patch_add_t))) < 0)
		return err;

	/* in */
	if (patch->in_count)
		if ((err = send_msg_data_check(*conn, patch->ins, sizeof(ld10k1_dsp_p_in_out_t) * patch->in_count)) < 0)
			return err;

	/* out */
	if (patch->out_count)
		if ((err = send_msg_data_check(*conn, patch->outs, sizeof(ld10k1_dsp_p_in_out_t) * patch->out_count)) < 0)
			return err;

	/* const */
	if (patch->const_count)
		if ((err = send_msg_data_check(*conn, patch->consts, sizeof(ld10k1_dsp_p_const_static_t) * patch->const_count)) < 0)
			return err;

	/* sta */
	if (patch->sta_count)
		if ((err = send_msg_data_check(*conn, patch->stas, sizeof(ld10k1_dsp_p_const_static_t) * patch->sta_count)) < 0)
			return err;

	/* hw */
	if (patch->hw_count)
		if ((err = send_msg_data_check(*conn, patch->hws, sizeof(ld10k1_dsp_p_hw_t) * patch->hw_count)) < 0)
			return err;

	if (patch->tram_count)
		/* tram groups */
		if ((err = send_msg_data_check(*conn, patch->tram, sizeof(ld10k1_dsp_tram_grp_t) * patch->tram_count)) < 0)
			return err;

	if (patch->tram_acc_count)
		/* tram access */
		if ((err = send_msg_data_check(*conn, patch->tram_acc, sizeof(ld10k1_dsp_tram_acc_t) * patch->tram_acc_count)) < 0)
			return err;

	if (patch->ctl_count)
		/* ctls */
		if ((err = send_msg_data_check(*conn, patch->ctl, sizeof(ld10k1_dsp_ctl_t) * patch->ctl_count)) < 0)
			return err;

	/* instr */
	if ((err = send_msg_data_check(*conn, patch->instr, sizeof(ld10k1_dsp_instr_t) * patch->instr_count)) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, tmpres, sizeof(tmpres))) < 0)
		return err;

	if (loaded)
		*loaded = tmpres[0];
	if (loaded_id)
		*loaded_id = tmpres[1];

	return 0;
}

int liblo10k1_debug(liblo10k1_connection_t *conn, int deb, void (*prn_fnc)(char *))
{
	int err;
	ld10k1_fnc_debug_t debug_info;
	char debug_line[1000];
	int opr, sizer;

	/* add */
	debug_info.what = deb;

	if ((err = send_request(*conn, FNC_DEBUG, &debug_info, sizeof(ld10k1_fnc_debug_t))) < 0)
		return err;

	while (1) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE)
			break;

		if (sizer < (int)sizeof(debug_line)) {
			if ((err = receive_msg_data(*conn, &debug_line, sizer)) < 0)
				return err;
		} else
			return LIBLO10K1_ERR_DEBUG;
		(*prn_fnc)(debug_line);
	}

	/* not checked */
	return receive_response(*conn, &opr, &sizer);
}

int liblo10k1_patch_unload(liblo10k1_connection_t *conn, int patch_num)
{
	ld10k1_fnc_patch_del_t patch_fnc;

	patch_fnc.where = patch_num;

	return send_request_check(*conn, FNC_PATCH_DEL, &patch_fnc, sizeof(ld10k1_fnc_patch_del_t));
}

int liblo10k1_dsp_init(liblo10k1_connection_t *conn)
{
	return send_request_check(*conn, FNC_DSP_INIT, NULL, 0);
}

static int liblo10k1_find_any(liblo10k1_connection_t *conn, int op, int patch, char *name, int *out)
{
	ld10k1_fnc_name_t name_info;
	int err;
	int idx = -1;

	name_info.patch_num = patch;
	name_info.gpr = -1;
	
	strncpy(name_info.name, name, sizeof(name_info.name) - 1);
	name_info.name[sizeof(name_info.name) - 1] = '\0';

	if ((err = send_request(*conn, op, &name_info, sizeof(ld10k1_fnc_name_t))) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, &idx, sizeof(idx))) < 0)
		return err;
	
	*out = idx;
	return 0;
}

int liblo10k1_find_patch(liblo10k1_connection_t *conn, char *patch_name, int *out)
{
	return liblo10k1_find_any(conn, FNC_PATCH_FIND, -1, patch_name, out);
}

int liblo10k1_find_fx(liblo10k1_connection_t *conn, char *fx_name, int *out)
{
	return liblo10k1_find_any(conn, FNC_FX_FIND, -1, fx_name, out);
}

int liblo10k1_find_in(liblo10k1_connection_t *conn, char *in_name, int *out)
{
	return liblo10k1_find_any(conn, FNC_IN_FIND, -1, in_name, out);
}

int liblo10k1_find_out(liblo10k1_connection_t *conn, char *out_name, int *out)
{
	return liblo10k1_find_any(conn, FNC_OUT_FIND, -1, out_name, out);
}

int liblo10k1_find_patch_in(liblo10k1_connection_t *conn, int patch_num, char *patch_in_name, int *out)
{
	return liblo10k1_find_any(conn, FNC_PATCH_IN_FIND, patch_num, patch_in_name, out);
}

int liblo10k1_find_patch_out(liblo10k1_connection_t *conn, int patch_num, char *patch_out_name, int *out)
{
	return liblo10k1_find_any(conn, FNC_PATCH_OUT_FIND, patch_num, patch_out_name, out);
}

int liblo10k1_con_add(liblo10k1_connection_t *conn, int multi, int simple, int from_type, int from_patch, int from_io, int to_type, int to_patch, int to_io, int *ci)
{
	ld10k1_fnc_connection_t connection_fnc;
	int err;
	int conn_id;
	
	connection_fnc.what = FNC_CONNECTION_ADD;
	connection_fnc.multi = multi;
	connection_fnc.simple = simple;
	connection_fnc.from_type = from_type;
	connection_fnc.from_patch = from_patch;
	connection_fnc.from_io = from_io;
	connection_fnc.to_type = to_type;
	connection_fnc.to_patch = to_patch;
	connection_fnc.to_io = to_io;

	if ((err = send_request(*conn, FNC_CONNECTION_ADD, &connection_fnc, sizeof(ld10k1_fnc_connection_t))) < 0)
		return err;
	
	if ((err = receive_response_data_check(*conn, &conn_id, sizeof(conn_id))) < 0)
		return err;
		
	if (ci)
		*ci = conn_id;
	return 0;
}

int liblo10k1_con_del(liblo10k1_connection_t *conn, int type, int patch, int io, int *ci)
{
	ld10k1_fnc_connection_t connection_fnc;
	int err;
	int conn_id;

	connection_fnc.what = FNC_CONNECTION_DEL;
	connection_fnc.from_type = type;
	connection_fnc.from_patch = patch;
	connection_fnc.from_io = io;
	connection_fnc.to_type = -1;
	connection_fnc.to_patch = -1;
	connection_fnc.to_io = -1;
	
	if ((err = send_request(*conn, FNC_CONNECTION_DEL, &connection_fnc, sizeof(ld10k1_fnc_connection_t))) < 0)
		return err;
	
	if ((err = receive_response_data_check(*conn, &conn_id, sizeof(conn_id))) < 0)
		return err;
		
	if (ci)
		*ci = conn_id;
	return 0;
}

static int liblo10k1_rename_any(liblo10k1_connection_t *conn, int op, int patch_num, int gpr_num, char *name)
{
	ld10k1_fnc_name_t name_info;

	name_info.patch_num = patch_num;
	name_info.gpr = gpr_num;
	
	strncpy(name_info.name, name, sizeof(name_info.name) - 1);
	name_info.name[sizeof(name_info.name) - 1] = '\0';
	
	return send_request_check(*conn, op, &name_info, sizeof(ld10k1_fnc_name_t));
}

int liblo10k1_rename_patch(liblo10k1_connection_t *conn, int patch_num, char *patch_name)
{
	return liblo10k1_rename_any(conn, FNC_PATCH_RENAME, patch_num, -1, patch_name);
}

int liblo10k1_rename_patch_in(liblo10k1_connection_t *conn, int patch_num, int in, char *patch_in_name)
{
	return liblo10k1_rename_any(conn, FNC_PATCH_IN_RENAME, patch_num, in, patch_in_name);
}

int liblo10k1_rename_patch_out(liblo10k1_connection_t *conn, int patch_num, int out, char *patch_out_name)
{
	return liblo10k1_rename_any(conn, FNC_PATCH_OUT_RENAME, patch_num, out, patch_out_name);
}

int liblo10k1_rename_fx(liblo10k1_connection_t *conn, int fx, char *fx_name)
{
	return liblo10k1_rename_any(conn, FNC_FX_RENAME, -1, fx, fx_name);
}

int liblo10k1_rename_in(liblo10k1_connection_t *conn, int in, char *in_name)
{
	return liblo10k1_rename_any(conn, FNC_IN_RENAME, -1, in, in_name);
}

int liblo10k1_rename_out(liblo10k1_connection_t *conn, int out, char *out_name)
{
	return liblo10k1_rename_any(conn, FNC_OUT_RENAME, -1, out, out_name);
}

int liblo10k1_get_io(liblo10k1_connection_t *conn, int op, int io_num, liblo10k1_get_io_t *out);
int liblo10k1_get_pio(liblo10k1_connection_t *conn, int op, int patch_num, int io_num, liblo10k1_get_io_t *out);

int liblo10k1_get_fx(liblo10k1_connection_t *conn, int fx_num, liblo10k1_get_io_t *out)
{
	return liblo10k1_get_io(conn, FNC_GET_FX, fx_num, out);
}

int liblo10k1_get_in(liblo10k1_connection_t *conn, int in_num, liblo10k1_get_io_t *out)
{
	return liblo10k1_get_io(conn, FNC_GET_IN, in_num, out);
}

int liblo10k1_get_out(liblo10k1_connection_t *conn, int out_num, liblo10k1_get_io_t *out)
{
	return liblo10k1_get_io(conn, FNC_GET_OUT, out_num, out);
}

int liblo10k1_get_pin(liblo10k1_connection_t *conn, int patch_num, int in_num, liblo10k1_get_io_t *out)
{
	return liblo10k1_get_pio(conn, FNC_GET_PIN, patch_num, in_num, out);
}

int liblo10k1_get_pout(liblo10k1_connection_t *conn, int patch_num, int out_num, liblo10k1_get_io_t *out)
{
	return liblo10k1_get_pio(conn, FNC_GET_POUT, patch_num, out_num, out);
}

int liblo10k1_get_io(liblo10k1_connection_t *conn, int op, int io_num, liblo10k1_get_io_t *out)
{
	int err;
	
	if ((err = send_request(*conn, op, &io_num, sizeof(int))) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, out, sizeof(liblo10k1_get_io_t))) < 0)
		return err;

	return 0;
}

int liblo10k1_get_pio(liblo10k1_connection_t *conn, int op, int patch_num, int io_num, liblo10k1_get_io_t *out)
{
	int tmp_num[2];
	int err;
	
	tmp_num[0] = patch_num;
	tmp_num[1] = io_num;
	
	if ((err = send_request(*conn, op, tmp_num, sizeof(int) * 2)) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, out, sizeof(liblo10k1_get_io_t))) < 0)
		return err;
	
	return 0;
}

int liblo10k1_get_io_count(liblo10k1_connection_t *conn, int op, int *out);
int liblo10k1_get_pio_count(liblo10k1_connection_t *conn, int op, int patch_num, int *out);

int liblo10k1_get_fx_count(liblo10k1_connection_t *conn, int *out)
{
	return liblo10k1_get_io_count(conn, FNC_GET_FX_COUNT, out);
}

int liblo10k1_get_in_count(liblo10k1_connection_t *conn, int *out)
{
	return liblo10k1_get_io_count(conn, FNC_GET_IN_COUNT, out);
}

int liblo10k1_get_out_count(liblo10k1_connection_t *conn, int *out)
{
	return liblo10k1_get_io_count(conn, FNC_GET_OUT_COUNT, out);
}

int liblo10k1_get_pin_count(liblo10k1_connection_t *conn, int patch_num, int *out)
{
	return liblo10k1_get_pio_count(conn, FNC_GET_PIN_COUNT, patch_num, out);
}

int liblo10k1_get_pout_count(liblo10k1_connection_t *conn, int patch_num, int *out)
{
	return liblo10k1_get_pio_count(conn, FNC_GET_POUT_COUNT, patch_num, out);
}

int liblo10k1_get_io_count(liblo10k1_connection_t *conn, int op, int *out)
{
	int err;
	
	if ((err = send_request(*conn, op, 0, 0)) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, out, sizeof(int))) < 0)
		return err;

	return 0;
}

int liblo10k1_get_pio_count(liblo10k1_connection_t *conn, int op, int patch_num, int *out)
{
	int err;
	
	if ((err = send_request(*conn, op, &patch_num, sizeof(int))) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, out, sizeof(int))) < 0)
		return err;
	
	return 0;
}

int liblo10k1_get_patches_info(liblo10k1_connection_t *conn, liblo10k1_patches_info_t **out, int *count)
{
	liblo10k1_patches_info_t *info;
	int opr, sizer;
	int err;

	*out = NULL;
	*count = 0;
	info = NULL;

	if ((err = send_request(*conn, FNC_GET_PATCHES_INFO, 0, 0)) < 0)
		return err;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	*count = sizer / sizeof(liblo10k1_patches_info_t);
	if (sizer > 0) {
		info = (liblo10k1_patches_info_t *)receive_msg_data_malloc(*conn, sizer);
		if (!info)
			return LD10K1_ERR_NO_MEM;
	}

	if ((err = receive_response(*conn, &opr, &sizer)) < 0) {
		free(info);
		return err;
	}

	*out = info;
	return 0;
}

int liblo10k1_patch_get(liblo10k1_connection_t *conn, int patch_num, liblo10k1_dsp_patch_t **opatch)
{
	liblo10k1_dsp_patch_t *patch;
	int err;

	ld10k1_dsp_patch_t tmp_patch;
	int opr, sizer;


	if ((err = send_request(*conn, FNC_GET_PATCH, &patch_num, sizeof(patch_num))) < 0)
		return err;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	if (opr != FNC_CONTINUE)
		return LD10K1_ERR_PROTOCOL;

	if ((err = receive_msg_data(*conn, &tmp_patch, sizeof(tmp_patch))) < 0)
		return err;

	/* alloc patch */
	patch = liblo10k1_patch_alloc(tmp_patch.in_count, tmp_patch.out_count, tmp_patch.const_count, tmp_patch.static_count, tmp_patch.dynamic_count, tmp_patch.hw_count, tmp_patch.tram_count, tmp_patch.tram_acc_count, tmp_patch.ctl_count, tmp_patch.instr_count);
	if (!patch)
		return LD10K1_ERR_NO_MEM;

	strcpy(patch->patch_name, tmp_patch.patch_name);

	/* ins */
	if (patch->in_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->in_count * sizeof(ld10k1_dsp_p_in_out_t))
			goto err_protocol;

		patch->ins = (ld10k1_dsp_p_in_out_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->ins)
			goto err_nomem;
	}

	/* outs */
	if (patch->out_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->out_count * sizeof(ld10k1_dsp_p_in_out_t))
			goto err_protocol;

		patch->outs = (ld10k1_dsp_p_in_out_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->outs)
			goto err_nomem;
	}

	/* consts */
	if (patch->const_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->const_count * sizeof(ld10k1_dsp_p_const_static_t))
			goto err_protocol;

		patch->consts = (ld10k1_dsp_p_const_static_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->consts)
			goto err_nomem;
	}

	/* stas */
	if (patch->sta_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->sta_count * sizeof(ld10k1_dsp_p_const_static_t))
			goto err_protocol;

		patch->stas = (ld10k1_dsp_p_const_static_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->stas)
			goto err_nomem;
	}

	/* hws */
	if (patch->hw_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->hw_count * sizeof(ld10k1_dsp_p_hw_t))
			goto err_protocol;

		patch->hws = (ld10k1_dsp_p_hw_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->hws)
			goto err_nomem;
	}

	/* tram grp */
	if (patch->tram_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->tram_count * sizeof(liblo10k1_dsp_tram_grp_t))
			goto err_protocol;

		patch->tram = (liblo10k1_dsp_tram_grp_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->tram)
			goto err_nomem;
	}

	/* tram acc */
	if (patch->tram_acc_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->tram_acc_count * sizeof(liblo10k1_dsp_tram_acc_t))
			goto err_protocol;

		patch->tram_acc = (liblo10k1_dsp_tram_acc_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->tram_acc)
			goto err_nomem;
	}

	/* ctls */
	if (patch->ctl_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->ctl_count * sizeof(liblo10k1_dsp_ctl_t))
			goto err_protocol;

		patch->ctl = (liblo10k1_dsp_ctl_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->ctl)
			goto err_nomem;
	}

	/* instr */
	if (patch->instr_count) {
		if ((err = receive_response(*conn, &opr, &sizer)) < 0)
			return err;

		if (opr != FNC_CONTINUE || (unsigned int)sizer != patch->instr_count * sizeof(liblo10k1_dsp_instr_t))
			goto err_protocol;

		patch->instr = (liblo10k1_dsp_instr_t *)receive_msg_data_malloc(*conn, sizer);
		if (!patch->instr)
			goto err_nomem;
	}

 	if ((err = receive_response(*conn, &opr, &sizer)) < 0) {
		liblo10k1_patch_free(patch);
		return err;
	}

	*opatch = patch;
	return 0;
err_nomem:
	liblo10k1_patch_free(patch);
	return LD10K1_ERR_NO_MEM;

err_protocol:
	liblo10k1_patch_free(patch);
	return LD10K1_ERR_PROTOCOL;
}

int liblo10k1_dump(liblo10k1_connection_t *conn, void **out, int *size)
{
	int opr, sizer;
	int err;
	void *dump = NULL;

	if ((err = send_request(*conn, FNC_DUMP, 0, 0)) < 0)
		return err;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	*size = sizer;
	if (sizer > 0) {
		dump = receive_msg_data_malloc(*conn, sizer);
		if (!dump)
			return LD10K1_ERR_NO_MEM;
	}

	if ((err = receive_response(*conn, &opr, &sizer)) < 0) {
		free(dump);
		return err;
	}

	*out = dump;
	return 0;
}

int liblo10k1_check_version(liblo10k1_connection_t *conn)
{
	int opr, sizer;
	int err;
	ld10k1_fnc_version_t ver;

	if ((err = send_request(*conn, FNC_VERSION, 0, 0)) < 0)
		return err;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	if (sizer == sizeof(ver)) {
		if ((err = receive_msg_data(*conn, &ver, sizer)) < 0)
			return err;
	}
	else
		return LD10K1_ERR_WRONG_VER;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	if (strcmp(ver.ld10k1_version, VERSION) == 0)
		return 0;
	else
		return LD10K1_ERR_WRONG_VER;
}

int liblo10k1_get_points_info(liblo10k1_connection_t *conn, int **out, int *count)
{
	int *info;
	int opr, sizer;
	int err;

	info = NULL;

	if ((err = send_request(*conn, FNC_GET_POINTS_INFO, 0, 0)) < 0)
		return err;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	*count = sizer / sizeof(int);
	if (sizer > 0) {
		info = (int *)receive_msg_data_malloc(*conn, sizer);
		if (!info)
			return LD10K1_ERR_NO_MEM;
	}

	if ((err = receive_response(*conn, &opr, &sizer)) < 0) {
		free(info);
		return err;
	}

	*out = info;
	return 0;
}

int liblo10k1_get_point_info(liblo10k1_connection_t *conn, int point_id, liblo10k1_point_info_t *out)
{
	int opr, sizer;
	int err;

	if ((err = send_request(*conn, FNC_GET_POINT_INFO, &point_id, sizeof(point_id))) < 0)
		return err;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	if (sizer != sizeof(liblo10k1_point_info_t))
		return LD10K1_ERR_PROTOCOL;
	if ((err = receive_msg_data(*conn, out, sizer)) < 0)
		return LD10K1_ERR_PROTOCOL;

	if ((err = receive_response(*conn, &opr, &sizer)) < 0)
		return err;

	return 0;
}

int liblo10k1_get_dsp_info(liblo10k1_connection_t *conn, liblo10k1_dsp_info_t *info)
{
	int err;
	
	if ((err = send_request(*conn, FNC_GET_DSP_INFO, NULL, 0)) < 0)
		return err;

	if ((err = receive_response_data_check(*conn, info, sizeof(liblo10k1_dsp_info_t))) < 0)
		return err;
	
	return 0;
}

struct errmsg_t
{
	int errnum;
	char *errmsg;
};

struct errmsg_t error_text[] =
{
	{LD10K1_ERR_UNKNOWN, "Unknown error"},
	{LD10K1_ERR_COMM_READ, "Error in read from socket"},
	{LD10K1_ERR_COMM_WRITE, "Error in write to socket"},
	{LD10K1_ERR_UNKNOWN_PATCH_NUM, "Wrong parameter - patch with this num doesn't exists"},
	{LD10K1_ERR_PROTOCOL, "ld10k1 is expecting more or less data as it got"},
	{LD10K1_ERR_COMM_CONN, "Error in socket connect"},

/* add patch */
	{LD10K1_ERR_PROTOCOL_IN_COUNT, "Wrong in registers count"},
	{LD10K1_ERR_PROTOCOL_OUT_COUNT, "Wrong out registers count"},
	{LD10K1_ERR_PROTOCOL_CONST_COUNT, "Wrong const registers count"},
	{LD10K1_ERR_PROTOCOL_STATIC_COUNT, "Wrong static registers count"},
	{LD10K1_ERR_PROTOCOL_DYNAMIC_COUNT, "Wrong dynamic registers count"},
	{LD10K1_ERR_PROTOCOL_HW_COUNT, "Wrong hw registers count"},
	{LD10K1_ERR_PROTOCOL_TRAM_COUNT, "Wrong tram count"},
	{LD10K1_ERR_PROTOCOL_TRAM_ACC_COUNT, "Wrong tram access registers count"},
	{LD10K1_ERR_PROTOCOL_CTL_COUNT, "Wrong controls count"},
	{LD10K1_ERR_PROTOCOL_INSTR_COUNT, "Wrong instructions count"},

/* driver */
	{LD10K1_ERR_DRIVER_CODE_POKE, "Unable to poke code"},
	{LD10K1_ERR_DRIVER_INFO, "Unable to get info"},
	{LD10K1_ERR_DRIVER_CODE_PEEK, "Unable to peek code"},
	{LD10K1_ERR_DRIVER_PCM_POKE, "Unable to poke pcm"},

/* tram */
	{LD10K1_ERR_ITRAM_FULL, "Not enought free itram"},
	{LD10K1_ERR_ETRAM_FULL, "Not enought free etram"},
	{LD10K1_ERR_TRAM_FULL, "Not enought free tram"},
	{LD10K1_ERR_TRAM_FULL_GRP, "Not enought free tram group"},

	{LD10K1_ERR_ITRAM_FULL_ACC, "Not enought free itram acc"},
	{LD10K1_ERR_ETRAM_FULL_ACC, "Not enought free etram acc"},
	{LD10K1_ERR_TRAM_FULL_ACC, "Not enought free tram acc"},

	{LD10K1_ERR_MAX_CON_PER_POINT, "Maximum connections per point reached"},

/* others */
	{LD10K1_ERR_NO_MEM, "Not enought free mem"},
	{LD10K1_ERR_MAX_PATCH_COUNT, "Max patch count excesed"},
	{LD10K1_ERR_NOT_FREE_REG, "There is not free reg"},
	{LD10K1_ERR_NOT_FREE_INSTR, "There is no free instruction slot"},

/* patch chceck */
	{LD10K1_ERR_WRONG_REG_HW_INDEX, "Loaded patch has wrong hw index"},
	{LD10K1_ERR_WRONG_TRAM_POS, "Loaded patch has wrong tram position"},

	{LD10K1_ERR_WRONG_TRAM_TYPE, "Loaded patch has wrong tram type"},
	{LD10K1_ERR_WRONG_TRAM_SIZE, "Loaded patch has wrong tram size"},
	{LD10K1_ERR_WRONG_TRAM_ACC_TYPE, "Loaded patch has wrong tram acc type"},

	{LD10K1_ERR_TRAM_GRP_OUT_OF_RANGE, "Loaded patch has tram gpr index out of range"},
	{LD10K1_ERR_TRAM_ACC_OUT_OF_RANGE, "Loaded patch has tram acc index out of range"},

	{LD10K1_ERR_CTL_VCOUNT_OUT_OF_RANGE, "Loaded patch has wrong count of visible gpr for control"},
	{LD10K1_ERR_CTL_COUNT_OUT_OF_RANGE, "Loaded patch has wrong count of gpr for control"},

	{LD10K1_ERR_CTL_MIN_MAX_RANGE, "Loaded patch has wrong min, max range for control"},
	{LD10K1_ERR_CTL_TRANLSLATION, "Loaded patch has wrong translation function for control"},
	{LD10K1_ERR_CTL_REG_INDEX, "Loaded patch has wrong gpr index for control"},
	{LD10K1_ERR_CTL_REG_VALUE, "Loaded patch has wrong initial value for control"},

	{LD10K1_ERR_INSTR_OPCODE, "Loaded patch has wrong instruction opcode"},
	{LD10K1_ERR_INSTR_ARG_INDEX, "Loaded patch has wrong argument for instruction"},

	{LD10K1_ERR_UNKNOWN_REG_NUM, "There isn't register with this reg number"},
	{LD10K1_ERR_UNKNOWN_PATCH_REG_NUM, "There isn't patch register with this reg number for patch"},

	{LD10K1_ERR_CONNECTION, "Couldn't connect patch to io"},
	{LD10K1_ERR_CONNECTION_FNC, "Not supported connect/disconnect function"},

	{LD10K1_ERR_CTL_EXISTS, "Can't add control because there is one with same name"},

	{LD10K1_ERR_PATCH_RENAME, "Couldn't rename patch"},
	{LD10K1_ERR_PATCH_REG_RENAME, "Couldn't rename patch register"},
	{LD10K1_ERR_REG_RENAME, "Couldn't rename register"},
	{LD10K1_ERR_WRONG_VER, "Wrong ld10k1 version"},
	{LD10K1_ERR_UNKNOWN_POINT, "Unknown point"},
	
	/* errors from liblo10k1ef */
	{LD10K1_EF_ERR_OPEN, "Can not open file"},
	{LD10K1_EF_ERR_STAT, "Can not stat file"},
	{LD10K1_EF_ERR_SIZE, "Wrong file size"},
	{LD10K1_EF_ERR_READ, "Can not read file"},
	{LD10K1_EF_ERR_SIGNATURE, "Wrong file signature"},
	{LD10K1_EF_ERR_FORMAT, "Wrong file format"},

	{LD10K1_EF_ERR_TRANSFORM_CTL, "Can not transform control"},
	{LD10K1_EF_ERR_TRANSFORM, "Can not transform effect"},
	{LD10K1_EF_ERR_TRANSFORM_TRANS, "Wrong control translation"},
	
	/* errors from liblo10k1lf */
	{LD10K1_LF_ERR_OPEN, "Can not open file"},
	{LD10K1_LF_ERR_WRITE, "Can not write file"},
	{LD10K1_LF_ERR_READ, "Can not read file"},
	{LD10K1_LF_ERR_SIGNATURE, "Wrong file signature"},
	{LD10K1_LF_ERR_PART_TYPE, "Wrong part type"},
	{LD10K1_LF_ERR_PART_SIZE, "Wrong part size"},
	{LD10K1_LF_ERR_VERSION, "Wrong file version"},
	{LD10K1_LF_ERR_FILE_TYPE, "Wrong file type"},

	{0, ""}
};

char *liblo10k1_error_str(int error)
{
	int i;

	for (i = 0; error_text[i].errnum != 0; i++)
		if (error_text[i].errnum == error)
			return error_text[i].errmsg;
	return "Error msg not specified in liblo10k1";
}
