/*
 * Copyright (C) 2015 Karumi.
 *
 * 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 com.karumi.expandableselector;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import com.karumi.expandableselector.animation.ExpandableSelectorAnimator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * FrameLayout extension used to show a list of ExpandableItems instances represented with Button
 * or ImageButton widgets which can be collapsed and expanded using an animation. The configurable
 * elements of the class are:
 *
 * - List of items to show represented with ExpandableItem instances.
 * - Time used to perform the collapse/expand animations. Expressed in milliseconds.
 * - Show or hide the view background when the List of ExpandaleItems are collapsed.
 * - Configure a ExpandableSelectorListeners to be notified when the view is going to be
 * collapsed/expanded or has
 * been collapsed/expanded.
 * - Configure a OnExpandableItemClickListener to be notified when an item is clicked.
 */
public class ExpandableSelector extends FrameLayout {

  private static final int DEFAULT_ANIMATION_DURATION = 300;

  private List<ExpandableItem> expandableItems = Collections.EMPTY_LIST;
  private List<View> buttons = new ArrayList<View>();
  private ExpandableSelectorAnimator expandableSelectorAnimator;
  private ExpandableSelectorListener listener;
  private OnExpandableItemClickListener clickListener;

  private boolean hideBackgroundIfCollapsed;
  private Drawable expandedBackground;

  public ExpandableSelector(Context context) {
    this(context, null);
  }

