/* protocol handling
 *
 * (C) 2021 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/>.
 */

/*
 * How code-word scheduling works on downlink (tx_sched):
 *
 * The DSP mode can be set to CONTROL or TRAFFIC, depending on the mode the
 * channel is working. Depending on that, a function is called for every
 * code-word to be transmitted, one to schedule code-words on control channel,
 * one to schedule code-words on traffic channel.
 *
 * The scheduler uses a state that indicates what was last scheduled, i.e. what
 * is currently transmitted. If nothing is scheduled yet, an IDLE state is set.
 * When switching between CONTROL and TRAFFIC mode, the different states (for
 * each mode) are handled as they would be IDLE state, so that no reset to IDLE
 * state is required when changing DSP mode.
 *
 * An IDLE state results in a startup sequence on control channel (SYNC) or on
 * traffic channel (SYNT), whenever a message must be scheduled. The message to
 * be scheduled depends on the unit states. All units are queried for any
 * message to be scheduled. If no message on control channel need to be
 * scheduled, an ALH message is scheduled, so that random access is possible.
 * On control channel the address conde-words alternate with CCSC code-word.
 *
 * To prevent random access when a unit is requested to transmit more than two
 * code-word, a dummy frame counter is set. Then a dummy AHY message is
 * scheduled, to prevent random access by other units in that slot.
 */

/*
 * How code-word scheduling works on uplink (rx_sched):
 *
 * The DSP mode can be set to CONTROL or TRAFFIC, depending on the mode the
 * channel is working. Depending on that, a function is called for every
 * code-word received, one for code-words on control channel, one for
 * code-words on traffic channel.
 *
 * Most messages have an address code-word only, so the message type is defined
 * by the elements in the code-word. Additional data code-words (that may
 * follow an address code-word) do not have a message type, because they are
 * defined by the previous address code-word. If a message has additional data
 * code-words, a data word counter is set, so that subsequent data code-words
 * are parsed as defined by the address code-word. In case of a CRC error, the
 * message resets into un-synced state, i.e. waiting for next sync + address
 * code-word.
 */

#define CHAN mpt1327->sender.kanal

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <inttypes.h>
#include "../libsample/sample.h"
#include "../liblogging/logging.h"
#include <osmocom/core/timer.h>
#include "../libmobile/call.h"
#include "../libmobile/cause.h"
#include "../libmobile/console.h"
#include <osmocom/cc/message.h>
#include "mpt1327.h"
#include "dsp.h"
#include "message.h"


/* Timers and counters */
#define RESPONSE_TIMEOUT	1,0
#define REPEAT_GTC		1
#define REPEAT_AHY		1
#define REPEAT_AHYC		1
#define REPEAT_AHYX		3
#define REPEAT_CLEAR		3

/* check if number is a valid station ID */
const char *mpt1327_number_valid(const char *number)
{
	int value;
	static char error[256];

	/* assume that the number has valid length(s) and digits */

	value = (number[0] - '0') * 100 + (number[1] - '0') * 10 + (number[2] - '0');
	if (value > 127) {
		sprintf(error, "Prefix '%03d' is not in range 000..127.", value);
		return error;
	}
	value = atoi(number + 3);
	if (value > 8100 || value < 1) {
		sprintf(error, "Ident '%04d' is not in range 0001..8100.", value);
		return error;
	}

	return NULL;
}

/*
 * Sysdef
 */

static mpt1327_sysdef_t sysdef;

void init_sysdef (uint16_t sys, int wt, int per, int pon, int timeout)
{
	memset(&sysdef, 0, sizeof(sysdef));

	sysdef.sys = sys;
	sysdef.wt = wt;
	sysdef.per = per;
	sysdef.pon = pon;
	sysdef.timeout = timeout;
	sysdef.framelength = 3;
	sysdef.bcast_slots = 10; /* every seconds is good */
}

/*
 * Units handling
 */

static mpt1327_unit_t *unit_list = NULL;

#define	UNIT_IDLE		0
#define UNIT_REGISTER_ACK	(1 << 0)	/* need to ack registration */
#define UNIT_DIVERSION_REJ	(1 << 1)	/* need to nack diversion */
#define UNIT_CALLING_REJ	(1 << 2)	/* need to reject call */
#define UNIT_CALLING_AHYC	(1 << 3)	/* need to request SAMIS */
#define UNIT_CALLING_SAMIS	(1 << 4)	/* wait for SAMIS response */
#define UNIT_CALLED_AHY		(1 << 5)	/* need to request ACK */
#define UNIT_CALLED_AHYX	(1 << 6)	/* cancel call */
#define UNIT_CALLED_ACK		(1 << 7)	/* wait for AHY response */
#define UNIT_GTC_P		(1 << 8)	/* need to assign channel (same prefix) */
#define UNIT_GTC_A		(1 << 9)	/* need to assign channel (calling unit) */
#define UNIT_GTC_B		(1 << 10)	/* need to assign channel (called unit) */
#define UNIT_CALL		(1 << 11)	/* established call */
#define UNIT_CALL_CLEAR		(1 << 12)	/* established call */
#define UNIT_CANCEL_ACK		(1 << 13)	/* need to ack cancelation */

static const char *unit_state_name(uint64_t state)
{
	static char invalid[32];

	switch (state) {
	case UNIT_IDLE:
		return "IDLE";
	case UNIT_REGISTER_ACK:
		return "REGISTER-ACK";
	case UNIT_DIVERSION_REJ:
		return "DIVERSION-REJ";
	case UNIT_CALLING_REJ:
		return "CALLING-REJ";
	case UNIT_CALLING_AHYC:
		return "CALLING-AHYC";
	case UNIT_CALLING_SAMIS:
		return "CALLING-SAMIS";
	case UNIT_CALLED_AHY:
		return "CALLED-AHY";
	case UNIT_CALLED_AHYX:
		return "CALLED-AHYX";
	case UNIT_CALLED_ACK:
		return "CALLED-ACK";
	case UNIT_GTC_P:
		return "GTC-BOTH";
	case UNIT_GTC_A:
		return "GTC-OTHER-UNIT";
	case UNIT_GTC_B:
		return "GTC-UNIT";
	case UNIT_CALL:
		return "CALL";
	case UNIT_CALL_CLEAR:
		return "CALL-CLEAR";
	case UNIT_CANCEL_ACK:
		return "CANCEL-ACK";
	}

	sprintf(invalid, "invalid(0x%" PRIx64 ")", state);
	return invalid;
}

static void unit_new_state(mpt1327_unit_t *unit, uint64_t new_state)
{
	LOGP(DMPT1327, LOGL_DEBUG, "Radio Unit (Prefix:%d Ident:%d) state: %s -> %s\n", unit->prefix, unit->ident, unit_state_name(unit->state), unit_state_name(new_state));
	unit->state = new_state;
}

static void unit_timeout(void *data);

static mpt1327_unit_t *get_unit(uint8_t prefix, uint16_t ident)
{
	mpt1327_unit_t **unitp;

	for (unitp = &unit_list; *unitp; unitp = &((*unitp)->next)) {
		if ((*unitp)->prefix == prefix
		 && (*unitp)->ident == ident)
			break;
	}

	if (!(*unitp)) {
		LOGP(DDB, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) added to database\n", prefix, ident);
		*unitp = calloc(1, sizeof(mpt1327_unit_t));
		osmo_timer_setup(&(*unitp)->timer, unit_timeout, (*unitp));
		(*unitp)->state = UNIT_IDLE;
		(*unitp)->prefix = prefix;
		(*unitp)->ident = ident;
	}

	return *unitp;
}

static mpt1327_unit_t *find_unit_state(uint32_t state, mpt1327_t *tc)
{
	mpt1327_unit_t *unit;

	for (unit = unit_list; unit; unit = unit->next) {
		if (tc && unit->tc != tc)
			continue;
		if ((unit->state & state))
			break;
	}

	return unit;
}

mpt1327_unit_t *find_unit_callref(uint32_t callref)
{
	mpt1327_unit_t *unit;

	for (unit = unit_list; unit; unit = unit->next) {
		if ((unit->callref & callref))
			break;
	}

	return unit;
}

