/* $Id$ */

#include <cstdarg>
#include <cstring>
#include <inttypes.h>
#include "throw.h"
#include "file.h"
#include "comm.h"
#include "cmds.h"
#include "file.h"
#include "util.h"
#include "chan.h"
#include "cli.h"

static void SendAck(CComm &comm)
{
	char ch(0x06);
	comm.Send(&ch, sizeof(ch));
}

static void RecvAck(CComm &comm)
{
	unsigned char ch;

	comm.Recv(&ch, sizeof(ch));

	if (ch != 0x06)
		Throw("Invalid ACK char received: 0x%02X", ch);
}

static void InitComm(CComm &comm, CCli &cli)
{
	if (cli.GetPort().empty())
		Throw("Port not specified");

	comm.Init(cli.GetPort().c_str());
}

static void InitBinfile(CFileBase &file, CCli &cli)
{
	if (cli.GetBinfile().empty())
		Throw("Binary file not specified");

	file.Open(cli.GetBinfile().c_str());
}

static void InitCsvfile(CFileBase &file, CCli &cli)
{
	if (cli.GetCsvfile().empty())
		Throw("CSV file not specified");

	file.Open(cli.GetCsvfile().c_str());
}

static void PrintFile(CFileWrite &file, const char *fmt, ...)
{
	// xxx static buffer
	char buf[102400];
	va_list ap;

	va_start(ap, fmt);
	size_t sz(vsnprintf(buf, sizeof(buf), fmt, ap));
	va_end(ap);

	file.Write(buf, sz);
}

static void Identify(CComm &comm)
{
	const char s[] = "HiWOUXUN\002";
	comm.Send(s, sizeof(s) - 1);
	RecvAck(comm);

	char ch(0x02);
	comm.Send(&ch, sizeof(ch));
	RecvAck(comm);

	char buf[7];
	comm.Recv(buf, sizeof(buf));
	buf[6] = 0;

	// buf can contain garbage (xxx sanitize to ascii)

	printf("Found radio: %s\n", buf);

	SendAck(comm);
	RecvAck(comm);
}

void CmdTest(int ac, char * const av[])
{
	CCli cli(ac, av);
	CComm comm;

	InitComm(comm, cli);

	Identify(comm);
}

void CmdGet(int ac, char * const av[])
{
	CCli cli(ac, av);
	CComm comm;
	CFileWrite file;

	InitComm(comm, cli);
	InitBinfile(file, cli);

	Identify(comm);

	unsigned char txbuf[4] = {'R', 0x00, 0x00, 0x40};
	unsigned char rxbuf[0x44];

	for (uint16_t addr(0); addr < 0x2000; addr += 0x40)
	{
		printf("Reading address: 0x%04X (%u%% done)\r", addr, addr * 100 / 0x2000);
		fflush(stdout);

		Util::Explode16(txbuf + 1, addr);

		comm.Send(txbuf, sizeof(txbuf));
		comm.Recv(rxbuf, sizeof(rxbuf));

		if (rxbuf[0] != 'W' || memcmp(txbuf + 1, rxbuf + 1, 3))
			Throw("Cannot read data from addr 0x%04X: Radio returned: %02X, %02X, %02X, %02X", addr, rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]);

		SendAck(comm);
		RecvAck(comm);

		file.Write(rxbuf + 4, sizeof(rxbuf) - 4);
	}

	file.Close();
	printf("\nAll OK\n");
}

void CmdPut(int ac, char * const av[])
{
	CCli cli(ac, av);
	CComm comm;
	CFileRead file;

	InitComm(comm, cli);
	InitBinfile(file, cli);

	Identify(comm);

	unsigned char txbuf[0x14] = {'W', 0x00, 0x00, 0x10};

	for (uint16_t addr(0); addr < 0x2000; addr += 0x10)
	{
		printf("Writing address: 0x%04X (%u%% done)\r", addr, addr * 100 / 0x2000);
		fflush(stdout);

		Util::Explode16(txbuf + 1, addr);

		if (file.Read(txbuf + 4, sizeof(txbuf) - 4) != (sizeof(txbuf) - 4))
			Throw("Error reading binary file - EOF?");

		comm.Send(txbuf, sizeof(txbuf));
		RecvAck(comm);
	}

	file.Close();
	printf("\nAll OK\n");
}

void CmdExport(int ac, char * const av[])
{
	CCli cli(ac, av);
	CFileRead binfile;
	CFileWrite csvfile;

	InitBinfile(binfile, cli);
	InitCsvfile(csvfile, cli);

	unsigned char buf[8192];
	if (binfile.Read(buf, sizeof(buf)) != sizeof(buf))
		Throw("Cannot read binary file - too short?");

	binfile.Close();

	PrintFile(csvfile, "id,name,rxfreq,txfreq,rxctcss,txctcss,busylock,txmode,dunno\n");

	for (size_t i(0); i < 128; ++i)
	{
		Chan::SBin chanbin;
		Chan::SCsv chancsv;

		Chan::BufToBin(chanbin, buf + i * 0x10 + 0x10);
		Chan::BinToCsv(chancsv, chanbin);

		std::string s;
		Chan::CsvToString(s, chancsv, i);
		s += "\n";
		csvfile.Write(s.c_str(), s.size());
	}

	csvfile.Close();
}

void CmdImport(int ac, char * const av[])
{
	CCli cli(ac, av);
	CFileRead binfileread;
	CFileRead csvfile;

	InitBinfile(binfileread, cli);
	InitCsvfile(csvfile, cli);

	unsigned char buf[8192];
	if (binfileread.Read(buf, sizeof(buf)) != sizeof(buf))
		Throw("Cannot read binary file - too short?");

	binfileread.Close();

	Util::ReadLine(csvfile);

	for (size_t i(0); i < 128; ++i)
	{
		std::string s(Util::ReadLine(csvfile));

		Chan::SCsv chancsv;
		Chan::SBin chanbin;

		Chan::StringToCsv(chancsv, s);
		Chan::CsvToBin(chanbin, chancsv);
		Chan::BinToBuf(buf + i * 0x10 + 0x10, chanbin);
	}

	csvfile.Close();

	CFileWrite binfilewrite;
	binfilewrite.Open(cli.GetBinfile().c_str());
	binfilewrite.Write(buf, sizeof(buf));
	binfilewrite.Close();	
}
