/* interface between mobile network/phone implementation and MNCC
 *
 * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
 * All Rights Reserved
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include "../libsample/sample.h"
#include "../libdebug/debug.h"
#include "sender.h"
#include "call.h"
#include "../libtimer/timer.h"
#include "../libmncc/mncc.h"
#include "../libmncc/cause.h"

#define DISC_TIMEOUT	30

//#define DEBUG_LEVEL

#ifdef DEBUG_LEVEL
static double level_of(double *samples, int count)
{
	double level = 0;
	int i;

	for (i = 0; i < count; i++) {
		if (samples[i] > level)
			level = samples[i];
	}

	return level;
}
#endif

static int connect_on_setup;		/* send patterns towards fixed network */
static int release_on_disconnect;	/* release towards mobile phone, if MNCC call disconnects, don't send disconnect tone */

/* stream patterns/announcements */
int16_t *ringback_spl = NULL;
int ringback_size = 0;
int ringback_max = 0;
int16_t *hangup_spl = NULL;
int hangup_size = 0;
int hangup_max = 0;
int16_t *busy_spl = NULL;
int busy_size = 0;
int busy_max = 0;
int16_t *noanswer_spl = NULL;
int noanswer_size = 0;
int noanswer_max = 0;
int16_t *outoforder_spl = NULL;
int outoforder_size = 0;
int outoforder_max = 0;
int16_t *invalidnumber_spl = NULL;
int invalidnumber_size = 0;
int invalidnumber_max = 0;
int16_t *congestion_spl = NULL;
int congestion_size = 0;
int congestion_max = 0;
int16_t *recall_spl = NULL;
int recall_size = 0;
int recall_max = 0;

enum audio_pattern {
	PATTERN_NONE = 0,
	PATTERN_TEST,
	PATTERN_RINGBACK,
	PATTERN_HANGUP,
	PATTERN_BUSY,
	PATTERN_NOANSWER,
	PATTERN_OUTOFORDER,
	PATTERN_INVALIDNUMBER,
	PATTERN_CONGESTION,
	PATTERN_RECALL,
};

static void get_pattern(const int16_t **spl, int *size, int *max, enum audio_pattern pattern)
{
	*spl = NULL;
	*size = 0;
	*max = 0;

	switch (pattern) {
	case PATTERN_RINGBACK:
no_recall:
		*spl = ringback_spl;
		*size = ringback_size;
		*max = ringback_max;
		break;
	case PATTERN_HANGUP:
		if (!hangup_spl)
			goto no_hangup;
		*spl = hangup_spl;
		*size = hangup_size;
		*max = hangup_max;
		break;
	case PATTERN_BUSY:
no_hangup:
no_noanswer:
		*spl = busy_spl;
		*size = busy_size;
		*max = busy_max;
		break;
	case PATTERN_NOANSWER:
		if (!noanswer_spl)
			goto no_noanswer;
		*spl = noanswer_spl;
		*size = noanswer_size;
		*max = noanswer_max;
		break;
	case PATTERN_OUTOFORDER:
		if (!outoforder_spl)
			goto no_outoforder;
		*spl = outoforder_spl;
		*size = outoforder_size;
		*max = outoforder_max;
		break;
	case PATTERN_INVALIDNUMBER:
		if (!invalidnumber_spl)
			goto no_invalidnumber;
		*spl = invalidnumber_spl;
		*size = invalidnumber_size;
		*max = invalidnumber_max;
		break;
	case PATTERN_CONGESTION:
no_outoforder:
no_invalidnumber:
		*spl = congestion_spl;
		*size = congestion_size;
		*max = congestion_max;
		break;
	case PATTERN_RECALL:
		if (!recall_spl)
			goto no_recall;
		*spl = recall_spl;
		*size = recall_size;
		*max = recall_max;
		break;
	default:
		;
	}
}

static enum audio_pattern cause2pattern(int cause)
{
	int pattern;