static void mpt1327_go_idle(mpt1327_t *mpt1327);
static void mpt1327_release(mpt1327_unit_t *unit);

/* Timeout handling */
static void unit_timeout(void *data)
{
	mpt1327_unit_t *unit = data;

	// FIXME: do some retry
	switch (unit->state) {
	case UNIT_CALLING_SAMIS:
		if (unit->repeat) {
			--unit->repeat;
			LOGP(DMPT1327, LOGL_INFO, "Resend AHYC, because unit timed out.\n");
			unit_new_state(unit, UNIT_CALLING_AHYC);
			break;
		}
		LOGP(DMPT1327, LOGL_INFO, "Unit failed to respond to AHYC, releasing...\n");
		mpt1327_release(unit);
		if (unit->callref) {
			call_up_release(unit->callref, CAUSE_NORMAL);
			unit->callref = 0;
		}
		break;
	case UNIT_CALLED_ACK:
		if (unit->repeat) {
			--unit->repeat;
			LOGP(DMPT1327, LOGL_INFO, "Resend AHY, because unit timed out.\n");
			unit_new_state(unit, UNIT_CALLED_AHY);
			break;
		}
		LOGP(DMPT1327, LOGL_INFO, "Unit failed to respond to AHY, releasing...\n");
		mpt1327_release(unit);
		if (unit->callref) {
			call_up_release(unit->callref, CAUSE_NORMAL);
			unit->callref = 0;
		}
		break;
	case UNIT_CALL:
		LOGP(DMPT1327, LOGL_NOTICE, "Release call, because unit timed out.\n");
		mpt1327_release(unit);
		if (unit->callref) {
			call_up_release(unit->callref, CAUSE_NORMAL);
			unit->callref = 0;
		}
		break;
	default:
		LOGP(DMPT1327, LOGL_ERROR, "Unknown timeout at state 0x%" PRIx64 ", please fix!\n", unit->state);
		break;
	}

}

void flush_units(void)
{
	mpt1327_unit_t *next;

	while (unit_list) {
		next = unit_list->next;
		osmo_timer_del(&unit_list->timer);
		free(unit_list);
		unit_list = next;
	}
}

static void dump_units(void)
{
	mpt1327_unit_t *unit = unit_list;

	LOGP(DDB, LOGL_NOTICE, "Dump of Radio Unit list:\n");
	if (!unit) {
		LOGP(DDB, LOGL_NOTICE, " - No Radio Unit seen yet!\n");
		return;
	}

	while (unit) {
		LOGP(DDB, LOGL_NOTICE, " - Radio Unit (Prefix:%d Ident:%d) seen on this TSC.\n", unit->prefix, unit->ident);
		unit = unit->next;
	}
}

/*
 * bands and channels
 */

static struct mpt1327_band_def {
	const char *name;
	const char *description;
} mpt1327_band_def[] = {
	{ "MPT1343/1", "MPT1343 Sub Band 1"},
	{ "MPT1343/2", "MPT1343 Sub Band 2"},
	{ "Regionet43/1", "Regionet43 410-430 MHz (German band)" },
	{ "Regionet43/2", "Regionet43 445-448 MHz (rarely used in Germany)" },
	{ NULL, NULL }
};

const char *mpt1327_band_name(enum mpt1327_band band)
{
	return mpt1327_band_def[band].name;
}

int mpt1327_band_by_short_name(const char *name)
{
	int i;

	for (i = 0; mpt1327_band_def[i].name; i++) {
		if (!strcasecmp(mpt1327_band_def[i].name, name))
			return i;
	}

	return -1;
}

void mpt1327_band_list(void)
{
	int i;

	printf("Name\t\tDescription\n");
	printf("------------------------------------------------------------------------\n");
	for (i = 0; mpt1327_band_def[i].name; i++)
		printf("%s\t%s\n", mpt1327_band_def[i].name, mpt1327_band_def[i].description);
}

/* convert channel to frequency */
double mpt1327_channel2freq(enum mpt1327_band band, int channel, int uplink)
{
	double freq = 0, offset = 0; // make GCC happy
	int channels = 0;

	switch(band) {
	case BAND_MPT1343_SUB1:
		freq = 177.2125;
		offset = 8.0; /* that's right! */
		channel -= 58;
		channels = 503;
		break;
	case BAND_MPT1343_SUB2:
		freq = 201.2125;
		offset = -8.0;
		channel -= 58;
		channels = 503;
		break;
	case BAND_REGIONET43_SUB1:
		freq = 420.0125;
		offset = -10.0;
		channel -= 1;
		channels = 799;
		break;
	case BAND_REGIONET43_SUB2:
		freq = 445.0125;
		offset = -5.0;
		channel -= 1;
		channels = 239;
		break;
	}

	/* channel out of range */
	if (channel < 0 || channel > channels)
		return 0.0;

	if (uplink == 2)
		return offset * 1e6;

	freq += channel * 0.0125;
	if (uplink)
		freq += offset;

	return freq * 1e6;
}

/* convert channel to chan field */
static uint16_t mpt1327_channel2chan(enum mpt1327_band band, int channel)
{
	uint16_t chan = 0;

	switch(band) {
	case BAND_MPT1343_SUB1:
		chan = channel - 58 + 513;
		break;
	case BAND_MPT1343_SUB2:
		chan = channel - 58 + 1;
		break;
	case BAND_REGIONET43_SUB1:
		chan = channel - 1 + 1;
		break;
	case BAND_REGIONET43_SUB2:
		chan = channel - 1 + 1; // works with DETEWE
		break;
	}

	return chan;
}

static struct mpt1327_channels {
	enum mpt1327_chan_type chan_type;
	const char *short_name;
	const char *long_name;
} mpt1327_channels[] = {
	{ CHAN_TYPE_CC,		"CC",	"control channel" },
	{ CHAN_TYPE_TC,		"TC",	"traffic channel" },
	{ CHAN_TYPE_CC_TC,	"CC/TC","combined control & traffic channel" },
	{ 0, NULL, NULL }
};

void mpt1327_channel_list(void)
{
	int i;

	printf("Type\t\tDescription\n");
	printf("------------------------------------------------------------------------\n");
	for (i = 0; mpt1327_channels[i].long_name; i++)
		printf("%s%s\t%s\n", mpt1327_channels[i].short_name, (strlen(mpt1327_channels[i].short_name) >= 8) ? "" : "\t", mpt1327_channels[i].long_name);
}

int mpt1327_channel_by_short_name(const char *short_name)
{
	int i;

	for (i = 0; mpt1327_channels[i].short_name; i++) {
		if (!strcasecmp(mpt1327_channels[i].short_name, short_name)) {
			LOGP(DMPT1327, LOGL_INFO, "Selecting channel '%s' = %s\n", mpt1327_channels[i].short_name, mpt1327_channels[i].long_name);
			return mpt1327_channels[i].chan_type;
		}
	}

	return -1;
}

const char *chan_type_short_name(enum mpt1327_chan_type chan_type)
{
	int i;

	for (i = 0; mpt1327_channels[i].short_name; i++) {
		if (mpt1327_channels[i].chan_type == chan_type)
			return mpt1327_channels[i].short_name;
	}

	return "invalid";
}

const char *chan_type_long_name(enum mpt1327_chan_type chan_type)
{
	int i;

	for (i = 0; mpt1327_channels[i].long_name; i++) {
		if (mpt1327_channels[i].chan_type == chan_type)
			return mpt1327_channels[i].long_name;
	}

	return "invalid";
}

/*
 * MPT processing
 */

static mpt1327_t *search_free_tc(void)
{
	sender_t *sender;
	mpt1327_t *tc, *cc_tc = NULL;

	for (sender = sender_head; sender; sender = sender->next) {
		tc = (mpt1327_t *) sender;
		if (tc->state != STATE_IDLE)
			continue;
		/* remember combined voice/control/paging channel as second alternative */
		if (tc->chan_type == CHAN_TYPE_CC_TC)
			cc_tc = tc;
		if (tc->chan_type == CHAN_TYPE_TC)
			return tc;
	}

	return cc_tc;

}

