/*
 *  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.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;

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

import junit.framework.Test;
import junit.framework.TestSuite;

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

public class TrinidadDateTimeConverterTest extends DateTimeConverterTestCase
{
  public TrinidadDateTimeConverterTest(String name)
  {
    super(name);
  }
  
  @Override
  protected void setUp() throws Exception
  {
    super.setUp();
    _mafct = new MockRequestContext();
    _mafct.setTwoDigitYearStart(1950);
    _mafct.setTimeZone(DEFAULT_TIME_ZONE);
  }

  @Override
  protected void tearDown() throws Exception
  {
    super.tearDown();
    _mafct.release();
    _mafct = null;
  }
  
  public static Test suite()
  {
    return new TestSuite(TrinidadDateTimeConverterTest.class);
  }
  
  public void testConveniencePatterns()
  {
    DateTimeConverter dtConv   = new DateTimeConverter();
    dtConv.setLocale(Locale.US);
    
    //this is what SimpleInputDateRenderer does
    if(dtConv.getTimeZone() == null)
    {
      TimeZone tz = null;
      tz = _mafct.getTimeZone();
      if(tz == null)
        tz = TimeZone.getDefault();
      dtConv.setTimeZone(tz);
    }
    
    Mock mock = buildMockUIComponent();
    UIComponent component = (UIComponent) mock.proxy();
    String[] inputValue = {"15/2/2002", "January 4, 2004", "4-JAnuARY-2004", "JANUARY/4/2007", "Jan 4, 2004",
        "01/4/2004", "01-4-2004", "01.4.2004", "1/4/2004", "1-4-2004", "1.4.2004", "Jan/4/2004", "Jan-4-2004",
        "Jan.4.2004", "04-jan-2004", "4-jan-04", "4-jan-2004", "04-jAn-04", "04-JAN-04", "04-JAN-2004",
        "4-JAN-04", "4-JAN-2004", "January 4, 2004", "Jan 4, 2004"};

    for(int i = 0; i < inputValue.length; i++)
    {
      dtConv.getAsObject(facesContext, component, inputValue[i]);
    }
  }

  public void testFormatedPatterns()
  {
    DateTimeConverter dtConv   = new DateTimeConverter();
    dtConv.setLocale(Locale.US);
    
    //this is what SimpleInputDateRenderer does
    if(dtConv.getTimeZone() == null)
    {
      TimeZone tz = null;
      tz = _mafct.getTimeZone();
      if(tz == null)
        tz = TimeZone.getDefault();
      dtConv.setTimeZone(tz);
    }
    
    Mock mock = buildMockUIComponent();
    UIComponent component = (UIComponent) mock.proxy();
    String[] inputValue = {"15/2/2002", "January 4, 2004", "4-JAnuARY-2004", "JANUARY/4/2007"};
    String[] formatedStrings = {"2/15/2002", "1/4/2004", "1/4/2004", "1/4/2007"};

    Date convertedDate = null;
    String returnedString = null;
    for(int i = 0; i < inputValue.length; i++)
    {
      convertedDate = (Date) dtConv.getAsObject(facesContext, component, inputValue[i]);
      returnedString = dtConv.getAsString(facesContext, component, convertedDate);
      assertEquals(returnedString, formatedStrings[i]);
    }
  }

  /**
   * @todo move this to the parent class once JSF fixes the bug
   */
  public void testEarlyExits()
  {
    checkNullComponent();
    checkNullContext();
  }

  public void testShortishForDatePatern()
  {
    GregorianCalendar gcal = new GregorianCalendar();
    gcal.set(2999,Calendar.JUNE,4,0,0,0);

    gcal.setTimeZone(DEFAULT_TIME_ZONE);
    // Make use of this date for testing.
    Date date = gcal.getTime();

    DateTimeConverter dtConv   = new DateTimeConverter();
    Mock mock = buildMockUIComponent();
    UIComponent component = (UIComponent) mock.proxy();
    String inputValue          = "6/4/2999";

    dtConv.setDateStyle("shortish");
    dtConv.setLocale(Locale.ENGLISH);

    Date dt = (Date) dtConv.getAsObject(facesContext, component, inputValue);
    assertEquals(true, isEqual(date, dt));

    String exptectedStr = dtConv.getAsString(facesContext, component, dt);
    assertEquals(inputValue, exptectedStr);
    mock.verify();
  }

  public void testShortishDateStyle()
  {
    doTestStyleValidity(_DATE_STYLE, new String[]{"shortish"});
  }

  public void testSecondaryPattern()
  {
    // Get as object should work fine - while getAsString is expected to fail
    GregorianCalendar gcal = new GregorianCalendar();
    gcal.set(1600,Calendar.JUNE,4,0,0,0);

    gcal.setTimeZone(DEFAULT_TIME_ZONE);
    // Make use of this date for testing.
    Date date = gcal.getTime();

    DateTimeConverter dtConv   = new DateTimeConverter();
    Mock mock = buildMockUIComponent();
    UIComponent component = (UIComponent) mock.proxy();
    String inputValue          = "6/4/1600";
    String secondaryPattern    = "MM/d/yyyy";

    dtConv.setLocale(Locale.US);
    dtConv.setDateStyle("Let us unset it ");
    dtConv.setType("Let us un set it");
    dtConv.setSecondaryPattern(secondaryPattern);

    // This should work fine
    Date dt = (Date) dtConv.getAsObject(facesContext, component, inputValue);
    assertEquals(true, isEqual(date, dt));

    try
    {
      dtConv.getAsString(facesContext, component, dt);
      fail("Use of secondary pattern in the above fashion is expected to fail here");
    }
    catch (RuntimeException ce)
    {
      // Just proceed . This is the expected state
    }

    dtConv.setDateStyle("shortish");
    dtConv.setType("date");

    // now we set date and type so this is expected to work fine.

    String expectedOut = dtConv.getAsString(facesContext, component, date);
    assertEquals(inputValue, expectedOut);

    mock.verify();
  }

  public void testLeniencyOnPrimaryPattern()
  {
    String primaryPattern = "MMM/d/yyyy";
    String secondaryPattern = null;
    dotestLeniencyOnPattern(primaryPattern, secondaryPattern);
  }

  public void testLeniencyOnSecondaryPattern()
  {
    String primaryPattern = null;
    String secondaryPattern = "MMM/d/yyyy";
    dotestLeniencyOnPattern(primaryPattern, secondaryPattern);
  }

  protected void dotestLeniencyOnPattern(
    String primaryPattern,
    String secondaryPatttern
    )
  {
    // inputs such as 6/7/2004 is also valid. Avoiding it since - equality
    // of the output is also compared.

    // Each of these inputs ends up causing the MessageFactory to grab the
    // label from the component a different number of times, so the number of
    // iterations to set the component up for is difficult to figure. The
    // numbers after the input string are the number of iterations of
    // getAttribute that we need to set up for.
    String[] validInputs =
      {
        "Jun/4/2004" /* 0 */, "Jun-4-2004" /* 10 */, "Jun.4.2004" /* 8 */,
        "06/4/2004"  /* 4 */, "06-04-2004" /* 12 */, "06.04.2004" /* 2 */,
        "6/4/2004"   /* 4 */, "6-4-2004"   /* 12 */, "6.4.2004"   /* 2 */
      };

    int iterations = (0 + 10 + 8 + 4 + 12 + 2 + 4 + 12 + 2);
    GregorianCalendar cal = new GregorianCalendar(2004, Calendar.JUNE, 4);
    cal.setTimeZone(DEFAULT_TIME_ZONE);
    Date dt = cal.getTime();
    Mock mock = buildMockUIComponent(iterations);
    UIComponent component = (UIComponent) mock.proxy();
    
    for (int i = 0; i < validInputs.length; i++)
    {
      DateTimeConverter
        dtConv = (DateTimeConverter) getDateTimeConverter();
      dtConv.setLocale(Locale.ENGLISH);
      dtConv.setPattern(primaryPattern);
      dtConv.setSecondaryPattern(secondaryPatttern);
      dtConv.setTimeZone(DEFAULT_TIME_ZONE);
      dtConv.setType("INVALID"); // make this type invalid

      Date convDate = (Date) dtConv.getAsObject(facesContext, component,
                                                validInputs[i]);
      assertEquals(convDate, dt);
      mock.verify();
    }
  }

  public void testCompareDateTimeConverter()
  {
    Object[][] data = _getDataForPatterns();

    for (int i = 0; i < data.length ; i++)
    {
      DateTimeConverter dtConv = new DateTimeConverter();
      dtConv.setPattern((String)data[i][0]);
      dtConv.setLocale((Locale)data[i][2]);
      dtConv.setTimeZone((TimeZone)data[i][3]);
      String inputValue = (String)data[i][1];

      javax.faces.convert.DateTimeConverter fdtConv
        = new javax.faces.convert.DateTimeConverter();
      fdtConv.setPattern((String)data[i][0]);
      fdtConv.setLocale((Locale)data[i][2]);
      fdtConv.setTimeZone((TimeZone)data[i][3]);

      Mock mock = buildMockUIComponent();
      UIComponent component = (UIComponent) mock.proxy();
      Date dtConvDate  = (Date)dtConv.getAsObject(facesContext, component, inputValue);
      Date fdtConvDate = (Date)fdtConv.getAsObject(facesContext, component, inputValue);
      //      assertEquals(dtConvDate, fdtConvDate);

      dtConv.getAsString(facesContext, component, dtConvDate);
      fdtConv.getAsString(facesContext, component, fdtConvDate);
      //      assertEquals(dtConvPattern, fdtConvPattern);
    }
  }

  @Override
  protected javax.faces.convert.DateTimeConverter getDateTimeConverter()
  {
    return new DateTimeConverter();
  }

  @Override
  protected void setSecondaryPattern(
    javax.faces.convert.DateTimeConverter converter,
    String secondaryPattern
    )
  {
    ((DateTimeConverter)converter).setSecondaryPattern(secondaryPattern);
  }

  @Override
  protected void doTestStateHolderSaveRestore(
    Converter conv1,
    Converter conv2,
    MockFacesContext context,
    MockUIComponentWrapper wrapper
    )
  {
    super.doTestStateHolderSaveRestore(conv1, conv2, context, wrapper);
  }

  public void testCustomMessageIsSet()
  {
    //default is shortish - M/d/yyyy
    //default time is short hh:m A.M/P.M
    //default both
    //let us choose pattern as M/d/yyyy
    String[] failingValues = {"15/15/2002", "02;30 A.M,", "15/15/2002 22:22 A*M.", "M/d/yyyy"};
    String[] types         = {"date",   "time",  "both", "pattern"};
    String[] customMessage = {"date",   "time",  "both", "pattern"};

    for (int i = 0; i < failingValues.length ; i++)
    {
      Mock mock = buildMockUIComponent(3 * 4);
      UIComponent component = (UIComponent) mock.proxy();

      org.apache.myfaces.trinidad.convert.DateTimeConverter converter =
        new org.apache.myfaces.trinidad.convert.DateTimeConverter();

      for (int j = 0; j < 3; j++)
      {
        for (int k = 0; k < 4; k++)
          facesContext.getViewRoot().setLocale(Locale.US);
      }

      try
      {
        // Trinidad Converter is not lenient.
        converter.setMessageDetailConvertDate(customMessage[0]);
        converter.setMessageDetailConvertTime(customMessage[1]);
        converter.setMessageDetailConvertBoth(customMessage[2]);
        // pattern and date type is driven using the same message.


        if ("pattern".equals(types[i]))
        {
          converter.setPattern("M/d/yyyy");
          // There is no specific messaging scheme for pattern. So use the
          // dateMessageDetail itself for this.
          converter.setMessageDetailConvertDate(customMessage[3]);
        }
        else
        {
          converter.setType(types[i]);
        }

        converter.getAsObject(facesContext, component, failingValues[i]);
        fail("Expected converter exception");
      }
      catch (ConverterException ce)
      {
        // We expected a exception to occur
        String msg = ce.getFacesMessage().getDetail();
        assertEquals(msg, customMessage[i]);
      }
    }
  }

  private Object[][] _getDataForPatterns()
  {
    // pattern, inputvalue,locale,timezone
    Object[][] data =
    {
      {"yyyy.MM.dd G 'at' HH:mm:ss z", "2001.07.04 AD at 12:08:56 PDT", Locale.US, null },

      {"EEE, MMM d, ''yy","Wed, Jul 4, '01", Locale.ENGLISH, getTzone("GMT")},

      {"h:mm a","12:08 PM", Locale.GERMAN, getTzone("GMT+1")},

      {"hh 'o''clock' a, zzzz","12 o'clock PM, Pacific Standard Time", Locale.CANADA, getTzone("GMT-8")},

      {"K:mm a, z","0:08 PM, PST", Locale.US, getTzone("PST")},

      {"yyyyy.MMMMM.dd GGG hh:mm aaa","02001.July.04 AD 12:08 PM", Locale.US, null},
      {"EEE, d MMM yyyy HH:mm:ss Z","Wed, 4 Jul 2001 12:08:56 GMT",Locale.US, getTzone("GMT")},
      {"yyMMddHHmmss", "010704120856", Locale.ENGLISH, null, null},

    };
    return data;
  }

  private MockRequestContext _mafct;

  private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
}