	switch (cause) {
	case CAUSE_NORMAL:
		pattern = PATTERN_HANGUP;
		break;
	case CAUSE_BUSY:
		pattern = PATTERN_BUSY;
		break;
	case CAUSE_NOANSWER:
		pattern = PATTERN_NOANSWER;
		break;
	case CAUSE_OUTOFORDER:
		pattern = PATTERN_OUTOFORDER;
		break;
	case CAUSE_INVALNUMBER:
		pattern = PATTERN_INVALIDNUMBER;
		break;
	case CAUSE_NOCHANNEL:
		pattern = PATTERN_CONGESTION;
		break;
	default:
		pattern = PATTERN_HANGUP;
	}

	return pattern;
}

enum process_state {
	PROCESS_IDLE = 0,	/* IDLE */
	PROCESS_SETUP_RO,	/* call from radio to MNCC */
	PROCESS_SETUP_RT,	/* call from MNCC to radio */
	PROCESS_ALERTING_RO,	/* call from radio to MNCC */
	PROCESS_ALERTING_RT,	/* call from MNCC to radio */
	PROCESS_CONNECT,
	PROCESS_DISCONNECT,
};

/* MNCC call instance */
typedef struct process {
	struct process *next;
	int callref;
	enum process_state state;
	int audio_disconnected; /* if not associated with transceiver anymore */
	enum audio_pattern pattern;
	int audio_pos;
	uint8_t cause;
	struct timer timer;
} process_t;

static process_t *process_head = NULL;

static void process_timeout(struct timer *timer);

static process_t *create_process(int callref, enum process_state state)
{
	process_t *process;

	process = calloc(sizeof(*process), 1);
	if (!process) {
		PDEBUG(DCALL, DEBUG_ERROR, "No memory!\n");
		abort();
	}
	timer_init(&process->timer, process_timeout, process);
	process->next = process_head;
	process_head = process;

	process->callref = callref;
	process->state = state;

	return process;
}

static void destroy_process(int callref)
{
	process_t *process = process_head;
	process_t **process_p = &process_head;

	while (process) {
		if (process->callref == callref) {
			*process_p = process->next;
			timer_exit(&process->timer);
			free(process);
			return;
		}
		process_p = &process->next;
		process = process->next;
	}
	PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
}

static process_t *get_process(int callref)
{
	process_t *process = process_head;

	while (process) {
		if (process->callref == callref)
			return process;
		process = process->next;
	}
	return NULL;
}

static void new_state_process(int callref, enum process_state state)
{
	process_t *process = get_process(callref);

	if (!process) {
		PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
		return;
	}
	process->state = state;
}

static void set_pattern_process(int callref, enum audio_pattern pattern)
{
	process_t *process = get_process(callref);

	if (!process) {
		PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
		return;
	}
	process->pattern = pattern;
	process->audio_pos = 0;
}

/* disconnect audio, now send audio directly from pattern/announcement, not from transceiver */
static void disconnect_process(int callref, int cause)
{
	process_t *process = get_process(callref);

	if (!process) {
		PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
		return;
	}
	process->pattern = cause2pattern(cause);
	process->audio_disconnected = 1;
	process->audio_pos = 0;
	process->cause = cause;
	timer_start(&process->timer, DISC_TIMEOUT);
}

static void get_process_patterns(process_t *process, int16_t *samples, int length)
{
	const int16_t *spl;
	int size, max, pos;

	get_pattern(&spl, &size, &max, process->pattern);

	/* stream sample */
	pos = process->audio_pos;
	while(length--) {
		if (pos >= size)
			*samples++ = 0;
		else
			*samples++ = spl[pos] >> 2;
		if (++pos == max)
			pos = 0;
	}
	process->audio_pos = pos;
}