static mpt1327_t *search_cc(void)
{
	sender_t *sender;
	mpt1327_t *cc = NULL;

	for (sender = sender_head; sender; sender = sender->next) {
		cc = (mpt1327_t *) sender;
		/* remember combined voice/control/paging channel as second alternative */
		if (cc->chan_type == CHAN_TYPE_CC_TC)
			return cc;
		if (cc->chan_type == CHAN_TYPE_CC)
			return cc;
	}

	return NULL;

}

static const char *mpt1327_state_name(enum mpt1327_state state)
{
	static char invalid[16];

	switch (state) {
	case STATE_NULL:
		return "(NULL)";
	case STATE_IDLE:
		return "IDLE";
	case STATE_BUSY:
		return "BUSY";
	}

	sprintf(invalid, "invalid(%d)", state);
	return invalid;
}

static void mpt1327_display_status(void)
{
	sender_t *sender;
	mpt1327_t *mpt1327;

	display_status_start();
	for (sender = sender_head; sender; sender = sender->next) {
		mpt1327 = (mpt1327_t *) sender;
		display_status_channel(mpt1327->sender.kanal, chan_type_short_name(mpt1327->chan_type), mpt1327_state_name(mpt1327->state));
		if (mpt1327->unit) {
			char unit_id[32];
			sprintf(unit_id, "%d/%d", mpt1327->unit->prefix, mpt1327->unit->ident);
			display_status_subscriber(unit_id, NULL);
		}
	}
	display_status_end();
}

static void mpt1327_new_state(mpt1327_t *mpt1327, enum mpt1327_state new_state, mpt1327_unit_t *unit)
{
	if (mpt1327->state == new_state)
		return;
	LOGP_CHAN(DMPT1327, LOGL_DEBUG, "State change: %s -> %s\n", mpt1327_state_name(mpt1327->state), mpt1327_state_name(new_state));

	/* unlink unit, if linked */
	if (mpt1327->unit) {
		mpt1327->unit->tc = NULL;
		mpt1327->unit = NULL;
	}

	/* link unit, if given */
	if (unit) {
		unit->tc = mpt1327;
		mpt1327->unit = unit;
	}

	mpt1327->state = new_state;
	mpt1327_display_status();
}

static void mpt1327_timeout(void *data);

/* Create transceiver instance and link to a list. */
int mpt1327_create(enum mpt1327_band band, const char *kanal, enum mpt1327_chan_type chan_type, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db)
{
	sender_t *sender;
	mpt1327_t *mpt1327;
	int rc;

	/* check channel matching and set deviation factor */
	if (mpt1327_channel2freq(band, atoi(kanal), 0) == 0.0)
		return -EINVAL;

	for (sender = sender_head; sender; sender = sender->next) {
		mpt1327 = (mpt1327_t *)sender;
		if ((mpt1327->chan_type == CHAN_TYPE_CC || mpt1327->chan_type == CHAN_TYPE_CC_TC)
		 && (chan_type == CHAN_TYPE_CC || chan_type == CHAN_TYPE_CC_TC)) {
			LOGP(DCNETZ, LOGL_NOTICE, "More than one control channel is not supported, please define other channels as traffic channels!\n");
			return -EINVAL;
		}
	}
	mpt1327 = calloc(1, sizeof(mpt1327_t));
	if (!mpt1327) {
		LOGP(DMPT1327, LOGL_ERROR, "No memory!\n");
		return -EIO;
	}

	LOGP(DMPT1327, LOGL_DEBUG, "Creating 'MPT1327' instance for Channel %s on Band %s (sample rate %d).\n", kanal, mpt1327_band_def[band].name, samplerate);

	/* init general part of transceiver */
	rc = sender_create(&mpt1327->sender, kanal, mpt1327_channel2freq(band, atoi(kanal), 0), mpt1327_channel2freq(band, atoi(kanal), 1), device, use_sdr, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE);
	if (rc < 0) {
		LOGP(DMPT1327, LOGL_ERROR, "Failed to init 'Sender' processing!\n");
		goto error;
	}

	/* init audio processing */
	rc = dsp_init_sender(mpt1327, squelch_db);
	if (rc < 0) {
		LOGP(DANETZ, LOGL_ERROR, "Failed to init signal processing!\n");
		goto error;
	}

	/* timers */
	osmo_timer_setup(&mpt1327->timer, mpt1327_timeout, mpt1327);

	mpt1327->band = band;
	mpt1327->chan_type = chan_type;

	/* only accept these valued */
	if (sysdef.framelength != 1 && sysdef.framelength != 3 && sysdef.framelength != 6) {
		LOGP(DMPT1327, LOGL_ERROR, "Invalid frame length %d, please fix!\n", sysdef.framelength);
		abort();
	}
	if (sysdef.wt != 5 && sysdef.wt != 10 && sysdef.wt != 15) {
		LOGP(DMPT1327, LOGL_ERROR, "Invalid WT value %d, please fix!\n", sysdef.wt);
		abort();
	}

	/* go into idle state */
	mpt1327_go_idle(mpt1327);

	LOGP(DMPT1327, LOGL_NOTICE, "Created channel #%s of type '%s' = %s\n", kanal, chan_type_short_name(chan_type), chan_type_long_name(chan_type));

	return 0;

error:
	mpt1327_destroy(&mpt1327->sender);

	return rc;
}

void mpt1327_check_channels(void)
{
	sender_t *sender;
	mpt1327_t *mpt1327;
	int cc = 0, tc = 0;
	int note = 0;

	for (sender = sender_head; sender; sender = sender->next) {
		mpt1327 = (mpt1327_t *) sender;
		if (mpt1327->chan_type == CHAN_TYPE_CC)
			cc = 1;
		if (mpt1327->chan_type == CHAN_TYPE_TC)
			tc = 1;
		if (mpt1327->chan_type == CHAN_TYPE_CC_TC) {
			cc = 1;
			tc = 1;
		}
	}
	if (cc && !tc) {
		LOGP(DMPT1327, LOGL_NOTICE, "\n");
		LOGP(DMPT1327, LOGL_NOTICE, "*** Selected channel(s) can be used for control only.\n");
		LOGP(DMPT1327, LOGL_NOTICE, "*** No call is possible.\n");
		LOGP(DMPT1327, LOGL_NOTICE, "*** Use at least one 'TC'!\n");
		note = 1;
	}
	if (tc && !cc) {
		LOGP(DMPT1327, LOGL_NOTICE, "\n");
		LOGP(DMPT1327, LOGL_NOTICE, "*** Selected channel(s) can be used for traffic only.\n");
		LOGP(DMPT1327, LOGL_NOTICE, "*** No call to the mobile phone is possible.\n");
		LOGP(DMPT1327, LOGL_NOTICE, "*** Use one 'CC'!\n");
		note = 1;
	}
	if (note)
		LOGP(DMPT1327, LOGL_NOTICE, "\n");
}

/* Destroy transceiver instance and unlink from list. */
void mpt1327_destroy(sender_t *sender)
{
	mpt1327_t *mpt1327 = (mpt1327_t *) sender;

	LOGP(DMPT1327, LOGL_DEBUG, "Destroying 'MPT1327' instance for channel = %s.\n", sender->kanal);

	dsp_cleanup_sender(mpt1327);
	osmo_timer_del(&mpt1327->timer);
	sender_destroy(&mpt1327->sender);
	free(sender);
}

