/*
 * Parser for standard guessnet configuration file
 *
 * Copyright (C) 2003--2010  Enrico Zini <enrico@debian.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

#include "GuessnetParser.h"
#include "scanner/scanbag.h"
#include "scanner/dhcp.h"
#include "scanner/peer.h"
#include "scanner/iwscan.h"
#include "scanner/script.h"
#include "scanner/linkbeat.h"
#include "util/output.h"
#include "options.h"

#include <wibble/regexp.h>

#include <string>

#include <memory>

using namespace std;
using namespace scanner;

/* Parse the input from `input'
 * To make it simple, use regexps on input lines instead of implementing a real
 * parser.
 */
void GuessnetParser::parse(FILE* input)
{
#define MACPATTERN "[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}"
#define IPPATTERN "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"
	wibble::ERegexp null_line("^[[:blank:]]*(#.*)?$");
	wibble::ERegexp peer_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"peer[[:blank:]]+"
		"(" IPPATTERN ")"
		"([[:blank:]]+(" MACPATTERN "))?"
		"([[:blank:]]+(" IPPATTERN "))?"
		"[[:blank:]]*$", 7);
	wibble::ERegexp cable_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"missing-cable[[:blank:]]*$", 2);
	wibble::ERegexp script_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"(script|command)[[:blank:]]+(.+)$", 4);
	wibble::ERegexp dhcp_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"dhcp[[:blank:]]*$", 2);
	wibble::ERegexp pppoe_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"pppoe[[:blank:]]*$", 2);
	wibble::ERegexp wireless_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"wireless[[:blank:]]+"
		"(.+)?", 3);
#if 0
	wibble::ERegexp wireless_mac_essid_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"wireless[[:blank:]]+mac[[:blank:]]+([^[:blank:]]+)[[:blank:]]+essid[[:blank:]](.+)$", 4);
	wibble::ERegexp wireless_mac_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"wireless[[:blank:]]+mac[[:blank:]]+([^[:blank:]]+)[[:blank:]]*$", 3);
	wibble::ERegexp wireless_essid_line(
		"^[[:blank:]]*"
		"([^[:blank:]]+)[[:blank:]]+"
		"wireless[[:blank:]]+essid[[:blank:]](.+)$", 3);