static void process_timeout(struct timer *timer)
{
	process_t *process = (process_t *)timer->priv;

	{
		/* announcement timeout */
		uint8_t buf[sizeof(struct gsm_mncc)];
		struct gsm_mncc *mncc = (struct gsm_mncc *)buf;

		if (process->state == PROCESS_DISCONNECT) {
			PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network (after timeout)\n");
			call_down_release(process->callref, process->cause);
		}

		memset(buf, 0, sizeof(buf));
		mncc->msg_type = MNCC_REL_IND;
		mncc->callref = process->callref;
		mncc->fields |= MNCC_F_CAUSE;
		mncc->cause.location = LOCATION_PRIVATE_LOCAL;
		mncc->cause.value = process->cause;

		destroy_process(process->callref);
		PDEBUG(DCALL, DEBUG_INFO, "Releasing MNCC call towards fixed network (after timeout)\n");
		mncc_up(buf, sizeof(struct gsm_mncc));
	}
}

int call_init(int _send_patterns, int _release_on_disconnect)
{
	connect_on_setup = _send_patterns;
	release_on_disconnect = _release_on_disconnect;

	return 0;
}

/* Setup is received from transceiver. */
static int _indicate_setup(int callref, const char *callerid, const char *dialing)
{
	uint8_t buf[sizeof(struct gsm_mncc)];
	struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
	int rc;

	memset(buf, 0, sizeof(buf));
	mncc->msg_type = MNCC_SETUP_IND;
	mncc->callref = callref;
	mncc->fields |= MNCC_F_CALLING;
	if (callerid) {
		strncpy(mncc->calling.number, callerid, sizeof(mncc->calling.number) - 1);
		mncc->calling.type = 4; /* caller ID is of type 'subscriber' */
	} // otherwise unknown and no number
	mncc->fields |= MNCC_F_CALLED;
	strncpy(mncc->called.number, dialing, sizeof(mncc->called.number) - 1);
	mncc->called.type = 0; /* dialing is of type 'unknown' */
	mncc->lchan_type = GSM_LCHAN_TCH_F;
	mncc->fields |= MNCC_F_BEARER_CAP;
	mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ;
	mncc->bearer_cap.speech_ver[1] = -1;

	PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC setup towards fixed network\n");
	rc = mncc_up(buf, sizeof(struct gsm_mncc));
	if (rc < 0)
		destroy_process(callref);
	return rc;
}
int call_up_setup(int callref, const char *callerid, const char *dialing)
{
	int rc;

	if (!callref) {
		PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring setup, because callref not set. (not for us)\n");
		return -CAUSE_INVALCALLREF;
	}

	if (callref < 0x4000000) {
		PDEBUG(DCALL, DEBUG_ERROR, "Invalid callref from mobile station, please fix!\n");
		abort();
	}

	PDEBUG(DCALL, DEBUG_INFO, "Incoming call from '%s' to '%s'\n", callerid ? : "unknown", dialing);
	if (!strcmp(dialing, "010"))
		PDEBUG(DCALL, DEBUG_INFO, " -> Call to Operator '%s'\n", dialing);


	create_process(callref, PROCESS_SETUP_RO);

	rc = _indicate_setup(callref, callerid, dialing);

	return rc;
}

/* Transceiver indicates alerting. */
static void _indicate_alerting(int callref)
{
	uint8_t buf[sizeof(struct gsm_mncc)];
	struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
	int rc;

	memset(buf, 0, sizeof(buf));
	mncc->msg_type = MNCC_ALERT_IND;
	mncc->callref = callref;

	PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC alerting towards fixed network\n");
	rc = mncc_up(buf, sizeof(struct gsm_mncc));
	if (rc < 0)
		destroy_process(callref);
}
void call_up_alerting(int callref)
{
	if (!callref) {
		PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring alerting, because callref not set. (not for us)\n");
		return;
	}

	PDEBUG(DCALL, DEBUG_INFO, "Call is alerting\n");

	if (!connect_on_setup)
		_indicate_alerting(callref);
	set_pattern_process(callref, PATTERN_RINGBACK);
	new_state_process(callref, PROCESS_ALERTING_RT);
}

/* Transceiver indicates early audio */
void call_up_early(int callref)
{
	set_pattern_process(callref, PATTERN_NONE);
}

