/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.myfaces.trinidad.convert;

import java.util.Locale;

import javax.faces.convert.ConverterException;
import javax.faces.convert.NumberConverter;
import javax.faces.component.UIComponent;

import org.apache.myfaces.trinidad.convert.ConverterTestCase;
import org.apache.myfaces.trinidadbuild.test.MockUIComponentWrapper;
import org.apache.shale.test.mock.MockFacesContext;
import org.jmock.Mock;

/**
 * Test NumberConverter
 * @version $Name: $ ($version: $) $Date: 16-aug-2005.15:12:23 $
 */
public abstract class NumberConverterTestCase extends ConverterTestCase
{
  public NumberConverterTestCase(String name)
  {
    super(name);
  }

  /**
   * Test when context is set to null
   */
  public void testNullContext()
  {
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    MockUIComponentWrapper wrapper = new MockUIComponentWrapper(mock, component);
    NumberConverter converter = getNumberConverter();

    doTestNullContext(wrapper, converter);
  }

  public void testNullComponent()
  {
    NumberConverter converter  = getNumberConverter();

    doTestNullComponent(facesContext, converter);
  }

  /**
   * Tests that null returns immediately.
   *
   * @throws ConverterException  when test fails
   */
  public void testNullInputValue() throws ConverterException
  {
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    MockUIComponentWrapper wrapper = new MockUIComponentWrapper(mock, component);
    NumberConverter converter  = getNumberConverter();

    doTestNull(facesContext, wrapper, converter);
  }

  public void testEmptyValueConversion()
  {
    super.doTestBlankValue(getNumberConverter());
  }