/* Abort connection towards mobile station changing to IDLE state */
static void mpt1327_go_idle(mpt1327_t *mpt1327)
{
	osmo_timer_del(&mpt1327->timer);
	mpt1327->pressel_on = 0;

	LOGP(DMPT1327, LOGL_INFO, "Entering IDLE state on channel %s.\n", mpt1327->sender.kanal);
	mpt1327_new_state(mpt1327, STATE_IDLE, NULL);
	memset(&mpt1327->tx_sched, 0, sizeof(mpt1327->tx_sched));
	switch (mpt1327->chan_type) {
	case CHAN_TYPE_CC:
	case CHAN_TYPE_CC_TC:
		mpt1327->tx_sched.state = SCHED_STATE_CC_IDLE;
		mpt1327_set_dsp_mode(mpt1327, DSP_MODE_CONTROL, 0);
		break;
	case CHAN_TYPE_TC:
		mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE;
		mpt1327_set_dsp_mode(mpt1327, DSP_MODE_OFF, 0);
		break;
	}
}

static void mpt1327_release(mpt1327_unit_t *unit)
{
	osmo_timer_del(&unit->timer);

	if (unit->state == UNIT_CALL && unit->tc) {
		/* release all units on traffic channel */
		unit_new_state(unit, UNIT_CALL_CLEAR);
		unit->repeat = REPEAT_CLEAR;
	} else {
		/* release unit on control channel */
		unit_new_state(unit, UNIT_CALLED_AHYX);
		unit->repeat = REPEAT_AHYX;
	}
}

static int gtc_aloha_number(int length)
{
	switch (length) {
	case 1:
		return 1;
	case 3:
		return 2;
	case 6:
		return 3;
	default:
		return 0;
	}
}

/* schedule message on control channel
 *
 * on IDLE state a STARTUP sequence is sent, followed by alternating CCSC and
 * ADDR codewords. if a unit has nothing to send, ALH is transmitted. if a
 * unit wants to send a message, the message is scheduled. this message can
 * be repeated. afterwards, if no unit has something to send, the ALH is
 * scheduled.
 *
 * a dummy slot is used to allow radio unit to allow multi slot response to a
 * request from TSC.
 */
static int mpt1327_send_codeword_control(mpt1327_t *mpt1327, mpt1327_codeword_t *codeword)
{
	mpt1327_unit_t *unit;

	/* CC scheduler, the sched_state is what we have sent */
	switch (mpt1327->tx_sched.state) {
	case SCHED_STATE_CC_ADDR:
		codeword->type = MPT_CCSC;
		codeword->params[MPT_SYS] = sysdef.sys;
		mpt1327->tx_sched.state = SCHED_STATE_CC_CCSC;
		break;
	case SCHED_STATE_CC_STARTUP:
	case SCHED_STATE_CC_CCSC:
		/* count slots for each broadcast */
		if (mpt1327->tx_sched.bcast_count < sysdef.bcast_slots) {
			mpt1327->tx_sched.bcast_count++;
		}
		/* count slots in frame */
		if (!mpt1327->tx_sched.frame_length || mpt1327->tx_sched.frame_count == mpt1327->tx_sched.frame_length) {
			mpt1327->tx_sched.frame_length = sysdef.framelength;
			mpt1327->tx_sched.frame_count = 0;
		}
		/* send out a dummy slot to prevent random access from other units */
		if (mpt1327->tx_sched.dummy_slot) {
			mpt1327->tx_sched.dummy_slot--;
			/* if this is a new frame, make it 1 slot long */
			if (mpt1327->tx_sched.frame_count == 0)
				mpt1327->tx_sched.frame_length = 1;
			codeword->type = MPT_AHY;
			codeword->params[MPT_PFIX] = 0x2a; /* just some alternating pattern (ignored) */
			codeword->params[MPT_IDENT1] = IDENT_DUMMYI;
			codeword->params[MPT_IDENT2] = IDENT_DUMMYI;
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending dummy AHY, to prevent random access while receiving SAMIS\n");
		} else {
			unit = find_unit_state(UNIT_REGISTER_ACK | UNIT_DIVERSION_REJ | UNIT_CALLING_REJ | UNIT_CALLING_AHYC | UNIT_CALLED_AHY | UNIT_GTC_P | UNIT_GTC_A | UNIT_GTC_B | UNIT_CANCEL_ACK | UNIT_CALLED_AHYX, NULL);
			if (!unit) {
				/* if we reached the slot count for broadcast message
				 * AND the frame is not frame 0 (with given length, as long es frame length > 1)
				 * if the frame length is 1 (implies count == 0), then make this message single slot frame
				 */
				if (mpt1327->tx_sched.bcast_count == sysdef.bcast_slots
				 && (mpt1327->tx_sched.frame_count > 0 || mpt1327->tx_sched.frame_length == 1)) {
					mpt1327->tx_sched.bcast_count = 0;
					/* if this is a new frame, make it 1 slot long */
					if (mpt1327->tx_sched.frame_count == 0)
						mpt1327->tx_sched.frame_length = 1;
					codeword->type = MPT_BCAST2;
					codeword->params[MPT_SYS] = sysdef.sys;
					codeword->params[MPT_IVAL] = sysdef.per;
					if (!sysdef.per)
						codeword->params[MPT_PER] = 1;
					if (!sysdef.pon)
					codeword->params[MPT_PON] = 1;
				} else {
					codeword->type = MPT_ALH;
					codeword->params[MPT_PFIX] = 0x2a; /* just some alternating pattern (ignored) */
					codeword->params[MPT_IDENT1] = 0x1555; /* dito */
					codeword->params[MPT_CHAN4] = mpt1327_channel2chan(mpt1327->band, atoi(mpt1327->sender.kanal)) & 0xf;
					codeword->params[MPT_WT] = (sysdef.wt < 5) ? sysdef.wt : sysdef.wt / 5 + 4;
				}
			} else switch (unit->state) {
			case UNIT_REGISTER_ACK: /* ack to register */
				codeword->type = MPT_ACK;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = IDENT_REGI;
				codeword->params[MPT_IDENT2] = unit->ident;
				codeword->params[MPT_QUAL] = 0;
				unit_new_state(unit, UNIT_IDLE);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				break;
			case UNIT_DIVERSION_REJ: /* reject diversion */
				codeword->type = MPT_ACKX;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->called_ident;
				codeword->params[MPT_IDENT2] = unit->ident;
				codeword->params[MPT_QUAL] = 0;
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending negative acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				if (unit->repeat) {
					--unit->repeat;
					break;
				}
				unit_new_state(unit, UNIT_IDLE);
				if (unit->tc)
					mpt1327_go_idle(unit->tc);
				break;
			case UNIT_CALLING_REJ: /* outgoing call rejected, no channel available */
				codeword->type = MPT_ACKX;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->called_ident;
				codeword->params[MPT_IDENT2] = unit->ident;
				codeword->params[MPT_QUAL] = 1;
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending negative acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				if (unit->repeat) {
					--unit->repeat;
					break;
				}
				unit_new_state(unit, UNIT_IDLE);
				if (unit->tc)
					mpt1327_go_idle(unit->tc);
				break;
			case UNIT_CALLING_AHYC: /* request SAMIS for dialing data */
				codeword->type = MPT_AHYC;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->called_ident;
				codeword->params[MPT_IDENT2] = unit->ident; /* implies Mode 1 */
				switch (unit->called_type) {
				case CALLED_TYPE_INTERPFX:
					codeword->params[MPT_SLOTS] = 0x1;
					codeword->params[MPT_DESC] = 0x0;
					break;
				case CALLED_TYPE_PSTN_LONG1:
					codeword->params[MPT_SLOTS] = 0x1;
					codeword->params[MPT_DESC] = 0x1;
					break;
				case CALLED_TYPE_PSTN_LONG2:
					codeword->params[MPT_SLOTS] = 0x2;
					codeword->params[MPT_DESC] = 0x1;
					mpt1327->tx_sched.dummy_slot = 1;
					break;
				case CALLED_TYPE_PBX_LONG:
					codeword->params[MPT_SLOTS] = 0x1;
					codeword->params[MPT_DESC] = 0x2;
					break;
				default:
					LOGP_CHAN(DMPT1327, LOGL_ERROR, "Want to send AHYC, but called_type not set correctly, please fix!\n");
					abort();
				}
				unit_new_state(unit, UNIT_CALLING_SAMIS);
				mpt1327->rx_sched.data_prefix = unit->prefix;
				mpt1327->rx_sched.data_ident = unit->ident;
				LOGP_CHAN(DMPT1327, LOGL_DEBUG, "Starting timer, waiting for response\n");
				osmo_timer_schedule(&unit->timer, RESPONSE_TIMEOUT);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending AHYC, to request SAMIS from Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				break;
			case UNIT_CALLED_AHY: /* call to unit and request ACK from unit */
				codeword->type = MPT_AHY;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->ident;
				codeword->params[MPT_IDENT2] = IDENT_PABXI;
				codeword->params[MPT_D] = 0; /* speech call */
				codeword->params[MPT_POINT] = 0; /* demand ACK from ident1 */
				codeword->params[MPT_CHECK] = 1; /* unit is in contact and accepts calls */
				codeword->params[MPT_E] = 0; /* no emergency call */
				codeword->params[MPT_AD] = 0; /* no appended data */
				unit_new_state(unit, UNIT_CALLED_ACK);
				LOGP_CHAN(DMPT1327, LOGL_DEBUG, "Starting timer, waiting for response\n");
				osmo_timer_schedule(&unit->timer, RESPONSE_TIMEOUT);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending AHY, to request ACK from Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				break;
			case UNIT_GTC_P: /* channel assignment to unit itself and called unit */
				codeword->type = MPT_GTC;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->called_ident;
				codeword->params[MPT_IDENT2] = unit->ident;
				codeword->params[MPT_CHAN] = mpt1327_channel2chan(unit->tc->band, atoi(unit->tc->sender.kanal));
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending channel assignment to calling and called Radio Units (Prefix:%d Ident:%d and Ident:%d)\n", unit->prefix, unit->ident, unit->called_ident);
				if (unit->repeat) {
					--unit->repeat;
					break;
				}
				unit_new_state(unit, UNIT_CALL);
				mpt1327_set_dsp_mode(unit->tc, DSP_MODE_TRAFFIC, 1);
				if (sysdef.timeout)
					osmo_timer_schedule(&unit->timer, sysdef.timeout,0);
				break;
			case UNIT_GTC_B: /* channel assignment to called unit */
				/* NOTE GTC to called unit must be sent before GTC to calling unit (1.3.5.3) */
				codeword->type = MPT_GTC;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->called_ident;
				codeword->params[MPT_IDENT2] = IDENT_DUMMYI;
				codeword->params[MPT_CHAN] = mpt1327_channel2chan(unit->tc->band, atoi(unit->tc->sender.kanal));
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending channel assignment to called Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				if (unit->repeat) {
					--unit->repeat;
					break;
				}
				unit_new_state(unit, UNIT_GTC_A);
				unit->repeat = REPEAT_GTC;
				break;
			case UNIT_GTC_A: /* channel assignment unit itself */
				codeword->type = MPT_GTC;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = IDENT_DUMMYI;
				codeword->params[MPT_IDENT2] = unit->ident;
				codeword->params[MPT_CHAN] = mpt1327_channel2chan(unit->tc->band, atoi(unit->tc->sender.kanal));
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending channel assignment to calling Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				if (unit->repeat) {
					--unit->repeat;
					break;
				}
				unit_new_state(unit, UNIT_CALL);
				mpt1327_set_dsp_mode(unit->tc, DSP_MODE_TRAFFIC, 1);
				if (sysdef.timeout)
					osmo_timer_schedule(&unit->timer, sysdef.timeout,0);
				break;
			case UNIT_CANCEL_ACK:
				codeword->type = MPT_ACK;
				codeword->params[MPT_PFIX] = unit->called_prefix;
				codeword->params[MPT_IDENT1] = unit->called_ident;
				codeword->params[MPT_IDENT2] = unit->ident;
				codeword->params[MPT_QUAL] = 1;
				unit_new_state(unit, UNIT_IDLE);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				break;
			case UNIT_CALLED_AHYX: /* cancel call towards unit */
				codeword->type = MPT_AHYX;
				codeword->params[MPT_PFIX] = unit->prefix;
				codeword->params[MPT_IDENT1] = unit->ident;
				codeword->params[MPT_IDENT2] = IDENT_PABXI;
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending AHYX, to cancel call to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);
				if (unit->repeat) {
					--unit->repeat;
					break;
				}
				unit_new_state(unit, UNIT_IDLE);
				if (unit->tc)
					mpt1327_go_idle(unit->tc);
				break;
			}
		}
		if (codeword->type == MPT_GTC)
			codeword->params[MPT_N] = (mpt1327->tx_sched.frame_count == 0) ? gtc_aloha_number(mpt1327->tx_sched.frame_length) : 0;
		else
			codeword->params[MPT_N] = (mpt1327->tx_sched.frame_count == 0) ? mpt1327->tx_sched.frame_length : 0;
		mpt1327->tx_sched.frame_count++;
		mpt1327->tx_sched.state = SCHED_STATE_CC_ADDR;
		break;
	default:
		/* on dirty state (e.g. changing from TC to CC), we start control channel framing */
		codeword->type = MPT_START_SYNC;
		mpt1327->tx_sched.frame_length = 0;
		mpt1327->tx_sched.state = SCHED_STATE_CC_STARTUP;
		break;
	}

	return 0;
}