#endif
	wibble::ERegexp old_input_line(
		"^[[:blank:]]*(" IPPATTERN ")[[:blank:]]+"
		"(" MACPATTERN ")"
		"[[:blank:]]+(" IPPATTERN ")[[:blank:]]+([[:alnum:]_+-]+)"
		"[[:blank:]]*$", 4);

	string line;
	int linenum = 1;
	int found = 0;
	int c;
	while ((c = fgetc(input)) != EOF)
	{
		if (c != '\n')
			line += c;
		else
		{
			if (null_line.match(line))
			{
				//fprintf(stderr, "EMPTY\n");
			}
			else if (old_input_line.match(line))
			{	
				string src = old_input_line[1];
				string mac = old_input_line[2];
				string ip = old_input_line[3];
				string name = old_input_line[4];
				struct ether_addr macAddr;
				parse_mac(&macAddr, mac);

				debug("parse old input line %s %s %s %s", src.c_str(), mac.c_str(), ip.c_str(), name.c_str());

				IPAddress ipAddr(ip);

				ScanBag::get().add(scanner::Peer::createScan(name, macAddr, ipAddr, src));
				found++;
			}
			else if (peer_line.match(line))
			{
				//fprintf(stderr, "0, %.*s\n", PFSTR(peer_line[0]));
				string name = peer_line[1];
				string ip = peer_line[2];
				string mac = peer_line[4];
				string src = peer_line[6];

				debug("parse peer line %s %s %s %s", name.c_str(), ip.c_str(), mac.c_str(), src.c_str());

				IPAddress ipAddr(ip);

				struct ether_addr macAddr;
				if (mac.empty())
					bzero(&macAddr, sizeof(struct ether_addr));
				else
					parse_mac(&macAddr, mac);

				if (src.empty())
					ScanBag::get().add(scanner::Peer::createScan(name, macAddr, ipAddr));
				else
					ScanBag::get().add(scanner::Peer::createScan(name, macAddr, ipAddr, IPAddress(src)));

				found++;
			}
			else if (wireless_line.match(line))
			{
				debug("parse wireless line");
				//fprintf(stderr, "0, %.*s\n", PFSTR(peer_line[0]));
				string name = wireless_line[1];
				wibble::Splitter tokens("[[:blank:]]\\+", 0);

				// split in a map of key->val
				map<string, string> args;
				string key;
				for (wibble::Splitter::const_iterator i = tokens.begin(wireless_line[2]);
						i != tokens.end(); ++i)
					if (key.empty())
					{
						if (*i == "open")
							args["open"] = "true";
						else if (*i == "closed")
							args["open"] = "false";
						else
							key = *i;
					}
					else
					{
						args.insert(make_pair(key, *i));
						key.clear();
					}
				if (!key.empty())
					warning("Ignoring extra argument \"%s\" at the end of line %d\n",
						key.c_str(), linenum);

				map<string, string>::const_iterator essid = args.find("essid");
				map<string, string>::const_iterator mac = args.find("mac");
				map<string, string>::const_iterator open = args.find("open");
				
				auto_ptr<scanner::WirelessScan> s(new scanner::WirelessScan(name));

				if (essid == args.end() && mac == args.end() && open == args.end())
				{
					warning("Missing ESSID or MAC address or open/closed at line %d: skipping line\n", linenum);
				} else {
					if (essid != args.end())
						s->setESSID(essid->second);

					if (mac != args.end())
					{
						struct ether_addr macAddr;
						parse_mac(&macAddr, mac->second);
						s->setMAC(macAddr);
					}

					if (open != args.end())
					{
						if (open->second == "true")
							s->setOpen(true);
						else if (open->second == "false")
							s->setOpen(false);
						else
							warning("Internal logic error: args[\"open\"] has been set to \"%s\" for line %d\n", open->second.c_str(), linenum);
					}

					s->finaliseInit();
					ScanBag::get().add(s.release());
					found++;
				}
			}
			else if (cable_line.match(line))
			{
				string name = cable_line[1];
				debug("parse cable line %s", name.c_str());
				//fprintf(stderr, "TEST: %.*s\n", PFSTR(cmd));
				//debug("Will use script %.*s to test %.*s\n",
				//	PFSTR(cmd), PFSTR(name));

				ScanBag::get().add(scanner::LinkBeat::createScan(name));
				found++;
			}
			else if (script_line.match(line))
			{
				string name = script_line[1];
				string cmd = script_line[3];
				debug("parse script line %s %s", name.c_str(), cmd.c_str());

				//fprintf(stderr, "TEST: %.*s\n", PFSTR(cmd));
				//debug("Will use script %.*s to test %.*s\n",
				//	PFSTR(cmd), PFSTR(name));

				ScanBag::get().add(scanner::Script::createScan(name, cmd));
				found++;
			}
			else if (dhcp_line.match(line))
			{
				string name = dhcp_line[1];
				debug("parse dhcp line %s", name.c_str());
				//fprintf(stderr, "TEST: %.*s\n", PFSTR(cmd));
				//debug("Will use script %.*s to test %.*s\n",
				//	PFSTR(cmd), PFSTR(name));

				ScanBag::get().add(scanner::DHCP::createScan(name));
				found++;
			}
			else if (pppoe_line.match(line))
			{
				string name = script_line[1];
				debug("parse pppoe line %s", name.c_str());
				//fprintf(stderr, "TEST: %.*s\n", PFSTR(cmd));
				//debug("Will use script %.*s to test %.*s\n",
				//	PFSTR(cmd), PFSTR(name));

				ScanBag::get().add(scanner::Script::createScan(name, string("pppoe -I ") + options.iface + " -A >/dev/null 2>&1"));
				found++;
			}
#if 0
			else if (wireless_mac_essid_line.match(line))
			{
				string name = wireless_mac_essid_line[1];
				ScanBag::get().add(scanner::Script::createScan(name, string(SCRIPTDIR "/test-wireless ") + Environment::get().iface() + " mac " + wireless_mac_essid_line[2] + " essid \"" + wireless_mac_essid_line[3] + "\""));
				found++;
			}
			else if (wireless_mac_line.match(line))
			{
				string name = wireless_mac_line[1];
				ScanBag::get().add(scanner::Script::createScan(name, string(SCRIPTDIR "/test-wireless ") + Environment::get().iface() + " mac " + wireless_mac_line[2]));
				found++;
			}
			else if (wireless_essid_line.match(line))
			{
				string name = wireless_essid_line[1];
				ScanBag::get().add(scanner::Script::createScan(name, string(SCRIPTDIR "/test-wireless ") + Environment::get().iface() + " essid \"" + wireless_essid_line[2] + "\"" ));
				found++;
			}
#endif
			else
			{
				warning("Parse error at line %d: line ignored\n", linenum);
			}
			line = string();
			linenum++;
		}
	}
	debug("%d candidates found in input\n", found);
}

// vim:set ts=4 sw=4:
