/* $Id$ */

#include <cassert>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "owxendian.h"
#include "impexp.h"
#include "throw.h"
#include "util.h"
#include "intl.h"

static void ImportName(unsigned char *data, const std::string s)
{
	if (s.size() > 6)
		Throw(_("Channel name \"%s\" is too long (6 chars max)"), s.c_str());

	memset(data, 0xFF, 6);

	for (size_t i(0); i < s.size(); ++i)
	{
		const char ch(s[i]);

		if (ch >= '0' && ch <= '9')
			data[i] = ch - '0';
		else if (ch >= 'A' && ch <= 'Z')
			data[i] = ch - 'A' + 0x0A;
		else if (ch >= 'a' && ch <= 'z')
			data[i] = ch - 'a' + 0x0A;
		else
			Throw(_("Invalid character in channel name \"%s\", only letters and digits allowed"), s.c_str());
	}
}

static void ImportFreq(unsigned char *data, const std::string s)
{
	if (s.empty() || s == "0")
	{
		memset(data, 0xFF, 4);
		return;
	}

	if (s.size() != 9 || s[3] != '.')
		Throw(_("Frequency \"%s\" must have strict format XXX.XXXXX"), s.c_str());

	std::string hexstr(s.substr(0, 3) + s.substr(4));

	data[3] = Util::FromHex(hexstr.c_str() + 0);
	data[2] = Util::FromHex(hexstr.c_str() + 2);
	data[1] = Util::FromHex(hexstr.c_str() + 4);
	data[0] = Util::FromHex(hexstr.c_str() + 6);
}

static void ImportCTCSS(unsigned char *data, const std::string s)
{
	if (s.empty() || s == "0")
	{
		memset(data, 0xFF, 2);
		return;
	}

	std::string ss(Util::StripDot(s));
	unsigned v(strtol(ss.c_str(), NULL, 10));

#ifdef OWX_LITTLE_ENDIAN
	data[1] = v >> 8;
	data[0] = v & 0xFF;
#else
	data[0] = v >> 8;
	data[1] = v & 0xFF;
#endif
}

static void ImportDeviation(unsigned char *data, const std::string s)
{
	if (s == "narrow")
		*data &= ~0x10;
	else if (s == "wide")
		*data |= 0x10;
	else
		Throw(_("Invalid deviation string \"%s\", must be \"narrow\" or \"wide\""), s.c_str());
}

static void ImportTXP(unsigned char *data, const std::string s)
{
	if (s == "low")
		*data &= ~0x20;
	else if (s == "high")
		*data |= 0x20;
	else
		Throw(_("Invalid TX power string \"%s\", must be \"low\" or \"high\""), s.c_str());
}

static void ImportScan(unsigned char *data, const std::string s)
{
	if (s == "no")
		*data &= ~0x40;
	else if (s == "yes")
		*data |= 0x40;
	else
		Throw(_("Invalid scan string \"%s\", must be \"no\" or \"yes\""), s.c_str());
}

static void ImportBCL(unsigned char *data, const std::string s)
{
	if (s == "no")
		*data = 0x00;
	else if (s == "yes")
		*data = 0x08;
	else if (s.size() == 4 && s.substr(0, 2) == "0x")
	{
		*data = Util::FromHex(s.substr(2));
		return;
	}
	else
		Throw(_("Invalid BCL string \"%s\", must be \"no\" or \"yes\""), s.c_str());

	// xxx - make sure
	if (data[1] & 0x07)
		*data ^= 0xF7;
}

static void ImportOne(unsigned row, unsigned char *data, const CCsv &csv, bool withName)
{
	if (atoi(csv.Read(row, 0).c_str()) != (int) row)
		Throw(_("Invalid channel number at row %u"), row);

	ImportFreq(data + 0x00, csv.Read(row, 2));
	ImportFreq(data + 0x04, csv.Read(row, 3));
	ImportCTCSS(data + 0x08, csv.Read(row, 4));
	ImportCTCSS(data + 0x0A, csv.Read(row, 5));
	ImportDeviation(data + 0x0D, csv.Read(row, 6));
	ImportTXP(data + 0x0D, csv.Read(row, 7));
	ImportScan(data + 0x0D, csv.Read(row, 8));
	ImportBCL(data + 0x0C, csv.Read(row, 9));

	if (withName)
		ImportName(data + 0x1000, csv.Read(row, 1));
}

