/*
 *  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.component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.faces.component.EditableValueHolder;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.PropertyNotFoundException;
import javax.faces.el.ValueBinding;
import javax.faces.render.Renderer;

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

import junit.textui.TestRunner;

import org.apache.myfaces.trinidad.model.ModelUtils;
import org.apache.myfaces.trinidad.model.SortableModel;
import org.apache.myfaces.trinidad.context.MockRequestContext;

/**
 * Unit tests for UIXTable
 *
 */
public class UIXTableTest extends UIComponentTestCase
{
  /**
   * @param testName  the unit test name
   */
  public UIXTableTest(
    String testName)
  {
    super(testName);
  }


  private MockRequestContext _mafct;

  @Override
  public void setUp() throws Exception
  {
    super.setUp();
    _mafct = new MockRequestContext();
  }

  @Override
  public void tearDown() throws Exception
  {
    _mafct.release();
    _mafct = null;
    super.tearDown();
  }


  public static Test suite()
  {
    return new TestSuite(UIXTableTest.class);
  }

  /**
   * Tests the initial values for the component attributes.
   */
  public void testInitialAttributeValues()
  {
    UIXTable table = _createTable();
    assertEquals(25, table.getRows());
    assertEquals(0, table.getFirst());
    assertFalse(table.isImmediate());
  }

  /**
   * Tests the transparency of the component attribute by comparing
   * bean accessor and mutator methods with attribute map accessor
   * and mutator methods.
   */
  public void testAttributeTransparency()
  {
    UIXTable table = _createTable();
    doTestAttributeTransparency(table, "var", "row", "emp");
    doTestAttributeTransparency(table, "value", "row", "emp");
    doTestAttributeTransparency(table, "first", new Integer(0), new Integer(1));
    doTestAttributeTransparency(table, "immediate", Boolean.TRUE, Boolean.FALSE);
    doTestAttributeTransparency(table, "rows", new Integer(30), new Integer(10));
  }

  public void testModelMethods()
  {
    TestTable table = _createTable();
    SortableModel model = table.model;
    int sz = model.getRowCount();
    assertEquals(sz, table.getRowCount());
    for(int i=0; i<sz; i++)
    {
      table.setRowIndex(i);
      assertEquals(i, model.getRowIndex());
      assertEquals(model.getRowKey(), table.getRowKey());
      assertEquals(model.isRowAvailable(), table.isRowAvailable());
      assertEquals(model.getRowData(), table.getRowData());
    }
    table.setRowIndex(-1);
    assertEquals(-1, model.getRowIndex());
    assertEquals(model.getRowKey(), table.getRowKey());
    assertEquals(model.isRowAvailable(), table.isRowAvailable());
  }

  public void testProcessDecodes()
  {
    TestTable table = _createTable();
    table.setRows(10);
    table.setFirst(3);
    doTestApplyRequestValues(table);
    _testColumnChild(table, table.column1Child.getDecodesRowData());
    _testColumnChild(table, table.column2Child.getDecodesRowData());

    assertEquals(1, table.column1Header.getDecodesRowData().size());
    assertEquals(1, table.column2Header.getDecodesRowData().size());

    List<Object> detailData = table.detailStamp.getDecodesRowData();
    _testDetailStamp(table, detailData);

  }

  public void testProcessValidators()
  {
    TestTable table = _createTable();
    table.setRows(10);
    table.setFirst(3);
    doTestProcessValidations(table);
    _testColumnChild(table, table.column1Child.getValidatesRowData());
    _testColumnChild(table, table.column2Child.getValidatesRowData());

    assertEquals(1, table.column1Header.getValidatesRowData().size());
    assertEquals(1, table.column2Header.getValidatesRowData().size());

    List<Object> detailData = table.detailStamp.getValidatesRowData();
    _testDetailStamp(table, detailData);

  }