  public ExpandableSelector(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public ExpandableSelector(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initializeView(attrs);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public ExpandableSelector(Context context, AttributeSet attrs, int defStyleAttr,
      int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    initializeView(attrs);
  }

  /**
   * Configures a List<ExpandableItem> to be shown. By default, the list of ExpandableItems is
   * going to be shown collapsed. Please take into account that this method creates
   * ImageButton/Button widgets based on the size of the list passed as parameter. Don't use this
   * library as a RecyclerView and take into account the number of elements to show.
   */
  public void showExpandableItems(List<ExpandableItem> expandableItems) {
    validateExpandableItems(expandableItems);

    reset();
    setExpandableItems(expandableItems);
    renderExpandableItems();
    hookListeners();
    bringChildsToFront(expandableItems);
  }

  /**
   * Performs different animations to show the previously configured ExpandableItems transformed
   * into Button widgets. Notifies the ExpandableSelectorListener instance there was previously
   * configured.
   */
  public void expand() {
    expandableSelectorAnimator.expand(new ExpandableSelectorAnimator.Listener() {
      @Override public void onAnimationFinished() {
        notifyExpanded();
      }
    });
    notifyExpand();
    updateBackground();
  }

  /**
   * Performs different animations to hide the previously configured ExpandableItems transformed
   * into Button widgets. Notifies the ExpandableSelectorListener instance there was previously
   * configured.
   */
  public void collapse() {
    expandableSelectorAnimator.collapse(new ExpandableSelectorAnimator.Listener() {
      @Override public void onAnimationFinished() {
        updateBackground();
        notifyCollapsed();
      }
    });
    notifyCollapse();
  }

  /**
   * Returns true if the view is collapsed and false if the view is expanded.
   */
  public boolean isCollapsed() {
    return expandableSelectorAnimator.isCollapsed();
  }

  /**
   * Returns true if the view is expanded and false if the view is collapsed.
   */
  public boolean isExpanded() {
    return expandableSelectorAnimator.isExpanded();
  }

  /**
   * Configures a ExpandableSelectorListener instance to be notified when different collapse/expand
   * animations be performed.
   */
  public void setExpandableSelectorListener(ExpandableSelectorListener listener) {
    this.listener = listener;
  }

  /**
   * Configures a OnExpandableItemClickListener instance to be notified when a Button/ImageButton
   * inside ExpandableSelector be clicked. If the component is collapsed an the first button is
   * clicked the listener will not be notified. This listener will be notified about button clicks
   * just when ExpandableSelector be collapsed.
   */
  public void setOnExpandableItemClickListener(OnExpandableItemClickListener clickListener) {
    this.clickListener = clickListener;
  }

  /**
   * Given a position passed as parameter returns the ExpandableItem associated.
   */
  public ExpandableItem getExpandableItem(int expandableItemPosition) {
    return expandableItems.get(expandableItemPosition);
  }

  /**
   * Changes the ExpandableItem associated to a given position and updates the Button widget to
   * show
   * the new ExpandableItem information.
   */
  public void updateExpandableItem(int expandableItemPosition, ExpandableItem expandableItem) {
    validateExpandableItem(expandableItem);
    expandableItems.remove(expandableItemPosition);
    expandableItems.add(expandableItemPosition, expandableItem);
    int buttonPosition = buttons.size() - 1 - expandableItemPosition;
    configureButtonContent(buttons.get(buttonPosition), expandableItem);
  }

  private void initializeView(AttributeSet attrs) {
    TypedArray attributes =
        getContext().obtainStyledAttributes(attrs, R.styleable.expandable_selector);
    initializeAnimationDuration(attributes);
    initializeHideBackgroundIfCollapsed(attributes);
    initializeHideFirstItemOnCollapse(attributes);
    attributes.recycle();
  }

  private void initializeHideBackgroundIfCollapsed(TypedArray attributes) {
    hideBackgroundIfCollapsed =
        attributes.getBoolean(R.styleable.expandable_selector_hide_background_if_collapsed, false);
    expandedBackground = getBackground();
    updateBackground();
  }

  private void initializeAnimationDuration(TypedArray attributes) {
    int animationDuration =
        attributes.getInteger(R.styleable.expandable_selector_animation_duration,
            DEFAULT_ANIMATION_DURATION);
    int expandInterpolatorId =
            attributes.getResourceId(R.styleable.expandable_selector_expand_interpolator,
                    android.R.anim.accelerate_interpolator);
    int collapseInterpolatorId =
            attributes.getResourceId(R.styleable.expandable_selector_collapse_interpolator,
                    android.R.anim.decelerate_interpolator);
    int containerInterpolatorId =
            attributes.getResourceId(R.styleable.expandable_selector_container_interpolator,
                    android.R.anim.decelerate_interpolator);
    expandableSelectorAnimator = new ExpandableSelectorAnimator(this, animationDuration, expandInterpolatorId,
            collapseInterpolatorId, containerInterpolatorId);
  }

  private void initializeHideFirstItemOnCollapse(TypedArray attributes) {
    boolean hideFirstItemOnCollapsed =
        attributes.getBoolean(R.styleable.expandable_selector_hide_first_item_on_collapse, false);
    expandableSelectorAnimator.setHideFirstItemOnCollapse(hideFirstItemOnCollapsed);
  }

  private void updateBackground() {
    if (!hideBackgroundIfCollapsed) {
      return;
    }
    if (isExpanded()) {
      setBackgroundDrawable(expandedBackground);
    } else {
      setBackgroundResource(android.R.color.transparent);
    }
  }

  private void reset() {
    this.expandableItems = Collections.EMPTY_LIST;
    for (View button : buttons) {
      removeView(button);
    }
    this.buttons = new ArrayList<View>();
    expandableSelectorAnimator.reset();
  }

  private void renderExpandableItems() {
    int numberOfItems = expandableItems.size();
    for (int i = numberOfItems - 1; i >= 0; i--) {
      View button = initializeButton(i);
      addView(button);
      buttons.add(button);
      expandableSelectorAnimator.initializeButton(button);
      configureButtonContent(button, expandableItems.get((i)));
    }
    expandableSelectorAnimator.setButtons(buttons);
  }

  private void hookListeners() {
    final int numberOfButtons = buttons.size();
    boolean thereIsMoreThanOneButton = numberOfButtons > 1;
    if (thereIsMoreThanOneButton) {
      buttons.get(numberOfButtons - 1).setOnClickListener(new OnClickListener() {
        @Override public void onClick(View v) {
          if (isCollapsed()) {
            expand();
          } else {
            notifyButtonClicked(0, v);
          }
        }
      });
    }
    for (int i = 0; i < numberOfButtons - 1; i++) {
      final int buttonPosition = i;
      buttons.get(i).setOnClickListener(new OnClickListener() {
        @Override public void onClick(View v) {
          int buttonIndex = numberOfButtons - 1 - buttonPosition;
          notifyButtonClicked(buttonIndex, v);
        }
      });
    }
  }

  private void notifyButtonClicked(int itemPosition, View button) {
    if (clickListener != null) {
      clickListener.onExpandableItemClickListener(itemPosition, button);
    }
  }

  private View initializeButton(int expandableItemPosition) {
    ExpandableItem expandableItem = expandableItems.get(expandableItemPosition);
    View button = null;
    Context context = getContext();
    LayoutInflater layoutInflater = LayoutInflater.from(context);
    if (expandableItem.hasTitle()) {
      button = layoutInflater.inflate(R.layout.expandable_item_button, this, false);
    } else {
      button = layoutInflater.inflate(R.layout.expandable_item_image_button, this, false);
    }
    int visibility = expandableItemPosition == 0 ? View.VISIBLE : View.INVISIBLE;
    button.setVisibility(visibility);
    return button;
  }

  private void configureButtonContent(View button, ExpandableItem expandableItem) {
    if (expandableItem.hasBackgroundId()) {
      int backgroundId = expandableItem.getBackgroundId();
      button.setBackgroundResource(backgroundId);
    }
    if (expandableItem.hasTitle()) {
      String text = expandableItem.getTitle();
      ((Button) button).setText(text);
    }
    if (expandableItem.hasResourceId()) {
      ImageButton imageButton = (ImageButton) button;
      int resourceId = expandableItem.getResourceId();
      imageButton.setImageResource(resourceId);
    }
  }

  private void notifyExpand() {
    if (hasListenerConfigured()) {
      listener.onExpand();
    }
  }

  private void notifyCollapse() {
    if (hasListenerConfigured()) {
      listener.onCollapse();
    }
  }

  private void notifyExpanded() {
    if (hasListenerConfigured()) {
      listener.onExpanded();
    }
  }

  private void notifyCollapsed() {
    if (hasListenerConfigured()) {
      listener.onCollapsed();
    }
  }

  private boolean hasListenerConfigured() {
    return listener != null;
  }

  private void validateExpandableItem(ExpandableItem expandableItem) {
    if (expandableItem == null) {
      throw new IllegalArgumentException(
          "You can't use a null instance of ExpandableItem as parameter.");
    }
  }

  private void validateExpandableItems(List<ExpandableItem> expandableItems) {
    if (expandableItems == null) {
      throw new IllegalArgumentException(
          "The List<ExpandableItem> passed as argument can't be null");
    }
  }

  private void setExpandableItems(List<ExpandableItem> expandableItems) {
    this.expandableItems = new ArrayList<ExpandableItem>(expandableItems);
  }

  private void bringChildsToFront(List<ExpandableItem> expandableItems) {
    int childCount = getChildCount();
    int numberOfExpandableItems = expandableItems.size();
    if (childCount > numberOfExpandableItems) {
      for (int i = 0; i < childCount - numberOfExpandableItems; i++) {
        getChildAt(i).bringToFront();
      }
    }
  }
}