/*
 * Copyright (c) 2008-2026 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * This program 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
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#import "ObjFW.h"
#import "ObjFWTest.h"

static const OFNotificationName notificationName =
    @"OFNotificationCenterTestName";
static const OFNotificationName otherNotificationName =
    @"OFNotificationCenterTestOtherName";

@interface OFNotificationCenterTests: OTTestCase
@end

@interface OFNotificationCenterTestClass: OFObject
{
@public
	id _expectedObject;
	int _received;
}

- (void)handleNotification: (OFNotification *)notification;
@end

@implementation OFNotificationCenterTestClass
- (void)handleNotification: (OFNotification *)notification
{
	OTAssertEqualObjects(notification.name, notificationName);
	OTAssert(_expectedObject == nil ||
	    notification.object == _expectedObject);

	_received++;
}
@end

@implementation OFNotificationCenterTests
- (void)testNotificationCenter
{
	OFNotificationCenter *center = [OFNotificationCenter defaultCenter];
	OFNotificationCenterTestClass *test1, *test2, *test3, *test4;
	OFNotification *notification;

	test1 = objc_autorelease([[OFNotificationCenterTestClass alloc] init]);
	test1->_expectedObject = self;
	test2 = objc_autorelease([[OFNotificationCenterTestClass alloc] init]);
	test3 = objc_autorelease([[OFNotificationCenterTestClass alloc] init]);
	test3->_expectedObject = self;
	test4 = objc_autorelease([[OFNotificationCenterTestClass alloc] init]);

	/* First one intentionally added twice to test deduplication. */
	[center addObserver: test1
		   selector: @selector(handleNotification:)
		       name: notificationName
		     object: self];
	[center addObserver: test1
		   selector: @selector(handleNotification:)
		       name: notificationName
		     object: self];
	[center addObserver: test2
		   selector: @selector(handleNotification:)
		       name: notificationName
		     object: nil];
	[center addObserver: test3
		   selector: @selector(handleNotification:)
		       name: otherNotificationName
		     object: self];
	[center addObserver: test4
		   selector: @selector(handleNotification:)
		       name: otherNotificationName
		     object: nil];

	notification = [OFNotification notificationWithName: notificationName
						     object: nil];
	[center postNotification: notification];
	OTAssertEqual(test1->_received, 0);
	OTAssertEqual(test2->_received, 1);
	OTAssertEqual(test3->_received, 0);
	OTAssertEqual(test4->_received, 0);

	notification = [OFNotification notificationWithName: notificationName
						     object: self];
	[center postNotification: notification];
	OTAssertEqual(test1->_received, 1);
	OTAssertEqual(test2->_received, 2);
	OTAssertEqual(test3->_received, 0);
	OTAssertEqual(test4->_received, 0);

	notification = [OFNotification notificationWithName: notificationName
						     object: @"foo"];
	[center postNotification: notification];
	OTAssertEqual(test1->_received, 1);
	OTAssertEqual(test2->_received, 3);
	OTAssertEqual(test3->_received, 0);
	OTAssertEqual(test4->_received, 0);

#ifdef OF_HAVE_BLOCKS
	__block bool received = false;
	id handle;

	notification = [OFNotification notificationWithName: notificationName
						     object: self];
	handle = [center addObserverForName: notificationName
				     object: self
				 usingBlock: ^ (OFNotification *notification_) {
		OTAssertEqual(notification_, notification);
		OTAssertFalse(received);
		received = true;
	    }];
	[center postNotification: notification];
	OTAssertTrue(received);
	OTAssertEqual(test1->_received, 2);
	OTAssertEqual(test2->_received, 4);
	OTAssertEqual(test3->_received, 0);
	OTAssertEqual(test4->_received, 0);

	/* Act like the block test didn't happen. */
	[center removeObserver: handle];
	test1->_received--;
	test2->_received--;
#endif

	[center removeObserver: test1
		      selector: @selector(handleNotification:)
			  name: notificationName
			object: self];
	[center removeObserver: test2
		      selector: @selector(handleNotification:)
			  name: notificationName
			object: nil];
	[center removeObserver: test3
		      selector: @selector(handleNotification:)
			  name: otherNotificationName
			object: self];
	[center removeObserver: test4
		      selector: @selector(handleNotification:)
			  name: otherNotificationName
			object: nil];

	notification = [OFNotification notificationWithName: notificationName
						     object: self];
	[center postNotification: notification];
	OTAssertEqual(test1->_received, 1);
	OTAssertEqual(test2->_received, 3);
	OTAssertEqual(test3->_received, 0);
	OTAssertEqual(test4->_received, 0);
}
@end