  public void testProcessUpdates()
  {
    TestTable table = _createTable();
    table.setRows(10);
    table.setFirst(3);
    doTestUpdateModelValues(table);
    _testColumnChild(table, table.column1Child.getUpdatesRowData());
    _testColumnChild(table, table.column2Child.getUpdatesRowData());

    assertEquals(1, table.column1Header.getUpdatesRowData().size());
    assertEquals(1, table.column2Header.getUpdatesRowData().size());

    List<Object> detailData = table.detailStamp.getUpdatesRowData();
    _testDetailStamp(table, detailData);

  }

  public void testEditableValueHolderChildren()
  {
    TestTable table = _createTable();
    UIXInput testComp = table.column1Child;
    UIXInput detailStamp = table.detailStamp;

    // initialize:
    table.setRowIndex(0);
    _setEVHData(testComp, "Foo", true);
    _setEVHData(detailStamp, "Foo-ds", false);

    table.setRowIndex(1);
    _setEVHData(testComp, "Bar", false);
    _setEVHData(detailStamp, "Bar-ds", true);

    // now test:
    table.setRowIndex(0);
    _testEVHData(testComp, "Foo", true);
    _testEVHData(detailStamp, "Foo-ds", false);

    table.setRowIndex(1);
    _testEVHData(testComp, "Bar", false);
    _testEVHData(detailStamp, "Bar-ds", true);

  }


  public void testSaveRestoreState()
  {
    final Object state;
    {
      TestTable table = _createTable();

      UIXInput testComp = table.column1Child;

      // initialize:
      table.setRowIndex(0);
      _setEVHData(testComp, "Foo", true);

      table.setRowIndex(1);
      _setEVHData(testComp, "Bar", false);

      // note that we did not set rowIndex back to -1.
      // processSaveState should store the stamp data even though rowIndex was
      // not changed.

      state = table.processSaveState(facesContext);
    }

    TestTable table = _createTable();
    UIXInput testComp = table.column1Child;
    table.processRestoreState(facesContext, state);

    // now test:
    table.setRowIndex(0);
    _testEVHData(testComp, "Foo", true);

    table.setRowIndex(1);
    _testEVHData(testComp, "Bar", false);

  }

  /**
   * make sure that the model is not executed at invalid or unnecessary times.
   * valueBindings cannot be called during restoreState.
   * Also table model must not be executed if rendered="false".
   * However, saveState is called even if rendered="false" on a component.
   * Therefore, saveState should not call getValue() on the table.
   */
  public void testSaveRestoreStateGetValue()
  {
    // make sure that getValue() is not called during restoreState:
    DoNotCallBinding doNotCall = new DoNotCallBinding();
    doNotCall.doNotCall = true;
    final Object state;
    {
      TestTable table = _createTable(false);
      table.setValue(null); // instead use the valueBinding below:
      table.setValueBinding("value", doNotCall);
      state = table.processSaveState(facesContext);
    }

    TestTable table = _createTable(false);
    table.setValue(null); // instead use the valueBinding below:
    // this value binding should be restored during processRestoreState;
    // however, set it anyway just to catch any getValue() calls prior to
    // that.
    table.setValueBinding("value", doNotCall);
    table.processRestoreState(facesContext, state);

    assertTrue(table.getValueBinding("value") instanceof DoNotCallBinding);

  }

  @SuppressWarnings("unchecked")
  public void testNotRenderedSaveRestoreState()
  {
    // this test is to make sure no exceptions are thrown during save/restore
    // state of the table. Exceptions were being thrown earlier because
    // model and certain initial state were computed lazily.
    final Object state;
    {
      UIXPanel panel = new UIXPanel();
      TestTable table = _createTable(false);
      table.setRendered(false);
      panel.getChildren().add(table);
      state = panel.processSaveState(facesContext);
    }

    UIXPanel panel = new UIXPanel();
    TestTable table = _createTable(false);
    panel.getChildren().add(table);

    panel.processRestoreState(facesContext, state);

  }

