/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero General Public License as published   ***
***  by the Free Software Foundation, either version 3 of the License, or    ***
***  (at your option) any later version.                                     ***
***                                                                          ***
***  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 Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <QTest>
using ::QObject;
// using QTEST_MAIN
// using QVERIFY_EXCEPTION_THROWN
#include <qt-mocks/Matchers.h>
// using QVERIFY_THAT
using qt_mocks::equals;

#include <limits>
using std::numeric_limits;
#include <set>
using std::set;

#include <accounting/Account.h>
using drn::accounting::Account;
using drn::accounting::AccountTypes;
#include <accounting/AccountNumberGenerator.h>
using drn::accounting::SequentialAccountNumberGenerator;
#include <foundation/Error.h>
using drn::foundation::Error;


namespace drn
{
namespace accounting
{
namespace tests
{

struct SequentialAccountNumberGeneratorUT : QObject
{
	Q_OBJECT

private slots:
	//{ prefix increment operator Tests

	void prefixIncrementOperator_validParam_ShouldBeNext()
	{
		SequentialAccountNumberGenerator sang{};
		QVERIFY_THAT(++sang, equals(AccountNumber(1)));
		QVERIFY_THAT(*sang, equals(AccountNumber(1)));
	}

	void prefixIncrementOperator_onLastOne_ShouldThrow()
	{
		const AccountNumber max{numeric_limits<quint32>::max()};
		SequentialAccountNumberGenerator sang{max};
		QVERIFY_EXCEPTION_THROWN(++sang, Error);
		QVERIFY_THAT(*sang, equals(max));
	}

	//}

	//{ postfix increment operator Tests

	void postfixIncrementOperator_validParam_ShouldBeNext()
	{
		SequentialAccountNumberGenerator sang{};
		QVERIFY_THAT(sang++, equals(AccountNumber()));
		QVERIFY_THAT(*sang, equals(AccountNumber(1)));
	}

	void postfixIncrementOperator_onLastOne_ShouldThrow()
	{
		const AccountNumber max{numeric_limits<quint32>::max()};
		SequentialAccountNumberGenerator sang{max};
		QVERIFY_EXCEPTION_THROWN(sang++, Error);
		QVERIFY_THAT(*sang, equals(max));
	}

	//}

	//{ prefix decrement operator Tests

	void prefixDecrementOperator_validParam_ShouldBeNext()
	{
		SequentialAccountNumberGenerator sang{AccountNumber{15u}};
		QVERIFY_THAT(--sang, equals(AccountNumber(AccountNumber{14u})));
		QVERIFY_THAT(*sang, equals(AccountNumber(AccountNumber{14u})));
	}

	void prefixDecrementOperator_onFirstOne_ShouldMoveToInvalid()
	{
		const AccountNumber firstOne{1u};
		SequentialAccountNumberGenerator sang{firstOne};
		QVERIFY_NO_THROW(--sang);
		QVERIFY_THAT(*sang, equals(AccountNumber()));
	}

	void prefixDecrementOperator_onInvalid_ShouldThrow()
	{
		const AccountNumber min{};
		Q_ASSERT(min.integer() == numeric_limits<AccountNumber::ValueType>::min());
		SequentialAccountNumberGenerator sang{min};
		QVERIFY_EXCEPTION_THROWN(--sang, Error);
		QVERIFY_THAT(*sang, equals(AccountNumber()));
	}

	//}

	//{ postfix decrement operator Tests

	void postfixDecrementOperator_validParam_ShouldBeNext()
	{
		SequentialAccountNumberGenerator sang{AccountNumber{15u}};
		QVERIFY_THAT(sang--, equals(AccountNumber(15u)));
		QVERIFY_THAT(*sang, equals(AccountNumber(AccountNumber{14u})));
	}

	void postfixDecrementOperator_onFirstOne_ShouldMoveToInvalid()
	{
		const AccountNumber firstOne{1u};
		SequentialAccountNumberGenerator sang{firstOne};
		QVERIFY_NO_THROW(sang--);
		QVERIFY_THAT(*sang, equals(AccountNumber()));
	}

	void postfixDecrementOperator_onInvalid_ShouldThrow()
	{
		const AccountNumber min{};
		Q_ASSERT(min.integer() == numeric_limits<AccountNumber::ValueType>::min());
		SequentialAccountNumberGenerator sang{min};
		QVERIFY_EXCEPTION_THROWN(sang--, Error);
		QVERIFY_THAT(*sang, equals(min));
	}

	//}

	//{ assignment operator Tests

	void assignmentOperator_validParam_ShouldBeNext()
	{
		SequentialAccountNumberGenerator sang{};
		sang = AccountNumber{15u};
		QVERIFY_THAT(*sang, equals(AccountNumber(15u)));
		QVERIFY_THAT(++sang, equals(AccountNumber{16u}));
	}

	//}

	//{ current Tests

	void current_validParam_ShouldBeCurrent()
	{
		const set<Account> accounts{
			{AccountTypes::Asset, AccountCode{AccountNumber{1u}}, Iso4217Codes::USD},
			{AccountTypes::Liability, AccountCode{AccountNumber{15u}}, Iso4217Codes::USD},
			{AccountTypes::Capital, AccountCode{AccountNumber{10u}}, Iso4217Codes::USD}
		};
		SequentialAccountNumberGenerator sang{};
		QVERIFY_THAT(sang.current(accounts), equals(AccountNumber(15u)));
	}

	void current_empty_ShouldBeZero()
	{
		const set<Account> accounts{};
		SequentialAccountNumberGenerator sang{};
		QVERIFY_THAT(sang.current(accounts), equals(AccountNumber()));
	}

	//}

	//{ update Tests

	void update_hasNoValue_ShouldUpdate()
	{
		SequentialAccountNumberGenerator sang{};
		const AccountNumber an{15u};
		sang.update(an);
		QVERIFY_THAT(*sang, equals(an));
	}

	void update_hasSmallerValue_ShouldUpdate()
	{
		SequentialAccountNumberGenerator sang{};
		const AccountNumber an{15u};
		++sang;
		++sang;
		sang.update(an);
		QVERIFY_THAT(*sang, equals(an));
	}

	void update_hasLargerValue_ShouldNotUpdate()
	{
		SequentialAccountNumberGenerator sang{};
		const AccountNumber an{2u};
		++sang;
		++sang;
		++sang;
		++sang;
		++sang;
		sang.update(an);
		QVERIFY_THAT(*sang, equals(AccountNumber(5u)));
	}

	//}
};

}}}

QTEST_MAIN(drn::accounting::tests::SequentialAccountNumberGeneratorUT)
#include "SequentialAccountNumberGenerator-unit-tests.moc"