/* schedule messages on traffic channel
 *
 * when the unit has a message to send, it, the repeat counter is decreased and
 * a SYNC is sent the next request will send the an ADDR codeword. this will
 * repeat until the repeat counter reaches 0.
 */
static int mpt1327_send_codeword_traffic(mpt1327_t *mpt1327, mpt1327_codeword_t __attribute__((unused)) *codeword)
{
	mpt1327_unit_t *unit;
	mpt1327_t *cc;

	/* TC scheduler */
	switch (mpt1327->tx_sched.state) {
	case SCHED_STATE_TC_IDLE:
	case SCHED_STATE_TC_ADDR:
		/* on idle state or after sending address, we search for a unit that wants to send a message */
		unit = find_unit_state(UNIT_CALL_CLEAR, mpt1327);
		if (!unit) {
			/* no message, so we have nothing to send */
			mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE;
			return -1;
		}
		switch (unit->state) {
		case UNIT_CALL_CLEAR: /* release channel */
			if (!unit->repeat) {
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Done sending clear down on traffic channel, releasing\n");
				unit_new_state(unit, UNIT_IDLE);
				mpt1327_go_idle(mpt1327);
				return -1;
			}
			--unit->repeat;
			break;
		}
		codeword->type = MPT_START_SYNT;
		mpt1327->tx_sched.state = SCHED_STATE_TC_SYNT;
		break;
	case SCHED_STATE_TC_SYNT:
		/* after sending SYNT, we process message that unit wants to send */
		unit = find_unit_state(UNIT_CALL_CLEAR, mpt1327);
		if (!unit) {
			mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE;
			return -1;
		}
		switch (unit->state) {
		case UNIT_CALL_CLEAR: /* release channel */
			codeword->type = MPT_CLEAR;
			codeword->params[MPT_CHAN] = mpt1327_channel2chan(mpt1327->band, atoi(mpt1327->sender.kanal));
			cc = search_cc();
			if (cc)
				codeword->params[MPT_CONT] = mpt1327_channel2chan(cc->band, atoi(cc->sender.kanal));
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Sending clear down on traffic channel\n");
		}
		mpt1327->tx_sched.state = SCHED_STATE_TC_ADDR;
		break;
	default:
		/* on dirty state (e.g. changing from CC to TC), we enter idle state */
		mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE;
		return -1;
	}

	return 0;
}

int mpt1327_send_codeword(mpt1327_t *mpt1327, uint64_t *bits)
{
	mpt1327_codeword_t codeword;
	int rc = -1;

	memset(&codeword, 0, sizeof(codeword));

	switch (mpt1327->dsp_mode) {
	case DSP_MODE_CONTROL:
		rc = mpt1327_send_codeword_control(mpt1327, &codeword);
		break;
	case DSP_MODE_TRAFFIC:
		rc = mpt1327_send_codeword_traffic(mpt1327, &codeword);
		break;
	default:
		;
	}

	if (rc < 0)
		return 0;

	*bits = mpt1327_encode_codeword(&codeword);
	return 64;
}

