/* $Id$ */

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "wouxun.h"
#include "impexp.h"
#include "throw.h"
#include "model.h"
#include "intl.h"
#include "file.h"
#include "cmds.h"
#include "csv.h"
#include "fsk.h"

#define MEMORY_SIZE 0x2000

static void RejectOptions(const CCli &cli, const std::string forbidden)
{
	for (std::string::const_iterator i(forbidden.begin()); i != forbidden.end(); ++i)
		if (cli.Avail(*i))
			Throw(_("Option \"-%c\" not available with this command. See help"), *i);
}

static std::string GetEnv(const std::string &name)
{
	const char *env(getenv(name.c_str()));
	return env ? env : "";
}

static void PrepareWouxun(const CCli &cli, CWouxun &wx, const CModel &m)
{
	std::string port(GetEnv("OWX_PORT"));

	if (cli.Avail('a'))
		Throw(_("Port autodetection not working yet. Sorry :)"));

	if (cli.Avail('p'))
		port = cli.Param('p');

	if (port.empty())
		Throw(_("Port not specified. See help"));

	wx.SetPort(port);

	int timeout(0);
	bool set(false);

	const std::string env(GetEnv("OWX_TIMEOUT"));
	if (!env.empty())
	{
		timeout = atoi(env.c_str());
		set = true;
	}

	if (cli.Avail('t'))
	{
		timeout = atoi(cli.Param('t').c_str());
		set = true;
	}

	if (set)
	{
		if (timeout < 0)
			Throw(_("Invalid timeout specified. Must be positive"));

		wx.SetTimeout(timeout);
	}

	wx.Open(m.GetWelcomeString());

	printf(_("Found radio: %s\n"), wx.GetIDString().c_str());

	if (wx.GetIDString() != m.GetIdString())
	{
		if (cli.Avail('f'))
			printf(_("Warning: This radio was not tested with specified model settings and may not work!\n"));
		else
			Throw(_("Radio ID string mismatches string for specified model (%s)"), m.GetIdString().c_str());
	}
}

void CmdUnknown(const CCli & /* cli */)
{
	Throw(_("Bad command or command not specified. See help"));
}

void CmdCheck(const CCli &cli)
{
	RejectOptions(cli, "ior");

	CModel m(cli.Avail('m') ? cli.Param('m') : "");

	CWouxun wx;
	PrepareWouxun(cli, wx, m);
}

void CmdGet(const CCli &cli)
{
	RejectOptions(cli, "ir");

	if (!cli.Avail('o'))
		Throw(_("Binary file to write to not specified, use -o"));

	CModel m(cli.Avail('m') ? cli.Param('m') : "");

	CFileWrite f;
	f.Open(cli.Param('o').c_str());

	CWouxun wx;
	PrepareWouxun(cli, wx, m);

	std::vector<char> buf;
	buf.resize(m.GetMemorySize());

	for (size_t i(0); i < buf.size(); i += 0x40)
	{
		printf(_("Reading address 0x%04X (%u%% done)\r"), (unsigned) i, (unsigned) (i * 100 / buf.size()));
		fflush(stdout);
		wx.GetPage(i, &buf[i]);
	}

	printf(_("\n"));

	f.Write(&buf[0], buf.size());
	f.Close();
}

static void LoadBinFile(const std::string &path, char *buf, size_t sz)
{
	CFileRead f;
	f.Open(path.c_str());

	if (f.Size() != sz)
		Throw(_("%s: Invalid file size"), path.c_str());

	if (f.Read(buf, sz) != sz)
		Throw(_("%s: Error reading"), path.c_str());
}

void CmdPut(const CCli &cli)
{
	RejectOptions(cli, "o");

	if (!cli.Avail('i'))
		Throw(_("Binary file to read from not specified, use -i"));

	if (!cli.Avail('r'))
		printf(_("Consider using -r\n"));

	CModel m(cli.Avail('m') ? cli.Param('m') : "");

	std::vector<char> buf;
	buf.resize(m.GetMemorySize());
	LoadBinFile(cli.Param('i'), &buf[0], buf.size());

	bool useref(cli.Avail('r'));
	std::vector<char> refbuf;
	refbuf.resize(m.GetMemorySize());

	if (useref)
		LoadBinFile(cli.Param('r'), &refbuf[0], refbuf.size());

	CWouxun wx;
	PrepareWouxun(cli, wx, m);

	for (size_t i(0); i < buf.size(); i += 0x10)
	{
		bool skip(false);
		if (useref && !memcmp(&buf[i], &refbuf[i], 0x10))
			skip = true;

		printf(_("%s address 0x%04X (%u%% done) \r"), skip ? _("Skipping") : _("Writing"), (unsigned) i, (unsigned) (i * 100 / buf.size()));
		fflush(stdout);

		if (!skip)
			wx.PutPage(i, &buf[i]);
	}

	printf(_("\n"));
}

void CmdExport(const CCli &cli)
{
	RejectOptions(cli, "fptr");

	if (!cli.Avail('i'))
		Throw(_("Binary file to export from was not specified"));

	if (!cli.Avail('o'))
		Throw(_("CSV file to export to was not specified"));

	CModel m(cli.Avail('m') ? cli.Param('m') : "");

	std::vector<char> buf;
	buf.resize(m.GetMemorySize());
	LoadBinFile(cli.Param('i'), &buf[0], buf.size());

	CCsv csv(m.GetRows(), m.GetCols());
	Export(csv, &buf[0], buf.size(), m);
	csv.Save(cli.Param('o'));
}

void CmdImport(const CCli &cli)
{
	RejectOptions(cli, "fptr");

	if (!cli.Avail('i'))
		Throw(_("CSV file to import from was not specified"));

	if (!cli.Avail('o'))
		Throw(_("Binary file to import to was not specified"));

	CModel m(cli.Avail('m') ? cli.Param('m') : "");

	CCsv csv(m.GetRows(), m.GetCols());
	csv.Load(cli.Param('i'));

	std::vector<char> buf;
	buf.resize(m.GetMemorySize());
	LoadBinFile(cli.Param('o'), &buf[0], buf.size());

	Import(&buf[0], buf.size(), csv, m);

	CFileWrite f;
	f.Open(cli.Param('o').c_str());
	f.Write(&buf[0], buf.size());
	f.Close();
}

void CmdFsk(const CCli &cli)
{
	RejectOptions(cli, "fioprt");

	CFskEncoder fsk;
	fsk.Write(stdout);

	for (;;)
	{
		char buf[255];
		int rs(fread(buf, 1, sizeof(buf), stdin));
		if (rs == 0)
			break;

		fsk.Feed(buf, rs);
		fsk.Write(stdout);
	}

	fsk.Terminate();
	fsk.Write(stdout);
}
