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

import org.apache.myfaces.tobago.compat.FacesUtils;
import org.apache.myfaces.tobago.component.Attributes;
import org.apache.myfaces.tobago.component.ComponentTypes;
import org.apache.myfaces.tobago.util.CreateComponentUtils;
import org.apache.myfaces.tobago.component.Facets;
import org.apache.myfaces.tobago.component.OnComponentPopulated;
import org.apache.myfaces.tobago.component.RendererTypes;
import org.apache.myfaces.tobago.component.SupportsRenderedPartially;
import org.apache.myfaces.tobago.event.TabChangeEvent;
import org.apache.myfaces.tobago.event.TabChangeListener;
import org.apache.myfaces.tobago.event.TabChangeSource;
import org.apache.myfaces.tobago.internal.layout.LayoutUtils;
import org.apache.myfaces.tobago.layout.LayoutComponent;
import org.apache.myfaces.tobago.layout.LayoutContainer;
import org.apache.myfaces.tobago.layout.LayoutManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.faces.component.ActionSource;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public abstract class AbstractUITabGroup extends AbstractUIPanelBase
    implements TabChangeSource, ActionSource, LayoutContainer, LayoutComponent, OnComponentPopulated,
    SupportsRenderedPartially {

  private static final Logger LOG = LoggerFactory.getLogger(AbstractUITabGroup.class);

  public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.TabGroup";

  public static final String SWITCH_TYPE_CLIENT = "client";
  public static final String SWITCH_TYPE_RELOAD_PAGE = "reloadPage";
  public static final String SWITCH_TYPE_RELOAD_TAB = "reloadTab";

  @Override
  public void encodeBegin(FacesContext facesContext) throws IOException {

    super.encodeBegin(facesContext);

    ((AbstractUILayoutBase) getLayoutManager()).encodeBegin(facesContext);
  }

  @Override
  public void encodeChildren(FacesContext facesContext) throws IOException {

//    ((AbstractUILayoutBase) getLayoutManager()).encodeChildren(facesContext);
  }

  @Override
  public void encodeEnd(FacesContext facesContext) throws IOException {

    ((AbstractUILayoutBase) getLayoutManager()).encodeEnd(facesContext);

    resetTabLayout();
    super.encodeEnd(facesContext);
    setRenderedIndex(getSelectedIndex());
  }


  @Override
  public boolean getRendersChildren() {
    return true;
  }

  public void queueEvent(FacesEvent event) {
    if (this == event.getSource()) {
      if (isImmediate() || isSwitchTypeClient()) {
        // if switch type client event is always immediate
        event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
      } else {
        event.setPhaseId(PhaseId.INVOKE_APPLICATION);
      }
    }
    super.queueEvent(event);
  }

  private void resetTabLayout() {
    for (UIComponent component : (List<UIComponent>) getChildren()) {
      component.getAttributes().remove(Attributes.LAYOUT_WIDTH);
      component.getAttributes().remove(Attributes.LAYOUT_HEIGHT);
    }
  }

  public AbstractUIPanelBase[] getTabs() {
    List<AbstractUIPanelBase> tabs = new ArrayList<AbstractUIPanelBase>();
    for (Object o : getChildren()) {
      UIComponent kid = (UIComponent) o;
      if (kid instanceof AbstractUIPanelBase) {
        //if (kid.isRendered()) {
        tabs.add((AbstractUIPanelBase) kid);
        //}
      } else {
        LOG.error("Invalid component in UITabGroup: " + kid);
      }
    }
    return tabs.toArray(new AbstractUIPanelBase[tabs.size()]);
  }

  public AbstractUIPanelBase getActiveTab() {
    return getTab(getSelectedIndex());
  }


  @Override
  public void processDecodes(FacesContext context) {
    if (!isSwitchTypeClient()) {

      if (context == null) {
        throw new NullPointerException("context");
      }
      if (!isRendered()) {
        return;
      }
      AbstractUIPanelBase renderedTab = getRenderedTab();
      renderedTab.processDecodes(context);
      for (UIComponent facet : (Collection<UIComponent>) getFacets().values()) {
        facet.processDecodes(context);
      }
      try {
        decode(context);
      } catch (RuntimeException e) {
        context.renderResponse();
        throw e;
      }
    } else {
      super.processDecodes(context);
    }
  }

  @Override
  public void processValidators(FacesContext context) {
    if (!isSwitchTypeClient()) {
      if (context == null) {
        throw new NullPointerException("context");
      }
      if (!isRendered()) {
        return;
      }
      AbstractUIPanelBase renderedTab = getRenderedTab();
      renderedTab.processValidators(context);
      for (UIComponent facet : (Collection<UIComponent>) getFacets().values()) {
        facet.processValidators(context);
      }
    } else {
      super.processValidators(context);
    }
  }

  @Override
  public void processUpdates(FacesContext context) {
    if (!isSwitchTypeClient()) {
      if (context == null) {
        throw new NullPointerException("context");
      }
      if (!isRendered()) {
        return;
      }
      AbstractUIPanelBase renderedTab = getRenderedTab();
      renderedTab.processUpdates(context);
      for (UIComponent facet : (Collection<UIComponent>) getFacets().values()) {
        facet.processUpdates(context);
      }

    } else {
      super.processUpdates(context);
    }
  }

  public void broadcast(FacesEvent facesEvent) throws AbortProcessingException {
    super.broadcast(facesEvent);
    if (facesEvent instanceof TabChangeEvent && facesEvent.getComponent() == this) {
      FacesUtils.invokeMethodBinding(getFacesContext(), getTabChangeListener(), facesEvent);

      FacesUtils.invokeMethodBinding(getFacesContext(), getActionListener(), facesEvent);
      if (!isSwitchTypeClient()) {
        ActionListener defaultActionListener = getFacesContext().getApplication().getActionListener();
        if (defaultActionListener != null) {
          defaultActionListener.processAction((ActionEvent) facesEvent);
        }
      }
      Integer index = ((TabChangeEvent) facesEvent).getNewTabIndex();
      if (FacesUtils.hasValueBindingOrValueExpression(this, Attributes.SELECTED_INDEX)) {
        FacesUtils.setValueOfBindingOrExpression(getFacesContext(), index, this, Attributes.SELECTED_INDEX);
      } else {
        setSelectedIndex(index);
      }
    }
  }

  public void addTabChangeListener(TabChangeListener listener) {
    if (LOG.isWarnEnabled() && isSwitchTypeClient()) {
      LOG.warn("Adding TabChangeListener to Client side Tabgroup!");
    }
    addFacesListener(listener);
  }

  public boolean isSwitchTypeClient() {
    String switchType = getSwitchType();
    return (switchType == null || switchType.equals(SWITCH_TYPE_CLIENT));
  }

  public void removeTabChangeListener(TabChangeListener listener) {
    removeFacesListener(listener);
  }

  public TabChangeListener[] getTabChangeListeners() {
    return (TabChangeListener[]) getFacesListeners(TabChangeListener.class);
  }

  public abstract Integer getRenderedIndex();

  public abstract void setRenderedIndex(Integer index);

  public abstract Integer getSelectedIndex();

  public abstract void setSelectedIndex(Integer index);

  public abstract String getSwitchType();

  private AbstractUIPanelBase getTab(int index) {
    int i = 0;
    for (UIComponent component : (List<UIComponent>) getChildren()) {
      if (component instanceof AbstractUIPanelBase) {
        if (i == index) {
          return (AbstractUIPanelBase) component;
        }
        i++;
      } else {
        LOG.error("Invalid component in UITabGroup: " + component);
      }
    }
    LOG.error("Found no component with index: " + index + " childCount: " + getChildCount());
    return null;
  }

  private AbstractUIPanelBase getRenderedTab() {
    return getTab(getRenderedIndex());
  }


  /**
   * @since 1.5.0
   */
  public void addActionListener(ActionListener listener) {
    addFacesListener(listener);
  }

  /**
   * @since 1.5.0
   */
  public ActionListener[] getActionListeners() {
    return (ActionListener[]) getFacesListeners(ActionListener.class);
  }

  /**
   * @since 1.5.0
   */
  public void removeActionListener(ActionListener listener) {
    removeFacesListener(listener);
  }

  public List<LayoutComponent> getComponents() {
    return LayoutUtils.findLayoutChildren(this);
  }

  public void onComponentPopulated(FacesContext facesContext, UIComponent parent) {
    if (getLayoutManager() == null) {
      setLayoutManager(CreateComponentUtils.createAndInitLayout(
          facesContext, ComponentTypes.TAB_GROUP_LAYOUT, RendererTypes.TAB_GROUP_LAYOUT, parent));
    }
  }
  
  public LayoutManager getLayoutManager() {
    return (LayoutManager) getFacet(Facets.LAYOUT);
  }

  public void setLayoutManager(LayoutManager layoutManager) {
    getFacets().put(Facets.LAYOUT, (AbstractUILayoutBase) layoutManager);
  }

  public boolean isLayoutChildren() {
    return isRendered();
  }
}
