/*
 * Copyright (c) 2012 Ngewi Fet <ngewif@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gnucash.android.test.unit.model;

import org.gnucash.android.model.Commodity;
import org.gnucash.android.model.Money;
import org.gnucash.android.test.unit.testutil.ShadowCrashlytics;
import org.gnucash.android.test.unit.testutil.ShadowUserVoice;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.math.BigDecimal;
import java.util.Currency;
import java.util.Locale;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 21, packageName = "org.gnucash.android", shadows = {ShadowCrashlytics.class, ShadowUserVoice.class})
public class MoneyTest{

	private static final String CURRENCY_CODE = "EUR";
	private Money mMoneyInEur;
	private int 	mHashcode;
	private String amountString = "15.75";

	@Before
	public void setUp() throws Exception {
		mMoneyInEur = new Money(new BigDecimal(amountString), Commodity.getInstance(CURRENCY_CODE));
		mHashcode = mMoneyInEur.hashCode();
	}

	@Test
	public void testCreation(){
		Locale.setDefault(Locale.US);
		String amount = "12.25";		
		
		Money temp = new Money(amount, CURRENCY_CODE);
		assertThat("12.25").isEqualTo(temp.toPlainString());
		assertThat(temp.getNumerator()).isEqualTo(1225L);
		assertThat(temp.getDenominator()).isEqualTo(100L);

		Commodity commodity = Commodity.getInstance(CURRENCY_CODE);
		temp = new Money(BigDecimal.TEN, commodity);
		
		assertEquals("10.00", temp.asBigDecimal().toPlainString()); //decimal places for EUR currency
		assertEquals(commodity, temp.getCommodity());
		assertThat("10").isNotEqualTo(temp.asBigDecimal().toPlainString());
	}

	@Test
	public void testAddition(){
		Money result = mMoneyInEur.add(new Money("5", CURRENCY_CODE));
		assertEquals("20.75", result.toPlainString());
		assertNotSame(result, mMoneyInEur);
		validateImmutability();				
	}

	@Test(expected = Money.CurrencyMismatchException.class)
	public void testAdditionWithIncompatibleCurrency(){
		Money addend = new Money("4", "USD");
		mMoneyInEur.add(addend);
	}

	@Test
	public void testSubtraction(){
		Money result = mMoneyInEur.subtract(new Money("2", CURRENCY_CODE));
		assertEquals(new BigDecimal("13.75"), result.asBigDecimal());
		assertNotSame(result, mMoneyInEur);
		validateImmutability();		
	}

	@Test(expected = Money.CurrencyMismatchException.class)
	public void testSubtractionWithDifferentCurrency(){
		Money addend = new Money("4", "USD");
		mMoneyInEur.subtract(addend);
	}

	@Test
	public void testMultiplication(){
		Money result = mMoneyInEur.multiply(new Money(BigDecimal.TEN, Commodity.getInstance(CURRENCY_CODE)));
		assertThat("157.50").isEqualTo(result.toPlainString());
		assertThat(result).isNotEqualTo(mMoneyInEur);
		validateImmutability();
	}

	@Test(expected = Money.CurrencyMismatchException.class)
	public void testMultiplicationWithDifferentCurrencies(){
		Money addend = new Money("4", "USD");
		mMoneyInEur.multiply(addend);
	}

	@Test
	public void testDivision(){
		Money result = mMoneyInEur.divide(2);
		assertThat(result.toPlainString()).isEqualTo("7.88");
		assertThat(result).isNotEqualTo(mMoneyInEur);
		validateImmutability();
	}

	@Test(expected = Money.CurrencyMismatchException.class)
	public void testDivisionWithDifferentCurrency(){
		Money addend = new Money("4", "USD");
		mMoneyInEur.divide(addend);
	}

	@Test
	public void testNegation(){
		Money result = mMoneyInEur.negate();
		assertThat(result.toPlainString()).startsWith("-");
		validateImmutability();
	}

	@Test
	public void testFractionParts(){
		Money money = new Money("14.15", "USD");
		assertThat(money.getNumerator()).isEqualTo(1415L);
		assertThat(money.getDenominator()).isEqualTo(100L);

		money = new Money("125", "JPY");
		assertThat(money.getNumerator()).isEqualTo(125L);
		assertThat(money.getDenominator()).isEqualTo(1L);
	}

	@Test
	public void nonMatchingCommodityFraction_shouldThrowException(){
		Money money = new Money("12.345", "JPY");
		assertThat(money.getNumerator()).isEqualTo(12L);
		assertThat(money.getDenominator()).isEqualTo(1);
	}

	@Test
	public void testPrinting(){
		assertEquals(mMoneyInEur.asString(), mMoneyInEur.toPlainString());
		assertEquals(amountString, mMoneyInEur.asString());

		// the unicode for Euro symbol is \u20AC

		String symbol = Currency.getInstance("EUR").getSymbol(Locale.GERMANY);
		String actualOuputDE = mMoneyInEur.formattedString(Locale.GERMANY);
		assertThat(actualOuputDE).isEqualTo("15,75 " + symbol);

		symbol = Currency.getInstance("EUR").getSymbol(Locale.GERMANY);
		String actualOuputUS = mMoneyInEur.formattedString(Locale.US);
		assertThat(actualOuputUS).isEqualTo(symbol + "15.75");
		
		//always prints with 2 decimal places only
		Money some = new Money("9.7469", CURRENCY_CODE);
		assertEquals("9.75", some.asString());
	}

	public void validateImmutability(){
		assertEquals(mHashcode, mMoneyInEur.hashCode());
		assertEquals(amountString, mMoneyInEur.toPlainString());
		assertEquals(CURRENCY_CODE, mMoneyInEur.getCommodity().getCurrencyCode());
	}
	
}