/* Transceiver indicates answer. */
static void _indicate_answer(int callref, const char *connect_id)
{
	uint8_t buf[sizeof(struct gsm_mncc)];
	struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
	int rc;

	memset(buf, 0, sizeof(buf));
	mncc->msg_type = MNCC_SETUP_CNF;
	mncc->callref = callref;
	mncc->fields |= MNCC_F_CONNECTED;
	/* copy connected number as subscriber number */
	strncpy(mncc->connected.number, connect_id, sizeof(mncc->connected.number));
	mncc->connected.type = 4;
	mncc->connected.plan = 1;
	mncc->connected.present = 0;
	mncc->connected.screen = 3;

	PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC answer towards fixed network\n");
	rc = mncc_up(buf, sizeof(struct gsm_mncc));
	if (rc < 0)
		destroy_process(callref);
}
void call_up_answer(int callref, const char *connect_id)
{
	if (!callref) {
		PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring answer, because callref not set. (not for us)\n");
		return;
	}

	PDEBUG(DCALL, DEBUG_INFO, "Call has been answered by '%s'\n", connect_id);

	if (!connect_on_setup)
		_indicate_answer(callref, connect_id);
	set_pattern_process(callref, PATTERN_NONE);
	new_state_process(callref, PROCESS_CONNECT);
}

/* Transceiver indicates release. */
static void _indicate_disconnect_release(int callref, int cause, int disc)
{
	uint8_t buf[sizeof(struct gsm_mncc)];
	struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
	int rc;

	memset(buf, 0, sizeof(buf));
	mncc->msg_type = (disc) ? MNCC_DISC_IND : MNCC_REL_IND;
	mncc->callref = callref;
	mncc->fields |= MNCC_F_CAUSE;
	mncc->cause.location = LOCATION_PRIVATE_LOCAL;
	mncc->cause.value = cause;

	PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC %s towards fixed network\n", (disc) ? "disconnect" : "release");
	rc = mncc_up(buf, sizeof(struct gsm_mncc));
	if (rc < 0)
		destroy_process(callref);
}
void call_up_release(int callref, int cause)
{
	process_t *process;

	if (!callref) {
		PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring release, because callref not set. (not for us)\n");
		return;
	}

	PDEBUG(DCALL, DEBUG_INFO, "Call has been released with cause=%d\n", cause);

	process = get_process(callref);
	if (process) {
		/* just keep MNCC connection if tones shall be sent.
		 * no tones while setting up / alerting the call. */
		if (connect_on_setup
		 && process->state != PROCESS_SETUP_RO
		 && process->state != PROCESS_ALERTING_RO)
			disconnect_process(callref, cause);
		else
		/* if no tones shall be sent, release on disconnect
		 * or RO setup states */
		if (process->state == PROCESS_DISCONNECT
		 || process->state == PROCESS_SETUP_RO
		 || process->state == PROCESS_ALERTING_RO) {
			destroy_process(callref);
			_indicate_disconnect_release(callref, cause, 0);
		/* if no tones shall be sent, disconnect on all other states */
		} else {
			disconnect_process(callref, cause);
			_indicate_disconnect_release(callref, cause, 1);
		}
	} else {
		/* we don't know about the process, just send release to upper layer anyway */
		_indicate_disconnect_release(callref, cause, 0);
	}
}

/* turn recall tone on or off */
void call_tone_recall(int callref, int on)
{
	set_pattern_process(callref, (on) ? PATTERN_RECALL : PATTERN_NONE);
}

/* forward audio to MNCC or call instance */
void call_up_audio(int callref, sample_t *samples, int count)
{
	if (count != 160) {
		fprintf(stderr, "Samples must be 160, please fix!\n");
		abort();
	}
	/* is MNCC us used, forward audio */
	uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)];
	struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
	process_t *process;

	if (!callref)
		return;

	/* if we are disconnected, ignore audio */
	process = get_process(callref);
	if (!process || process->pattern != PATTERN_NONE)
		return;

	/* forward audio */
	data->msg_type = ANALOG_8000HZ;
	data->callref = callref;