static void ImportRangeEntry(unsigned char *data, const std::string s)
{
	if (s.size() == 6 && s.substr(0, 2) == "0x")
	{
		data[1] = Util::FromHex(s.substr(2, 2));
		data[0] = Util::FromHex(s.substr(4, 2));
		return;
	}

	unsigned short value(strtol(s.c_str(), NULL, 10));

	if (!(value >= 100 && value <= 200) && !(value >= 400 && value <= 600))
	{
		fprintf(stderr, _("Warning: Value %u seems to be bogus or out of range.\n"), value);
		fprintf(stderr, _("Programming this binary file can render your radio unusable.\n"));
	}

	char buf[6];
	snprintf(buf, sizeof(buf), "%04u", value);

	static const unsigned char transtbl[] = { 0x07, 0x0A, 0x00, 0x09, 0x0B, 0x02, 0x0E, 0x01, 0x03, 0x0F };
	unsigned char values[4];

	for (size_t i(0); i < 4; ++i)
	{
		if (buf[i] < '0' || buf[i] > '9')
		{
			fprintf(stderr, _("Internal frequency range conversion error, could not program the range.\n"));
			fprintf(stderr, _("Do NOT program this memory into the radio, it will render it unusable.\n"));
			fprintf(stderr, _("Please contact me at owx@chmurka.net and send me the range you've put\n"));
			fprintf(stderr, _("into the frequency range table row.\n"));
			Throw(_("Internal frequency range conversion error"));
		}
		values[i] = transtbl[buf[i] - '0'];
	}

	data[0] = (values[0] << 4) | values[1];
	data[1] = (values[2] << 4) | values[3];
}

static void ImportRadio(unsigned chan, unsigned char *data, const CCsv &csv, unsigned row)
{
	char buf[256];
	snprintf(buf, sizeof(buf), _("FM%u"), chan);

	if (csv.Read(row, 0) != buf)
		Throw(_("Invalid radio channel number at row %u"), row);

	std::string s(csv.Read(row, 1));

	if (s.empty() || s == "0")
	{
		memset(data, 0xFF, 2);
		return;
	}

	std::string ss(Util::StripDot(s));
	unsigned v(strtol(ss.c_str(), NULL, 10) - 760);

#ifdef OWX_LITTLE_ENDIAN
	data[0] = v >> 8;
	data[1] = v & 0xFF;
#else
	data[1] = v >> 8;
	data[0] = v & 0xFF;
#endif
}

static void ImportWelcome(unsigned char *data, const std::string s)
{
	memset(data, 0x20, 6);

	size_t idx(0);
	int state(0);
	unsigned char hi(0);

	for (std::string::const_iterator i(s.begin()); i != s.end(); ++i)
	{
		switch (state)
		{
			case 0:
				if (*i == '\\')
					state = 1;
				else
					data[idx++] = *i;
				break;

			case 1:
				hi = Util::FromHexOne(*i);
				state = 2;
				break;

			case 2:
				data[idx++] = (hi << 4) | Util::FromHexOne(*i);
				state = 0;
				break;
		}

		if (idx == 6)
			break;
	}
}

void Import(char *buf, size_t bufsz, const CCsv &csv, const CModel &model)
{
	assert(bufsz == model.GetMemorySize());

	unsigned char *p(reinterpret_cast<unsigned char *>(buf));

	size_t i;
	for (i = 1; i <= model.GetChannelsCount(); ++i)
		ImportOne(i, p + i * 0x10, csv, model.UseChannelNames());

	unsigned offset(model.GetChannelsCount() + 2);

	if(model.AreRangesSupported()) {
		ImportRangeEntry(p + model.GetRangesOffset() + 0x70, csv.Read(offset + 0, 1));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x72, csv.Read(offset + 0, 2));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x78, csv.Read(offset + 1, 1));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x7A, csv.Read(offset + 1, 2));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x74, csv.Read(offset + 2, 1));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x76, csv.Read(offset + 2, 2));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x7C, csv.Read(offset + 3, 1));
		ImportRangeEntry(p + model.GetRangesOffset() + 0x7E, csv.Read(offset + 3, 2));

		offset += 5;
	}

	if (model.UseFmRadio())
	{
		for (i = 1; i < 10; ++i)
			ImportRadio(i, p + 0x840 + i * 2, csv, offset + i - 1);

		offset += 10;
	}

        if (model.GetModel() == CModel::MODEL_UVA1)
        {
		p[0x63B] = strtol(csv.Read(offset + 0, 1).c_str(), NULL, 16);
		p[0x627] = strtol(csv.Read(offset + 1, 1).c_str(), NULL, 10);
		p[0x625] = strtol(csv.Read(offset + 2, 1).c_str(), NULL, 16);
		p[0x623] = strtol(csv.Read(offset + 3, 1).c_str(), NULL, 16);
		p[0x622] = strtol(csv.Read(offset + 4, 1).c_str(), NULL, 16);
		p[0x636] = strtol(csv.Read(offset + 6, 1).c_str(), NULL, 10);
		p[0x62C] = strtol(csv.Read(offset + 7, 1).c_str(), NULL, 16);
		p[0x62D] = strtol(csv.Read(offset + 8, 1).c_str(), NULL, 16);
		p[0x65E] = strtol(csv.Read(offset + 9, 1).c_str(), NULL, 10);

		int tot(strtol(csv.Read(offset + 5, 1).c_str(), NULL, 10) / 15 - 1);

		if (tot < 0)
			tot = 0;

		if (tot > 0x27)
			tot = 0x27;

		p[0x626] = tot;

		offset += 11;
        }

	if (model.UseWelcomeMessage())
	{
		ImportWelcome(p + 0x1827, csv.Read(offset, 1));
		offset += 2;
	}
}