  @SuppressWarnings("unchecked")
  @Override
  protected void doTestUpdateModelValues(
    FacesContext context,
    UIViewRoot   root,
    UIComponent  component)
  {
    root.getChildren().add(component);
    root.processUpdates(context);
  }

  @SuppressWarnings("unchecked")
  @Override
  protected void doTestProcessValidations(
    FacesContext context,
    UIViewRoot   root,
    UIComponent  component)
  {
    root.getChildren().add(component);
    root.processValidators(context);
  }

  @Override
  public void setCurrentContext(FacesContext fc)
  {
    // prevent removal of facesContext before we are done testing:
    if (fc != null)
      super.setCurrentContext(fc);
  }

  private void _setEVHData(
    EditableValueHolder testComp,
    String value,
    boolean isValid)
  {
    testComp.setSubmittedValue("submitedValue-"+value);
    testComp.setValue("Value-"+value);
    testComp.setValid(isValid);
  }

  private void _testEVHData(
    EditableValueHolder testComp,
    String value,
    boolean isValid)
  {
    assertEquals("submitedValue-"+value, testComp.getSubmittedValue());
    assertEquals("Value-"+value, testComp.getLocalValue());
    assertEquals(isValid, testComp.isValid());
  }

  private void _testDetailStamp(TestTable table, List<Object> detailData)
  {
    assertEquals(1, detailData.size());
    table.setRowIndex(_DISCLOSED_INDEX);
    assertEquals(table.getRowData(), detailData.get(0));
  }

  private void _testColumnChild(TestTable table, List<Object> rowData)
  {
    // make sure that the rowData values that were seen during this phase
    // were the correct values:
    int rows = table.getRows();
    assertEquals(rows, rowData.size());
    int first = table.getFirst();
    for(int i=0; i<rows; i++)
    {
      table.setRowIndex(i+first);
      assertEquals(table.getRowData(), rowData.get(i));
    }
  }

  public static void main(String[] args)
  {
    TestRunner.run(UIXTableTest.class);
//    UIXTableTest test = new UIXTableTest("aasd");
//    test.testSaveRestoreStateGetValue();
  }

  @SuppressWarnings("unchecked")
  @Override
  protected void doTestApplyRequestValues(
    FacesContext context,
    UIViewRoot   root,
    UIComponent  component)
  {
    root.getChildren().add(component);
    root.processDecodes(context);
  }

  @Override
  protected boolean isRendererUsed()
  {
    // we use our own MockRenderer; not the one created by our super class:
    return false;
  }

  private TestTable _createTable()
  {
    return _createTable(true);
  }

  private TestTable _createTable(boolean useModel)
  {
    SortableModel model = useModel ? _createTableData() : null;
    TestTable table = new TestTable(model);
    return table;
  }

  private static SortableModel _createTableData()
  {
    final int sz = 25;
    List<Object> data = new ArrayList<Object>(sz);
    for(int i=0; i<sz; i++)
    {
      data.add(new Integer(i));
    }
    return new SortableModel(ModelUtils.toDataModel(data));
  }

  // must be public static so that it can be state-saved:
  public static final class DoNotCallBinding extends ValueBinding
    implements StateHolder
  {
    @Override
    public Class<?> getType(FacesContext context) throws EvaluationException, PropertyNotFoundException
    {
      throw new AssertionFailedError("This method should not be called");
    }

    @Override
    public Object getValue(FacesContext context) throws EvaluationException, PropertyNotFoundException
    {
      if (doNotCall)
        throw new AssertionFailedError("This method should not be called");

      return _createTableData();
    }

    @Override
    public boolean isReadOnly(FacesContext context) throws EvaluationException, PropertyNotFoundException
    {
      throw new AssertionFailedError("This method should not be called");
    }

    @Override
    public void setValue(FacesContext context, Object value) throws EvaluationException, PropertyNotFoundException
    {
      throw new AssertionFailedError("This method should not be called");
    }

    public Object saveState(FacesContext context)
    {
      return Boolean.FALSE;
    }

    public void restoreState(FacesContext context, Object state)
    {
      // apon restoring state, to not allow getValue() calls:
      doNotCall = true;
    }

    public boolean isTransient()
    {
      return false;
    }

    public void setTransient(boolean newTransientValue)
    {
    }

    public boolean doNotCall = false;
  }