#ifdef DEBUG_LEVEL
	double lev = level_of(samples, count);
	printf("   mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
#endif
	samples_to_int16((int16_t *)data->data, samples, count);

	mncc_up(buf, sizeof(buf));
	/* don't destroy process here in case of an error */
}

/* clock that is used to transmit patterns */
void call_clock(void)
{
	process_t *process = process_head;
	uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)];
	struct gsm_data_frame *data = (struct gsm_data_frame *)buf;

	call_down_clock();

	while(process) {
		if (process->pattern != PATTERN_NONE) {
			data->msg_type = ANALOG_8000HZ;
			data->callref = process->callref;
			/* try to get patterns, else copy the samples we got */
			get_process_patterns(process, (int16_t *)data->data, 160);
#ifdef DEBUG_LEVEL
			sample_t samples[160];
			int16_to_samples(samples, (int16_t *)data->data, 160);
			double lev = level_of(samples, 160);
			printf("   mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
			samples_to_int16((int16_t *)data->data, samples, 160);
#endif
			mncc_up(buf, sizeof(buf));
			/* don't destroy process here in case of an error */
		}
		process = process->next;
	}
}

/* mncc messages received from fixed network */
void mncc_down(uint8_t *buf, int length)
{
	struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
	char number[sizeof(mncc->called.number)];
	char caller_id[sizeof(mncc->calling.number)];
	enum number_type caller_type;
	int callref;
	int rc;
	process_t *process;

	callref = mncc->callref;
	process = get_process(callref);
	if (!process) {
		if (mncc->msg_type == MNCC_SETUP_REQ)
			process = create_process(callref, PROCESS_SETUP_RT);
		else {
			if (mncc->msg_type != MNCC_REL_REQ)
				PDEBUG(DCALL, DEBUG_ERROR, "No process!\n");
			return;
		}
	}

	if (mncc->msg_type == ANALOG_8000HZ) {
		struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
		sample_t samples[160];

		/* if we are disconnected, ignore audio */
		if (process->pattern != PATTERN_NONE)
			return;
		int16_to_samples(samples, (int16_t *)data->data, 160);
#ifdef DEBUG_LEVEL
		double lev = level_of(samples, 160);
		printf("festnetz-level: %s                  %.4f\n", debug_db(lev), (20 * log10(lev)));
#endif
		call_down_audio(callref, samples, 160);
		return;
	}

	if (process->audio_disconnected) {
		switch(mncc->msg_type) {
		case MNCC_DISC_REQ:
			PDEBUG(DCALL, DEBUG_INFO, "Received MNCC disconnect from fixed network with cause %d\n", mncc->cause.value);

			PDEBUG(DCALL, DEBUG_INFO, "Call disconnected, releasing!\n");

			destroy_process(callref);

			PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC release towards fixed network\n");
			mncc->msg_type = MNCC_REL_IND;
			rc = mncc_up(buf, sizeof(struct gsm_mncc));
			if (rc < 0)
				destroy_process(callref);
		break;
		case MNCC_REL_REQ:
			PDEBUG(DCALL, DEBUG_INFO, "Received MNCC release from fixed network with cause %d\n", mncc->cause.value);

			PDEBUG(DCALL, DEBUG_INFO, "Call released\n");

			destroy_process(callref);
			break;
		}
		return;
	}

	switch(mncc->msg_type) {
	case MNCC_SETUP_REQ:
		strcpy(number, mncc->called.number);

		/* caller ID conversion */
		strcpy(caller_id, mncc->calling.number);
		switch(mncc->calling.type) {
		case 1:
			caller_type = TYPE_INTERNATIONAL;
			break;
		case 2:
			caller_type = TYPE_NATIONAL;
			break;
		case 4:
			caller_type = TYPE_SUBSCRIBER;
			break;
		default: /* or 0 */
			caller_type = TYPE_UNKNOWN;
			break;
		}
		if (!caller_id[0])
			caller_type = TYPE_NOTAVAIL;
		if (mncc->calling.present == 1)
			caller_type = TYPE_ANONYMOUS;

		PDEBUG(DCALL, DEBUG_INFO, "Received MNCC call from fixed network '%s' to mobile '%s'\n", caller_id, number);

		if (mncc->callref >= 0x4000000) {
			fprintf(stderr, "Invalid callref from fixed network, please fix!\n");
			abort();
		}

		PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC call confirm towards fixed network\n");
		memset(buf, 0, length);
		mncc->msg_type = MNCC_CALL_CONF_IND;
		mncc->callref = callref;
		mncc->lchan_type = GSM_LCHAN_TCH_F;
		mncc->fields |= MNCC_F_BEARER_CAP;
		mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ;
		mncc->bearer_cap.speech_ver[1] = -1;

		mncc_up(buf, sizeof(struct gsm_mncc));

		PDEBUG(DCALL, DEBUG_INFO, "Outgoing call from '%s' to '%s'\n", caller_id, number);

		rc = call_down_setup(callref, caller_id, caller_type, number);
		if (rc < 0) {
			PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc);
			if (connect_on_setup) {
				PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
				_indicate_answer(callref, number);
			} else {
				PDEBUG(DCALL, DEBUG_INFO, "Disconnecting MNCC call towards fixed network (cause=%d)\n", -rc);
				_indicate_disconnect_release(callref, -rc, 1);
			}
			disconnect_process(callref, -rc);
			break;
		}

		if (connect_on_setup) {
			PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
			_indicate_answer(callref, number);
			break;
		}
		break;
	case MNCC_ALERT_REQ:
		PDEBUG(DCALL, DEBUG_INFO, "Received MNCC alerting from fixed network\n");
		new_state_process(callref, PROCESS_ALERTING_RO);
		break;
	case MNCC_SETUP_RSP:
		PDEBUG(DCALL, DEBUG_INFO, "Received MNCC answer from fixed network\n");
		new_state_process(callref, PROCESS_CONNECT);
		PDEBUG(DCALL, DEBUG_INFO, "Call answered\n");
		call_down_answer(callref);
		PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC setup complete towards fixed network\n");
		memset(buf, 0, length);
		mncc->msg_type = MNCC_SETUP_COMPL_IND;
		mncc->callref = callref;
		rc = mncc_up(buf, sizeof(struct gsm_mncc));
		if (rc < 0)
			destroy_process(callref);
		break;
	case MNCC_DISC_REQ:
		PDEBUG(DCALL, DEBUG_INFO, "Received MNCC disconnect from fixed network with cause %d\n", mncc->cause.value);

		process = get_process(callref);
		if (process && process->state == PROCESS_CONNECT && release_on_disconnect) {
			PDEBUG(DCALL, DEBUG_INFO, "Releasing, because we don't send disconnect tones to mobile phone\n");

			PDEBUG(DCALL, DEBUG_INFO, "Indicate MNCC release towards fixed network\n");
			mncc->msg_type = MNCC_REL_IND;
			mncc_up(buf, sizeof(struct gsm_mncc));
			goto release;
		}
		new_state_process(callref, PROCESS_DISCONNECT);
		PDEBUG(DCALL, DEBUG_INFO, "Call disconnected\n");
		call_down_disconnect(callref, mncc->cause.value);
		timer_start(&process->timer, DISC_TIMEOUT);
		break;
	case MNCC_REL_REQ:
		PDEBUG(DCALL, DEBUG_INFO, "Received MNCC release from fixed network with cause %d\n", mncc->cause.value);

release:
		destroy_process(callref);
		PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network\n");
		call_down_release(callref, mncc->cause.value);
		break;
	}
}

int (*mncc_up)(uint8_t *buf, int length) = NULL;

/* break down of MNCC socket */
void mncc_flush(void)
{
	while(process_head) {
		PDEBUG(DCALL, DEBUG_NOTICE, "MNCC socket closed, releasing call\n");
		call_down_release(process_head->callref, CAUSE_TEMPFAIL);
		destroy_process(process_head->callref);
		/* note: callref is released by sender's instance */
	}
}

