/*
 * Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <errno.h>

#import "OFApplication.h"
#import "OFArray.h"
#import "OFDDPSocket.h"
#import "OFDictionary.h"
#import "OFNumber.h"
#import "OFOptionsParser.h"
#import "OFPair.h"
#import "OFStdIOStream.h"

#import "OFGetOptionFailedException.h"
#import "OFInvalidFormatException.h"
#import "OFSetOptionFailedException.h"

@interface OFATalkCfg: OFObject <OFApplicationDelegate>
@end

OF_APPLICATION_DELEGATE(OFATalkCfg)

static void
showError(OFString *error)
{
	[OFStdErr writeFormat: @"%@: %@\n", [OFApplication programName], error];
	[OFApplication terminateWithStatus: 1];
}

static void
showConfiguration(OFArray OF_GENERIC(OFString *) *arguments)
{
	OFAppleTalkInterfaceConfiguration config;
	OFNumber *network, *node, *phase;
	OFPair OF_GENERIC(OFNumber *, OFNumber *) *range;

	if (arguments.count == 0)
		showError(@"No interface specified!");
	if (arguments.count > 1)
		showError(@"More than one interface specified!");

	config = [OFDDPSocket configurationForInterface: arguments.firstObject];
	network = [config
	    objectForKey: OFAppleTalkInterfaceConfigurationNetwork];
	node = [config objectForKey: OFAppleTalkInterfaceConfigurationNode];
	phase = [config objectForKey: OFAppleTalkInterfaceConfigurationPhase];
	range = [config
	    objectForKey: OFAppleTalkInterfaceConfigurationNetworkRange];

	if (network == nil || node == nil)
		[OFApplication terminateWithStatus: 1];

	[OFStdOut writeLine: arguments.firstObject];
	[OFStdOut writeFormat: @"\tNetwork:   %04X\n",
			       network.unsignedShortValue];
	[OFStdOut writeFormat: @"\tNode:      %02X\n", node.unsignedCharValue];
	if (phase != nil)
		[OFStdOut writeFormat: @"\tPhase:     %@\n", phase];
	if (range != nil) {
		unsigned short start = [range.firstObject unsignedShortValue];
		unsigned short end = [range.secondObject unsignedShortValue];
		[OFStdOut writeFormat: @"\tNet range: %04X-%04X", start, end];
	}
}

static void
setConfiguration(OFArray OF_GENERIC(OFString *) *arguments,
    OFString *networkString, OFString *nodeString, OFString *phaseString,
    OFString *rangeString)
{
	OFMutableDictionary *config = [OFMutableDictionary dictionary];
	unsigned long long node, network, phase, rangeStart, rangeEnd;
	OFArray OF_GENERIC(OFString *) *rangeArray;

	if (arguments.count == 0)
		showError(@"No interface specified!");
	if (arguments.count > 1)
		showError(@"More than one interface specified!");

	if (networkString == nil)
		showError(@"--network not specified!");
	@try {
		network = [networkString unsignedLongLongValueWithBase: 0];
	} @catch (OFInvalidFormatException *e) {
		showError(@"Invalid format for --network!");
		return;
	}
	if (network > UINT16_MAX)
		showError(@"--network out of range!");
	[config setObject: [OFNumber numberWithUnsignedShort: (uint16_t)network]
		   forKey: OFAppleTalkInterfaceConfigurationNetwork];

	if (nodeString == nil)
		showError(@"--node not specified!");
	@try {
		node = [nodeString unsignedLongLongValueWithBase: 0];
	} @catch (OFInvalidFormatException *e) {
		showError(@"Invalid format for --node!");
		return;
	}
	if (node > UINT8_MAX)
		showError(@"--node out of range!");
	[config setObject: [OFNumber numberWithUnsignedChar: (uint8_t)node]
		   forKey: OFAppleTalkInterfaceConfigurationNode];

	if (phaseString != nil) {
		@try {
			phase = [phaseString unsignedLongLongValueWithBase: 0];
		} @catch (OFInvalidFormatException *e) {
			showError(@"Invalid format for "@"--phase!");
			return;
		}

		if (phase > 2)
			showError(@"--phase out of range!");

		[config setObject: [OFNumber
				       numberWithUnsignedChar: (uint8_t)phase]
			   forKey: OFAppleTalkInterfaceConfigurationPhase];
	}

	if (rangeString != nil) {
		const OFAppleTalkInterfaceConfigurationKey key =
		    OFAppleTalkInterfaceConfigurationNetworkRange;
		OFPair *range;

		rangeArray = [rangeString componentsSeparatedByString: @"-"];
		if (rangeArray.count != 2)
			showError(@"Invalid format for --range!");

		@try {
			rangeStart = [[rangeArray objectAtIndex: 0]
			    unsignedLongLongValueWithBase: 0];
			rangeEnd = [[rangeArray objectAtIndex: 1]
			    unsignedLongLongValueWithBase: 0];
		} @catch (OFInvalidFormatException *e) {
			showError(@"Invalid format for --range!");
			return;
		}
		if (rangeStart > UINT16_MAX || rangeEnd > UINT16_MAX)
			showError(@"--range out of range!");

		range = [OFPair
		    pairWithFirstObject: [OFNumber numberWithUnsignedShort:
					     (uint16_t)rangeStart]
			   secondObject: [OFNumber numberWithUnsignedShort:
					     (uint16_t)rangeEnd]];
		[config setObject: range forKey: key];
	}

	@try {
		[OFDDPSocket setConfiguration: config
				 forInterface: arguments.firstObject];
	} @catch (OFSetOptionFailedException *e) {
		showError([OFString stringWithFormat:
		    @"Setting configuration failed: %@", e]);
	}
}

@implementation OFATalkCfg
- (void)applicationDidFinishLaunching: (OFNotification *)notification
{
	OFString *nodeString = nil, *networkString = nil, *phaseString = nil;
	OFString *rangeString = nil;
	const OFOptionsParserOption options[] = {
		{ '\0', @"network", 1, NULL, &networkString },
		{ '\0', @"node", 1, NULL, &nodeString },
		{ '\0', @"phase", 1, NULL, &phaseString },
		{ '\0', @"range", 1, NULL, &rangeString },
		{ '\0', nil, 0, NULL, NULL }
	};
	OFOptionsParser *optionsParser =
	    [OFOptionsParser parserWithOptions: options];
	OFUnichar option;

	while ((option = [optionsParser nextOption]) != '\0') {
		switch (option) {
		case ':':
			if (optionsParser.lastLongOption != nil)
				[OFStdErr writeFormat:
				    @"%@: Argument for option --%@ missing\n",
				    [OFApplication programName],
				    optionsParser.lastLongOption];
			else
				[OFStdErr writeFormat:
				    @"%@: Argument for option -%C missing\n",
				    [OFApplication programName],
				    optionsParser.lastOption];

			[OFApplication terminateWithStatus: 1];
			break;
		case '?':
			if (optionsParser.lastLongOption != nil)
				[OFStdErr writeFormat:
				    @"%@: Unknown option: --%@\n",
				    [OFApplication programName],
				    optionsParser.lastLongOption];
			else
				[OFStdErr writeFormat:
				    @"%@: Unknown option: -%C\n",
				    [OFApplication programName],
				    optionsParser.lastOption];

			[OFApplication terminateWithStatus: 1];
			break;
		}
	}

	if (networkString == nil && nodeString == nil && phaseString == nil &&
	    rangeString == nil)
		showConfiguration(optionsParser.remainingArguments);
	else
		setConfiguration(optionsParser.remainingArguments,
		    networkString, nodeString, phaseString, rangeString);

	[OFApplication terminate];
}
@end