static void out_setup(mpt1327_unit_t *unit, uint8_t network_type, int network_id)
{
	char caller_id[32], id[16];

	/* setup call */
	LOGP(DMPT1327, LOGL_INFO, "Setup call to network.\n");
	sprintf(caller_id, "%03d%04d", unit->prefix, unit->ident);
	if (network_id)
		sprintf(id, "%d", network_id);
	else
		id[0] = '\0';
	unit->callref = call_up_setup(caller_id, unit->called_number, network_type, id);
}

static void _cancel_pending_call(mpt1327_t *mpt1327, mpt1327_unit_t *unit)
{
	LOGP_CHAN(DMPT1327, LOGL_INFO, "We are already in a call, the phone might have restarted, so we free old channel first.\n");
	mpt1327_go_idle(unit->tc);
	osmo_timer_del(&unit->timer);
	if (unit->callref) {
		call_up_release(unit->callref, CAUSE_NORMAL);
		unit->callref = 0;
	}
}

static void mpt1327_receive_codeword_control(mpt1327_t *mpt1327, mpt1327_codeword_t *codeword)
{
	mpt1327_unit_t *unit;
	mpt1327_t *tc;
	char station_id[8];

	switch (codeword->type) {
	case MPT_RQR: /* register */
		mpt1327_reset_sync(mpt1327); /* message complete */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
		snprintf(station_id, sizeof(station_id), "%03d%04d", unit->prefix, unit->ident);
#pragma GCC diagnostic pop
		console_inscription(station_id);
		LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) registers\n", unit->prefix, unit->ident);
		if (unit->tc)
			_cancel_pending_call(mpt1327, unit);
		unit_new_state(unit, UNIT_REGISTER_ACK);
		break;
	case MPT_RQT: /* diversion */
		mpt1327_reset_sync(mpt1327); /* message complete */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]);
		unit->called_ident = codeword->params[MPT_IDENT1];
		LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) requests diversion\n", unit->prefix, unit->ident);
		if (unit->tc)
			_cancel_pending_call(mpt1327, unit);
		LOGP_CHAN(DMPT1327, LOGL_NOTICE, "Diversion not supported by TSC, rejecting...\n");
		unit_new_state(unit, UNIT_DIVERSION_REJ);
		break;
	case MPT_RQS: /* simple call */
	case MPT_RQE: /* emergency call */
		mpt1327_reset_sync(mpt1327); /* message complete */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]);
		unit->called_ident = codeword->params[MPT_IDENT1];
		LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Ident:%d%s\n", unit->prefix, unit->ident, unit->called_ident, (codeword->type == MPT_RQE) ? " (emergency)" : "");
		if (unit->tc)
			_cancel_pending_call(mpt1327, unit);
		tc = search_free_tc();
		if (!tc) {
			unit_new_state(unit, UNIT_CALLING_REJ);
			LOGP_CHAN(DMPT1327, LOGL_NOTICE, "No free Traffic Channel, call is rejected.\n");
			break;
		}
		if (codeword->params[MPT_EXT]) {
			int exchange;
			unit->called_type = CALLED_TYPE_PBX_SHORT;
			sprintf(unit->called_number, "%d", unit->called_ident);
			exchange = ((codeword->params[MPT_FLAG1] << 1) | codeword->params[MPT_FLAG2]) + 1;
			LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to PBX exchange %d, Number %s\n", exchange, unit->called_number);
			unit_new_state(unit, UNIT_GTC_A);
			unit->repeat = REPEAT_GTC;
			out_setup(unit, OSMO_CC_NETWORK_MPT1327_PBX, exchange);
		} else if (unit->called_ident >= IDENT_PSTNSI1 && unit->called_ident < IDENT_PSTNSI1 + 15) {
			unit->called_type = CALLED_TYPE_PSTN_PRE;
			sprintf(unit->called_number, "%d", unit->called_ident - IDENT_PSTNSI1 + 1);
			LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to PSTN with pre-arranged Number %s\n", unit->called_number);
			unit_new_state(unit, UNIT_GTC_A);
			unit->repeat = REPEAT_GTC;
			out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0);
		} else switch (unit->called_ident) {
		case IDENT_IPFIXI:
			unit->called_type = CALLED_TYPE_INTERPFX;
			LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to Unit/Group %d with different Prefix\n", unit->called_ident);
			unit_new_state(unit, UNIT_CALLING_AHYC);
			unit->repeat = REPEAT_AHYC;
			break;
		case IDENT_ALLI:
			unit->called_type = CALLED_TYPE_SYSTEM;
			LOGP_CHAN(DMPT1327, LOGL_INFO, " -> System wide Call\n");
			unit_new_state(unit, UNIT_GTC_P);
			unit->repeat = REPEAT_GTC;
			break;
		case IDENT_PSTNGI:
			if (codeword->params[MPT_FLAG1]) {
				unit->called_type = CALLED_TYPE_PSTN_LONG2;
				LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to PSTN with long Number (10..31 Digits)\n");
			} else {
				unit->called_type = CALLED_TYPE_PSTN_LONG1;
				LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to PSTN with long Number (1..9 Digits)\n");
			}
			unit_new_state(unit, UNIT_CALLING_AHYC);
			unit->repeat = REPEAT_AHYC;
			break;
		case IDENT_PABXI:
			unit->called_type = CALLED_TYPE_PBX_LONG;
			LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to PBX (long number)\n");
			unit_new_state(unit, UNIT_CALLING_AHYC);
			unit->repeat = REPEAT_AHYC;
			break;
		default:
			unit->called_type = CALLED_TYPE_UNIT;
			unit->called_prefix = unit->prefix;
			LOGP_CHAN(DMPT1327, LOGL_INFO, " -> Call to Unit/Group %d (same Prefix)\n", unit->called_ident);
			unit_new_state(unit, UNIT_GTC_P);
			unit->repeat = REPEAT_GTC;
		}
		LOGP_CHAN(DMPT1327, LOGL_INFO, "Allocating Traffic Channel %s\n", tc->sender.kanal);
		mpt1327_new_state(tc, STATE_BUSY, unit);
		break;
	case MPT_SAMIS: /* SAMIS response */
		unit = get_unit(mpt1327->rx_sched.data_prefix, mpt1327->rx_sched.data_ident);
		if (unit->state != UNIT_CALLING_SAMIS) {
			LOGP_CHAN(DMPT1327, LOGL_ERROR, "Radio Unit (Prefix:%d Ident:%d) sends SAMIS, but not requested\n", unit->prefix, unit->ident);
			break;
		}
		switch (unit->called_type) {
		case CALLED_TYPE_INTERPFX:
			if (codeword->params[MPT_DESC] != 0x0) {
				LOGP_CHAN(DMPT1327, LOGL_ERROR, "Expecting DESC=%d from Radio Unit, but got DESC=%d, dropping!\n", 0x0, (int)codeword->params[MPT_DESC]);
				return;
			}
			unit->called_prefix = codeword->params[MPT_PARAMETERS1] >> 13;
			unit->called_ident = codeword->params[MPT_PARAMETERS1] & 0x1fff;
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Prefix:%d Ident:%d\n", unit->prefix, unit->ident, unit->called_prefix, unit->called_ident);
			unit_new_state(unit, UNIT_GTC_B);
			unit->repeat = REPEAT_GTC;
			break;
		case CALLED_TYPE_PSTN_LONG1:
		case CALLED_TYPE_PSTN_LONG2:
			if (codeword->params[MPT_DESC] != 0x1) {
				LOGP_CHAN(DMPT1327, LOGL_ERROR, "Expecting DESC=%d from Radio Unit, but got DESC=%d, dropping!\n", 0x1, (int)codeword->params[MPT_DESC]);
				return;
			}
			unit->called_number[0] = '0';
			unit->called_number[1] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 16) & 0xf];
			unit->called_number[2] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 12) & 0xf];
			unit->called_number[3] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 8) & 0xf];
			unit->called_number[4] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 4) & 0xf];
			unit->called_number[5] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 0) & 0xf];
			unit->called_number[6] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 12) & 0xf];
			unit->called_number[7] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 8) & 0xf];
			unit->called_number[8] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 4) & 0xf];
			unit->called_number[9] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 0) & 0xf];
			unit->called_number[10] = '\0';
			/* schedule reception of one or two words */
			mpt1327->rx_sched.data_num = codeword->params[MPT_PARAMETERS2] >> 16;
			mpt1327->rx_sched.data_count = 0;
			mpt1327->rx_sched.data_word = MPT_SAMIS_DT;
			if (mpt1327->rx_sched.data_num == 0) {
				osmo_timer_del(&unit->timer);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number);
				out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0);
				unit_new_state(unit, UNIT_GTC_A);
				unit->repeat = REPEAT_GTC;
			}
			break;
		case CALLED_TYPE_PBX_LONG:
			if (codeword->params[MPT_DESC] != 0x2) {
				LOGP_CHAN(DMPT1327, LOGL_ERROR, "Expecting DESC=%d from Radio Unit, but got DESC=%d, dropping!\n", 0x2, (int)codeword->params[MPT_DESC]);
				return;
			}
			unit->called_number[0] = '0';
			unit->called_number[1] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 16) & 0xf];
			unit->called_number[2] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 12) & 0xf];
			unit->called_number[3] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 8) & 0xf];
			unit->called_number[4] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 4) & 0xf];
			unit->called_number[5] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 0) & 0xf];
			unit->called_number[6] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 12) & 0xf];
			unit->called_number[7] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 8) & 0xf];
			unit->called_number[8] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 4) & 0xf];
			unit->called_number[9] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 0) & 0xf];
			unit->called_number[10] = '\0';
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number);
			osmo_timer_del(&unit->timer);
			out_setup(unit, OSMO_CC_NETWORK_MPT1327_PBX, 0);
			unit_new_state(unit, UNIT_GTC_A);
			unit->repeat = REPEAT_GTC;
			break;
		default:
			LOGP_CHAN(DMPT1327, LOGL_ERROR, "Want to receive SAMIS, but called_type not set correctly, please fix!\n");
			abort();
		}
		break;
	case MPT_SAMIS_DT:
		unit = get_unit(mpt1327->rx_sched.data_prefix, mpt1327->rx_sched.data_ident);
		if (unit->state != UNIT_CALLING_SAMIS) {
			LOGP_CHAN(DMPT1327, LOGL_ERROR, "Radio Unit (Prefix:%d Ident:%d) sends SAMIS, but not requested\n", unit->prefix, unit->ident);
			break;
		}
		if (mpt1327->rx_sched.data_count == 1) {
			unit->called_number[10] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 40) & 0xf];
			unit->called_number[11] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 36) & 0xf];
			unit->called_number[12] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 32) & 0xf];
			unit->called_number[13] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 28) & 0xf];
			unit->called_number[14] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 24) & 0xf];
			unit->called_number[15] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 20) & 0xf];
			unit->called_number[16] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 16) & 0xf];
			unit->called_number[17] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 12) & 0xf];
			unit->called_number[18] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 8) & 0xf];
			unit->called_number[19] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 4) & 0xf];
			unit->called_number[20] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 0) & 0xf];
			unit->called_number[21] = '\0';
			if (mpt1327->rx_sched.data_num == 1) {
				osmo_timer_del(&unit->timer);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number);
				out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0);
				unit_new_state(unit, UNIT_GTC_A);
				unit->repeat = REPEAT_GTC;
			}
		} else {
			unit->called_number[21] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 40) & 0xf];
			unit->called_number[22] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 36) & 0xf];
			unit->called_number[23] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 32) & 0xf];
			unit->called_number[24] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 28) & 0xf];
			unit->called_number[25] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 24) & 0xf];
			unit->called_number[26] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 20) & 0xf];
			unit->called_number[27] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 16) & 0xf];
			unit->called_number[28] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 12) & 0xf];
			unit->called_number[29] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 8) & 0xf];
			unit->called_number[30] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 4) & 0xf];
			unit->called_number[31] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 0) & 0xf];
			unit->called_number[32] = '\0';
			mpt1327->rx_sched.data_num = 0; /* just in case it is more than 2 data words */
			osmo_timer_del(&unit->timer);
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number);
			out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0);
			unit_new_state(unit, UNIT_GTC_A);
			unit->repeat = REPEAT_GTC;
		}
		break;
	case MPT_RQX: /* call cancel */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]);
		unit->called_ident = codeword->params[MPT_IDENT1];
		osmo_timer_del(&unit->timer);
		LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) cancels call to %d\n", unit->prefix, unit->ident, unit->called_ident);
		unit_new_state(unit, UNIT_CANCEL_ACK);
		if (unit->tc) {
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Free Traffic Channel %s, because unit cancels on control channel\n", unit->tc->sender.kanal);
			mpt1327_go_idle(unit->tc);
		}
		break;
	case MPT_ACKI: /* ack from unit (not ready, wait for RQQ) */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]);
		osmo_timer_del(&unit->timer);
		if (unit->state == UNIT_CALLED_ACK) {
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) acknowledges call (not yet ready, waiting for RQQ\n", unit->prefix, unit->ident);
			if (unit->callref)
				call_up_alerting(unit->callref);
			break;
		}
		LOGP_CHAN(DMPT1327, LOGL_ERROR, "Radio Unit (Prefix:%d Ident:%d) acknowledges, no call\n", unit->prefix, unit->ident);
		break;
	case MPT_ACK: /* ack from unit */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]);
		osmo_timer_del(&unit->timer);
		if (unit->state == UNIT_CALLED_ACK) {
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) acknowledges call\n", unit->prefix, unit->ident);
answer:
			if (unit->callref) {
				char connected_id[32];
				sprintf(connected_id, "%03d%04d", unit->prefix, unit->ident);
				call_up_answer(unit->callref, connected_id);
			}
			unit_new_state(unit, UNIT_GTC_B);
			unit->repeat = REPEAT_GTC;
			break;
		}
		LOGP_CHAN(DMPT1327, LOGL_ERROR, "Radio Unit (Prefix:%d Ident:%d) acknowledges, no call\n", unit->prefix, unit->ident);
		break;
	case MPT_RQQ: /* status from radio */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]);
		osmo_timer_del(&unit->timer);
		LOGP_CHAN(DMPT1327, LOGL_ERROR, "Radio Unit (Prefix:%d Ident:%d) sends RRQ with STATUS=%d\n", unit->prefix, unit->ident, (int)codeword->params[MPT_STATUS]);
		switch (codeword->params[MPT_STATUS]) {
		case 0x00:
			if (unit->state == UNIT_CALLED_ACK) {
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) answers call\n", unit->prefix, unit->ident);
				// NOTE: GTC acknowledges RQQ
				goto answer;
			}
			break;
		case 0x1f:
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) rejects call, releasing\n", unit->prefix, unit->ident);
			// NOTE: AHYX acknowledges RQQ
			mpt1327_release(unit);
			if (unit->callref) {
				call_up_release(unit->callref, CAUSE_NORMAL);
				unit->callref = 0;
			}
			break;
		}
		break;
	case MPT_ALH: /* control channel Aloha for loopback mode */
	case MPT_ALHS:
	case MPT_ALHD:
	case MPT_ALHE:
	case MPT_ALHR:
	case MPT_ALHX:
	case MPT_ALHF:
		/* schedule reception of CCSC word */
		mpt1327->rx_sched.data_num = 1;
		mpt1327->rx_sched.data_count = 0;
		mpt1327->rx_sched.data_word = MPT_CCSC;
		break;
	default:
		if (mpt1327->sender.loopback)
			return;
		LOGP_CHAN(DMPT1327, LOGL_NOTICE, "Received unsupported codeword '%s' = '%s' on control channel\n", codeword->short_name, codeword->long_name);
	}
}

static void mpt1327_receive_codeword_traffic(mpt1327_t *mpt1327, mpt1327_codeword_t *codeword)
{
	mpt1327_unit_t *unit;

	switch (codeword->type) {
	case MPT_MAINT: /* maintenance message */
		unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]);
		if (codeword->params[MPT_CHAN] != mpt1327_channel2chan(mpt1327->band, atoi(mpt1327->sender.kanal))) {
			LOGP_CHAN(DMPT1327, LOGL_NOTICE, "Radio Unit (Prefix:%d Ident:%d) sends maintenance message on wrong channel %d, ignoring!\n", unit->prefix, unit->ident, (int)codeword->params[MPT_CHAN]);
			return;
		}
		if (!unit->tc) {
			LOGP_CHAN(DMPT1327, LOGL_NOTICE, "Radio Unit (Prefix:%d Ident:%d) sends maintenance, but it has no channel assigned, ignoring!\n", unit->prefix, unit->ident);
			return;
		}
		switch (codeword->params[MPT_OPER]) {
		case OPER_PRESSEL_ON:
			if (sysdef.timeout)
				osmo_timer_schedule(&unit->timer, sysdef.timeout,0);
			mpt1327->pressel_on = 1;
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) starts transmission\n", unit->prefix, unit->ident);
			break;
		case OPER_PRESSEL_OFF:
			if (sysdef.timeout)
				osmo_timer_schedule(&unit->timer, sysdef.timeout,0);
			mpt1327->pressel_on = 0;
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) stops transmission\n", unit->prefix, unit->ident);
			break;
		case OPER_DISCONNECT:
			/* ignore while we send clear message */
			if (unit->state == UNIT_CALL_CLEAR)
				return;
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) disconnects from channel\n", unit->prefix, unit->ident);
			if (unit->state == UNIT_CALL) {
				osmo_timer_del(&unit->timer);
				LOGP_CHAN(DMPT1327, LOGL_INFO, "Free Traffic Channel %s, because the initiator goes on-hook\n", unit->tc->sender.kanal);
				mpt1327_go_idle(unit->tc);
				if (unit->callref) {
					call_up_release(unit->callref, CAUSE_NORMAL);
					unit->callref = 0;
				}
			}
			unit_new_state(unit, UNIT_IDLE);
			break;
		case OPER_PERIODIC:
			if (sysdef.timeout)
				osmo_timer_schedule(&unit->timer, sysdef.timeout,0);
			mpt1327->pressel_on = 1;
			LOGP_CHAN(DMPT1327, LOGL_INFO, "Radio Unit (Prefix:%d Ident:%d) sends periodic message\n", unit->prefix, unit->ident);
			break;
		}
		break;
	default:
		if (mpt1327->sender.loopback)
			return;
		LOGP_CHAN(DMPT1327, LOGL_NOTICE, "Received unsupported codeword '%s' = '%s' on traffic channel\n", codeword->short_name, codeword->long_name);
	}
}