  public void testValueType()
  {
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    setFacesContext(facesContext);
    try
    {
      String input = "123";
      NumberConverter converter = getNumberConverter();
      Object number = converter.getAsObject(facesContext, component, input);
      assertEquals(true, number instanceof Number);
      assertEquals(true, (((Number)number).intValue() == 123));

      String outVal = converter.getAsString(facesContext, component, number);
      assertEquals(input, outVal);
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  public void testAppropriateFormatsArePicked()
  {
    // check if appropriate formtas based on the types are chosen.
    // like numeric, currency, percent are picked. Chose pattern if set avoiding
    // types

    String[] patterns = {"##,##",null, null, null,null};

    //pick pattern, numeric, percent, currency
    String[] types = {null,"number", "percent", "currency", "currency"};
    String[] inputValues = {"99,99","99", "99%","$99", "$99.00"} ;
    Number[] expectedValues = {new Long(9999), new Long(99), new Double(0.99), new Long(99), new Long(99)};
    String[] expectedStringValues = {"99,99","99", "99%","$99.00", "$99.00"} ;
    Locale usLocl = Locale.US;
    Locale[] locales = {usLocl, usLocl, usLocl, usLocl,Locale.CANADA};

    NumberConverter nconv = getNumberConverter();

    for (int i = 0; i < patterns.length; i++)
    {
      Mock mock = mock(UIComponent.class);
      UIComponent component = (UIComponent) mock.proxy();

      setFacesContext(facesContext);
      try
      {
        nconv.setPattern(patterns[i]);
        nconv.setType(types[i]);
        nconv.setLocale(locales[i]);
        
        Object convValue = nconv.getAsObject(facesContext, component, inputValues[i]);

        // Trinidad does BigDecimal, for some reasons.
        // see TRINIDAD-1124
        if(i==2)
        {
          convValue = ((Number) convValue).doubleValue();
        }
        else
        {
          convValue = ((Number) convValue).longValue();
        }

        assertEquals(expectedValues[i], convValue);
        
        String outValue = nconv.getAsString(facesContext, component, expectedValues[i]);
        
        assertEquals(expectedStringValues[i], outValue);
      }
      finally
      {
        setFacesContext(null);
      }
      mock.verify();
    }
  }

  public void testStateHolderSaveRestore()
  {
    NumberConverter converter = getNumberConverter();

    NumberConverter restoreConverter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    MockUIComponentWrapper wrapper = new MockUIComponentWrapper(mock, component);

    for (int i = 0; i < _LOCALES.length; i++)
    {
      converter.setLocale(_LOCALES[i]);
      restoreConverter.setLocale(_LOCALES[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setCurrencyCode( _CURRENCY_CODES[i]);
      restoreConverter.setCurrencyCode( _CURRENCY_CODES[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setCurrencySymbol(_CURRENCY_SYMBOLS[i]);
      restoreConverter.setCurrencySymbol(_CURRENCY_SYMBOLS[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setIntegerOnly(_INTEGER_ONLY[1]);
      restoreConverter.setIntegerOnly(_INTEGER_ONLY[1]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setMaxFractionDigits(_MAX_FRACTION_DIGITS[i]);
      restoreConverter.setMaxFractionDigits(_MAX_FRACTION_DIGITS[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setMaxIntegerDigits(_MAX_INT_DIGITS[i]);
      restoreConverter.setMaxIntegerDigits(_MAX_INT_DIGITS[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setMinFractionDigits(_MIN_FRACT_DIGITS[i]);
      restoreConverter.setMinFractionDigits(_MIN_FRACT_DIGITS[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setMinIntegerDigits(_MIN_INT_DIGITS[i]);
      restoreConverter.setMinIntegerDigits(_MIN_INT_DIGITS[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setPattern( _PATTTERNS[i]);
      restoreConverter.setPattern(_PATTTERNS[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setTransient(_TRANSIENT[i]);
      restoreConverter.setTransient(_TRANSIENT[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

      converter.setType(_TYPES[i]);
      doTestStateHolderSaveRestore(converter, restoreConverter, facesContext, wrapper);

    }
  }

//  public void testFranceLocale()
//  {
//    NumberConverter converter = getNumberConverter();
//    Mock mock = mock(UIComponent.class);
//    UIComponent comp = (UIComponent) mock.proxy();
//    
//    converter.setLocale(Locale.FRANCE);
//    converter.setType("currency");
//    Double d = new Double(12345.68d);
//    
//    setFacesContext(facesContext);
//    try
//    {
//      String convertedString = converter.getAsString(facesContext, comp, d);
//      assertEquals("12 345,68 €", convertedString);
//    }
//    finally
//    {
//      setFacesContext(null);
//    }
//    mock.verify();
//  }
  
  public void testCurrencyCodeIsHonoured()
  {
    NumberConverter converter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();

    converter.setLocale(Locale.US);
    converter.setType("currency");
    Double  value = new Double(99);

    setFacesContext(facesContext);
    try
    {
      String outPut = converter.getAsString(facesContext, component, value);
      assertEquals("$99.00", outPut);
      //Locale is US. By general convention the output prefix would be '$'
      // since we set the currency code to 'DEM' value should be DEM[value]
      converter.setCurrencyCode("DEM");
      
      outPut = converter.getAsString(facesContext, component, value);
      assertEquals("DEM99.00", outPut);
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  public void testCurrencyCodeIsHonouredWhenCurrencyCodeAndCurrencySymbolIsSet()
  {
    NumberConverter converter   = getNumberConverter();
    Mock mock = buildMockUIComponent(2);
    UIComponent component = (UIComponent) mock.proxy();

    converter.setLocale(Locale.US);
    converter.setType("currency");
    Double value = new Double(99);

    setFacesContext(facesContext);
    try
    {
      String outPut = converter.getAsString(facesContext, component, value);
      assertEquals("$99.00", outPut);
      //Locale is US. By general convention the output prefix would be '$'
      // since we set the currency code to 'DEM' value should be DEM[value]
      converter.setCurrencyCode("DEM");
      
      // Let us set the symbol to '*'. This should not take effect, since currency
      // code is set.
      converter.setCurrencySymbol("*");
      
      outPut = converter.getAsString(facesContext, component, value);
      assertEquals("DEM99.00", outPut);
      try
      {
        if(converter.getAsObject(facesContext, component, "DEM99.00") instanceof Number)
        {
          // FIXME =-= sobryan:  this is not reporting an error as of
          // JSF 1.1_02 - should it? I'm thinking not because adam documented
          // that the error was not reported in JSF 1.2 either.
          //fail("Exception should occur - since currency should not be considered while formatting");
        }
      }
      catch(Exception e)
      {
        ;//Expected to fail.
      }
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  public void testCurrencySymbolIsHonoured()
  {
    NumberConverter converter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    converter.setLocale(Locale.US);
    converter.setType("currency");
    Double  value = new Double(99);
    //Locale is US. By general convention the output prefix would be '$'
    // since we set currency symbol to '*' we should get the value to be *99.00
    converter.setCurrencySymbol("*");
    
    setFacesContext(facesContext);
    try
    {
      String outPut = converter.getAsString(facesContext, component, value);
      assertEquals("*99.00", outPut);
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  public void testIntegerOnlyIsHonoured()
  {
    // integerOnly is used only while parsing to create number objects
    NumberConverter converter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    converter.setLocale(Locale.US);

    String[] inputs = {"23.10", "44.90876", "11111", "67859.0001"};
    Number[] expectedValues = {new Long(23), new Long(44), new Long(11111), new Long(67859)};

    setFacesContext(facesContext);
    try
    {
      for (int i = 0; i < inputs.length; i++)
      {
        converter.setIntegerOnly(true);
        Number num = (Number) converter.getAsObject(facesContext, component, inputs[i]);
        assertEquals(expectedValues[i], num);
      }
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }


  public void testSettingFractDigitsAndSettingMinDigitsDoesNotAffectParsing()
  {
    // integerOnly is used only while parsing to create number objects
    NumberConverter converter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    converter.setLocale(Locale.US);

    String[] inputs = {"23.10", "44.90876", "11111", "67859.0001"};
    Number[] expectedValues = {new Long(23), new Long(44), new Long(11111), new Long(67859)};

    setFacesContext(facesContext);
    try
    {
      for (int i = 0; i < inputs.length; i++)
      {
        // setting these values should not affect parsing.
        converter.setMaxFractionDigits(10);
        converter.setMaxIntegerDigits(1);
        converter.setMinFractionDigits(1);
        converter.setMinFractionDigits(0);
        
        // this should be taken care by the parsing code
        converter.setIntegerOnly(true);
        Number num = (Number) converter.getAsObject(facesContext, component, inputs[i]);
        assertEquals(expectedValues[i], num);
      }
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  public void testLocaleIsPickedUpFromViewRoot()
  {

    NumberConverter converter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();

    facesContext.getViewRoot().setLocale(Locale.US);

    String input = "1234.56";

    setFacesContext(facesContext);
    try
    {
      // if we get a valid object, implies locale was indeed picked up.
      // otherwise we would have got a null pointer exception or other exception
      Object value = converter.getAsObject(facesContext, component, input);
      assertEquals(new Double(1234.56), ((Number)value).doubleValue());
    }
    finally
    {
      setFacesContext(null);
    }
  }

  public void testGroupingIsHonoured()
  {
    Number[] inputValues = {new Long(9999), new Long(99), new Double(0.99), new Double(99999.567), new Long(9999)};
    boolean [] isGroupingUsed = {true, true, true, false, false };
    String[] expectedValues = {"9,999", "99", "0.99", "99999.567", "9999"};

    NumberConverter converter = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();
    converter.setLocale(Locale.US);
    setFacesContext(facesContext);
    try
    {
      for (int i = 0; i < inputValues.length; i++)
      {
        converter.setGroupingUsed(isGroupingUsed[i]);
        String out = converter.getAsString(facesContext, component, inputValues[i]);
        assertEquals(expectedValues[i], out);
      }
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  public void testStrictnessOfConversion()
  {
    String[] inputValues = {"123ABC", "22.22.2" };
    Mock mock = buildMockUIComponent(inputValues.length * 3);
    UIComponent component = (UIComponent) mock.proxy();
    MockUIComponentWrapper wrapper = new MockUIComponentWrapper(mock, component);

    for (int i = 0; i < inputValues.length; i++)
    {
      doTestStrictNess(facesContext, wrapper, Locale.US, inputValues[i]);
    }
    mock.verify();
  }

  public void testSettingFractDigitsAndSettingMinDigitsAreHononured()
  {
    Number[] inputValues = {new Long(1234), new Double(1234.5678), new Double(1234), new Double(10.00)};
    String[] expectedValues = {"1,234", "34.57", "1,234", "10.00"};

    int[] maxFractDigits = {0, 2, 2, 2};
    int[] maxIntDigits   = {4, 2, 4, 3};
    int[] minIntDigits   = {4, 1, 2, 1};
    int[] minFractDigits = {0, 2, 0, 2};

    NumberConverter converter   = getNumberConverter();
    Mock mock = mock(UIComponent.class);
    UIComponent component = (UIComponent) mock.proxy();

    setFacesContext(facesContext);
    try
    {
      converter.setLocale(Locale.US);
      for (int i = 0; i < maxFractDigits.length; i++)
      {
        converter.setMaxFractionDigits(maxFractDigits[i]);
        converter.setMaxIntegerDigits(maxIntDigits[i]);
        converter.setMinFractionDigits(minFractDigits[i]);
        converter.setMinIntegerDigits(minIntDigits[i]);
        
        String out = converter.getAsString(facesContext, component, inputValues[i]);
        assertEquals(expectedValues[i], out);   
      }
    }
    finally
    {
      setFacesContext(null);
    }
    mock.verify();
  }

  protected abstract NumberConverter getNumberConverter();

  protected abstract void doTestStrictNess(
    MockFacesContext context,
    MockUIComponentWrapper wrapper,
    Locale locale,
    String inputValue);

  private static final String[] _CURRENCY_CODES = {"USD", "DEM" };

  private static final String[] _CURRENCY_SYMBOLS = {"*", "!"};

  private static final Locale[] _LOCALES = {Locale.US, Locale.GERMAN};

  private static final int[] _MAX_FRACTION_DIGITS = {2, 3};

  private static final int[] _MAX_INT_DIGITS = {5, 6};

  private static final int[] _MIN_FRACT_DIGITS = {2, 3};

  private static final int[] _MIN_INT_DIGITS = {2, 3};

  private static final String[] _PATTTERNS = {"##,##", null};

  private static final String[] _TYPES = {"currency","percent"};

  // -= Simon Lessard =-
  // TODO: Never read locally as of 2006-08-09. Remove whenever possible
  //       or implements a grouping test using this constant.
  //private static final boolean[] _GROUPING = {true, false};

  private static final boolean[] _INTEGER_ONLY = {true, false};

  private static final boolean[] _TRANSIENT = {true, false};

}
// DONOT DELETE LET THESE STAY HERE.
// CurrencyCode
// CurrencySymbol
// Locale
// MaxFractionDigits
// MaxIntegerDigits
// MinFractionDigits
// MinIntegerDigits
// Pattern
// Type
// GroupingUsed
// IntegerOnly

//Currency Code | Country Currency
//USD - United States Dollar
//
//ITL - Italian Lira
//
//DEM - German Mark
//
//HKG - Hong Kong Dollar
//
//MXN - Mexican Peso
//
//EUR - Euro

// CurrencyCode        tested
// CurrencySymbol      tested
// Locale              tested  to pick up from viewRoot


// MaxFractionDigits      only while formatting
// MaxIntegerDigits       only while formatting
// MinFractionDigits      only while formatting
// MinIntegerDigits       only while formatting

// Pattern             tested
// Type                tested
// GroupingUsed        tested
// IntegerOnly         tested   // only while parsing
