/* gprs_coding_scheme.cpp
 *
 * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH
 * Author: Jacob Erlbeck <jerlbeck@sysmocom.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */


#include "gprs_coding_scheme.h"

enum Family {
	FAMILY_INVALID,
	FAMILY_A,
	FAMILY_B,
	FAMILY_C,
};

static struct {
	struct {
		uint8_t bytes;
		uint8_t ext_bits;
		uint8_t data_header_bits;
	} uplink, downlink;
	uint8_t data_bytes;
	uint8_t optional_padding_bits;
	enum HeaderType data_hdr;
	enum Family family;
} mcs_info[NUM_SCHEMES] = {
	{{0, 0},   {0, 0},    0,  0,
		HEADER_INVALID, FAMILY_INVALID},
	{{23, 0},  {23, 0},  20,  0,
		HEADER_GPRS_DATA, FAMILY_INVALID},
	{{33, 7},  {33, 7},  30,  0,
		HEADER_GPRS_DATA, FAMILY_INVALID},
	{{39, 3},  {39, 3},  36,  0,
		HEADER_GPRS_DATA, FAMILY_INVALID},
	{{53, 7},  {53, 7},  50,  0,
		HEADER_GPRS_DATA, FAMILY_INVALID},

	{{26, 1},  {26, 1},  22,  0,
		HEADER_EGPRS_DATA_TYPE_3, FAMILY_C},
	{{32, 1},  {32, 1},  28,  0,
		HEADER_EGPRS_DATA_TYPE_3, FAMILY_B},
	{{41, 1},  {41, 1},  37, 48,
		HEADER_EGPRS_DATA_TYPE_3, FAMILY_A},
	{{48, 1},  {48, 1},  44,  0,
		HEADER_EGPRS_DATA_TYPE_3, FAMILY_C},

	{{60, 7},  {59, 6},  56,  0,
		HEADER_EGPRS_DATA_TYPE_2, FAMILY_B},
	{{78, 7},  {77, 6},  74, 48,
		HEADER_EGPRS_DATA_TYPE_2, FAMILY_A},
	{{118, 2}, {117, 4}, 56,  0,
		HEADER_EGPRS_DATA_TYPE_1, FAMILY_B},
	{{142, 2}, {141, 4}, 68,  0,
		HEADER_EGPRS_DATA_TYPE_1, FAMILY_A},
	{{154, 2}, {153, 4}, 74,  0,
		HEADER_EGPRS_DATA_TYPE_1, FAMILY_A},
};

GprsCodingScheme GprsCodingScheme::getBySizeUL(unsigned size)
{
	switch (size) {
		case 23: return GprsCodingScheme(CS1);
		case 27: return GprsCodingScheme(MCS1);
		case 33: return GprsCodingScheme(MCS2);
		case 34: return GprsCodingScheme(CS2);
		case 40: return GprsCodingScheme(CS3);
		case 42: return GprsCodingScheme(MCS3);
		case 49: return GprsCodingScheme(MCS4);
		case 54: return GprsCodingScheme(CS4);
		case 61: return GprsCodingScheme(MCS5);
		case 79: return GprsCodingScheme(MCS6);
		case 119: return GprsCodingScheme(MCS7);
		case 143: return GprsCodingScheme(MCS8);
		case 155: return GprsCodingScheme(MCS9);
	}

	return GprsCodingScheme(UNKNOWN);
}

uint8_t GprsCodingScheme::sizeUL() const
{
	return mcs_info[m_scheme].uplink.bytes + (spareBitsUL() ? 1 : 0);
}

uint8_t GprsCodingScheme::usedSizeUL() const
{
	if (mcs_info[m_scheme].data_hdr == HEADER_GPRS_DATA)
		return mcs_info[m_scheme].uplink.bytes;
	else
		return sizeUL();
}

uint8_t GprsCodingScheme::maxBytesUL() const
{
	return mcs_info[m_scheme].uplink.bytes;
}

uint8_t GprsCodingScheme::spareBitsUL() const
{
	return mcs_info[m_scheme].uplink.ext_bits;
}

uint8_t GprsCodingScheme::sizeDL() const
{
	return mcs_info[m_scheme].downlink.bytes + (spareBitsDL() ? 1 : 0);
}

uint8_t GprsCodingScheme::usedSizeDL() const
{
	if (mcs_info[m_scheme].data_hdr == HEADER_GPRS_DATA)
		return mcs_info[m_scheme].downlink.bytes;
	else
		return sizeDL();
}

uint8_t GprsCodingScheme::maxBytesDL() const
{
	return mcs_info[m_scheme].downlink.bytes;
}

uint8_t GprsCodingScheme::spareBitsDL() const
{
	return mcs_info[m_scheme].downlink.ext_bits;
}

uint8_t GprsCodingScheme::maxDataBlockBytes() const
{
	return mcs_info[m_scheme].data_bytes;
}

uint8_t GprsCodingScheme::optionalPaddingBits() const
{
	return mcs_info[m_scheme].optional_padding_bits;
}

enum HeaderType GprsCodingScheme::headerTypeData() const
{
	return mcs_info[m_scheme].data_hdr;
}

void GprsCodingScheme::inc(enum mcs_kind mode)
{
	if (!isCompatible(mode))
		/* This should not happen. TODO: Use assert? */
		return;

	CodingScheme new_cs(CodingScheme(m_scheme + 1));
	if (!GprsCodingScheme(new_cs).isCompatible(mode))
		/* Clipping, do not change the value */
		return;

	m_scheme = new_cs;
}

void GprsCodingScheme::dec(enum mcs_kind mode)
{
	if (!isCompatible(mode))
		/* This should not happen. TODO: Use assert? */
		return;

	CodingScheme new_cs(CodingScheme(m_scheme - 1));
	if (!GprsCodingScheme(new_cs).isCompatible(mode))
		/* Clipping, do not change the value */
		return;

	m_scheme = new_cs;
}

void GprsCodingScheme::inc()
{
	if (mcs_is_gprs(m_scheme) && m_scheme == CS4)
		return;

	if (mcs_is_edge(m_scheme) && m_scheme == MCS9)
		return;

	if (!isValid())
		return;

	m_scheme = CodingScheme(m_scheme + 1);
}

void GprsCodingScheme::dec()
{
	if (mcs_is_gprs(m_scheme) && m_scheme == CS1)
		return;

	if (mcs_is_edge(m_scheme) && m_scheme == MCS1)
		return;

	if (!isValid())
		return;

	m_scheme = CodingScheme(m_scheme - 1);
}

bool GprsCodingScheme::isFamilyCompatible(GprsCodingScheme o) const
{
	if (*this == o)
		return true;

	if (mcs_info[m_scheme].family == FAMILY_INVALID)
		return false;

	return mcs_info[m_scheme].family == mcs_info[o.m_scheme].family;
}

void GprsCodingScheme::decToSingleBlock(bool *needStuffing)
{
	switch (m_scheme) {
	case MCS7: *needStuffing = false; m_scheme = MCS5; break;
	case MCS8: *needStuffing =  true; m_scheme = MCS6; break;
	case MCS9: *needStuffing = false; m_scheme = MCS6; break;
	default:   *needStuffing = false; break;
	}
}