void mpt1327_receive_codeword(mpt1327_t *mpt1327, uint64_t bits, double quality, double level)
{
	mpt1327_codeword_t codeword;
	int rc;

	LOGP_CHAN(DDSP, LOGL_INFO, "RX Level: %.0f%% Quality=%.0f%%\n", level * 100.0, quality * 100.0);

	rc = mpt1327_decode_codeword(&codeword, (mpt1327->rx_sched.data_num) ? mpt1327->rx_sched.data_word : -1, (mpt1327->sender.loopback) ? MPT_DOWN : MPT_UP, bits);
	if (rc < 0) {
		mpt1327->rx_sched.data_num = 0;
		mpt1327_reset_sync(mpt1327); /* message complete */
		return;
	}

	/* count if we have data words */
	if (mpt1327->rx_sched.data_num)
		mpt1327->rx_sched.data_count++;

	switch (mpt1327->dsp_mode) {
	case DSP_MODE_CONTROL:
		mpt1327_receive_codeword_control(mpt1327, &codeword);
		break;
	case DSP_MODE_TRAFFIC:
		mpt1327_receive_codeword_traffic(mpt1327, &codeword);
		break;
	default:
		;
	}

	/* if all data words are received */
	if (mpt1327->rx_sched.data_num && mpt1327->rx_sched.data_count == mpt1327->rx_sched.data_num)
		mpt1327->rx_sched.data_num = 0;

	/* reset receiver unless there is a pending data word to be received */
	if (mpt1327->rx_sched.data_num == 0)
		mpt1327_reset_sync(mpt1327); /* message complete */
}

void mpt1327_signal_indication(mpt1327_t *mpt1327)
{
	/* restart timer, if enabled */
	if (mpt1327->unit && mpt1327->unit->state == UNIT_CALL) {
		if (sysdef.timeout)
			osmo_timer_schedule(&mpt1327->unit->timer, sysdef.timeout,0);
	}
}

/* Timeout handling */
static void mpt1327_timeout(void *data)
{
	mpt1327_t *mpt1327 = data;

	switch (mpt1327->state) {
	default:
		break;
	}
}

/*
 * call control (from upper layer)
 */

/* Call control starts call towards mobile station. */
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
{
	mpt1327_unit_t *unit;
	mpt1327_t *tc;
	uint8_t prefix;
	uint16_t ident;

	/* 1. split number into prefix and ident */
	prefix = (dialing[0] - '0') * 100 + (dialing[1] - '0') * 10 + (dialing[2] - '0');
	ident = atoi(dialing + 3);

	/* 2. check if given number is already in a call, return BUSY */
	unit = get_unit(prefix, ident);
	if (unit->state != UNIT_IDLE) {
		LOGP(DMPT1327, LOGL_NOTICE, "Outgoing call to busy Radio Unit, rejecting!\n");
		return -CAUSE_BUSY;
	}

	/* 3. check if all channels are busy, return NOCHANNEL */
	tc = search_free_tc();
	if (!tc) {
		LOGP(DMPT1327, LOGL_NOTICE, "Outgoing call, but no free channel, rejecting!\n");
		return -CAUSE_NOCHANNEL;
	}

	LOGP(DMPT1327, LOGL_INFO, "Outgoing call to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident);

	/* 4. trying to reach radio unit */
	unit->callref = callref;
	mpt1327_new_state(tc, STATE_BUSY, unit);
	unit_new_state(unit, UNIT_CALLED_AHY);
	unit->repeat = REPEAT_AHY;
	unit->called_prefix = unit->prefix;
	unit->called_ident = unit->ident;

	return 0;
}

void call_down_answer(int __attribute__((unused)) callref, struct timeval __attribute__((unused)) *tv_meter)
{
}

/* Call control sends disconnect (with tones).
 * An active call stays active, so tones and annoucements can be received
 * by mobile station.
 */
void call_down_disconnect(int callref, int cause)
{
	mpt1327_unit_t *unit;

	LOGP(DMPT1327, LOGL_INFO, "Call has been disconnected by network.\n");

	unit = find_unit_callref(callref);
	if (!unit) {
		LOGP(DMPT1327, LOGL_NOTICE, "Outgoing disconnect, but no unit for callref!\n");
		call_up_release(callref, CAUSE_INVALCALLREF);
		return;
	}

	/* Release when not active */
	if (unit->state == UNIT_CALL)
		return;
	LOGP(DMPT1327, LOGL_NOTICE, "Outgoing disconnect, but no call, releasing!\n");
	mpt1327_release(unit);
	unit->callref = 0;

	call_up_release(callref, cause);
}

/* Call control releases call toward mobile station. */
void call_down_release(int callref, __attribute__((unused)) int cause)
{
	mpt1327_unit_t *unit;

	LOGP(DMPT1327, LOGL_INFO, "Call has been released by network, releasing call.\n");

	unit = find_unit_callref(callref);
	if (!unit) {
		LOGP(DMPT1327, LOGL_NOTICE, "Outgoing release, but no unit for callref!\n");
		/* don't send release, because caller already released */
		return;
	}

	LOGP(DMPT1327, LOGL_NOTICE, "Outgoing release, releasing!\n");
	mpt1327_release(unit);
	unit->callref = 0;
}

void dump_info(void)
{
	dump_units();
}