  private static final class TestComponent extends UIXInput
  {
    public TestComponent(String id)
    {
      _decodes   = Collections.emptyList();
      _updates   = Collections.emptyList();
      _validates = Collections.emptyList();
      setId(id);
    }

    @Override
    public void processDecodes(FacesContext fc)
    {
      _decodes = _addRowData(fc, _decodes);
    }

    @Override
    public void processUpdates(FacesContext fc)
    {
      _updates = _addRowData(fc, _updates);
    }

    @Override
    public void processValidators(FacesContext fc)
    {
      _validates = _addRowData(fc, _validates);
    }

    public List<Object> getDecodesRowData()
    {
      return _decodes;
    }

    public List<Object> getValidatesRowData()
    {
      return _validates;
    }

    public List<Object> getUpdatesRowData()
    {
      return _updates;
    }

    @Override
    public String toString()
    {
      return getId();
    }

    @Override
    protected Renderer getRenderer(FacesContext fc)
    {
      // for testing purposes must return null. Otherwise a renderer
      // will be needed to compute the clientID:
      return null;
    }

    private List<Object> _addRowData(FacesContext fc, List<Object> currList)
    {
      if (currList == Collections.emptyList())
        currList = new ArrayList<Object>(10);

      Object rowData = fc.getExternalContext().getRequestMap().get(_VAR);
      currList.add(rowData);
      return currList;
    }

    private List<Object> _decodes;
    private List<Object> _updates;
    private List<Object> _validates;
  }

  private static final String _VAR = "row";

  private static final class TestTable extends UIXTable
  {
    @SuppressWarnings("unchecked")
    public TestTable(SortableModel model)
    {
      super();
      setValue(model);
      this.model = model;
      setVar(_VAR);
      setId("table1");

      UIXColumn column1 = new UIXColumn()
      {
        @Override
        protected Renderer getRenderer(FacesContext fc)
        {
          // for testing purposes must return null. Otherwise a renderer
          // will be needed to compute the clientID:
          return null;
        }
      };
      column1.setId("col1");
      column1Header = new TestComponent("col1Header");
      column1.setHeader(column1Header);
      column1Child = new TestComponent("col1Child");
      column1.getChildren().add(column1Child);

      UIXColumn column2 = new UIXColumn()
      {
        @Override
        protected Renderer getRenderer(FacesContext fc)
        {
          // for testing purposes must return null. Otherwise a renderer
          // will be needed to compute the clientID:
          return null;
        }
      };
      column2.setId("col2");
      column2Header = new TestComponent("col2Header");
      column2.setHeader(column2Header);
      column2Child = new TestComponent("col2Child");
      column2.getChildren().add(column2Child);

      List<UIComponent> kids = getChildren();
      kids.add(column1);
      kids.add(column2);

      detailStamp = new TestComponent("detail");
      setDetailStamp(detailStamp);

      if (model != null)
      {
        setRowIndex(_DISCLOSED_INDEX);
        getDisclosedRowKeys().add();
        setRowIndex(-1);
      }
    }

    @Override
    protected Renderer getRenderer(FacesContext fc)
    {
      // for testing purposes must return null. Otherwise a renderer
      // will be needed to compute the clientID:
      return null;
    }

    public final SortableModel model;
    public final TestComponent column1Header, column1Child,
      column2Header, column2Child, detailStamp;
  }

  private static final int _DISCLOSED_INDEX = 5;
}
