/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.android.incallui.incall.impl;

import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.StringRes;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.SoundEffectConstants;
import android.widget.Checkable;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

/** A button to show on the incall screen */
public class CheckableLabeledButton extends LinearLayout implements Checkable {

  private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
  private static final float DISABLED_STATE_OPACITY = .3f;
  private boolean broadcasting;
  private boolean isChecked;
  private OnCheckedChangeListener onCheckedChangeListener;
  private ImageView iconView;
  @DrawableRes private int iconResource = 0;
  private TextView labelView;
  private Drawable background;
  private Drawable backgroundMore;

  public CheckableLabeledButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
  }

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

  private void init(Context context, AttributeSet attrs) {
    setOrientation(VERTICAL);
    setGravity(Gravity.CENTER_HORIZONTAL);
    Drawable icon;
    CharSequence labelText;
    boolean enabled;

    backgroundMore =
        getResources().getDrawable(R.drawable.incall_button_background_more, context.getTheme());
    background =
        getResources().getDrawable(R.drawable.incall_button_background, context.getTheme());

    TypedArray typedArray =
        context.obtainStyledAttributes(attrs, R.styleable.CheckableLabeledButton);
    icon = typedArray.getDrawable(R.styleable.CheckableLabeledButton_incall_icon);
    labelText = typedArray.getString(R.styleable.CheckableLabeledButton_incall_labelText);
    enabled = typedArray.getBoolean(R.styleable.CheckableLabeledButton_android_enabled, true);
    typedArray.recycle();

    int paddingSize = getResources().getDimensionPixelOffset(R.dimen.incall_button_padding);
    setPadding(paddingSize, paddingSize, paddingSize, paddingSize);

    int iconSize = getResources().getDimensionPixelSize(R.dimen.incall_labeled_button_size);
    int imageSize = getResources().getDimensionPixelSize(R.dimen.incall_labeled_button_icon_size);
    int iconPadding = (iconSize - imageSize) / 2;

    iconView = new ImageView(context, null, android.R.style.Widget_Material_Button_Colored);
    LayoutParams iconParams = generateDefaultLayoutParams();
    iconParams.width = iconSize;
    iconParams.height = iconSize;
    iconView.setLayoutParams(iconParams);
    iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding);
    iconView.setImageDrawable(icon);
    iconView.setImageTintMode(Mode.SRC_IN);
    iconView.setImageTintList(
        getResources().getColorStateList(R.color.incall_button_icon, context.getTheme()));

    iconView.setBackground(
        getResources().getDrawable(R.drawable.incall_button_background, context.getTheme()));
    iconView.setDuplicateParentStateEnabled(true);
    iconView.setElevation(getResources().getDimension(R.dimen.incall_button_elevation));
    iconView.setStateListAnimator(
        AnimatorInflater.loadStateListAnimator(context, R.animator.incall_button_elevation));
    addView(iconView);

    labelView = new TextView(context);
    LayoutParams labelParams = generateDefaultLayoutParams();
    labelParams.width = LayoutParams.WRAP_CONTENT;
    labelParams.height = LayoutParams.WRAP_CONTENT;
    labelParams.topMargin =
        context.getResources().getDimensionPixelOffset(R.dimen.incall_button_label_margin);
    labelView.setLayoutParams(labelParams);
    labelView.setTextAppearance(R.style.Dialer_Incall_TextAppearance_Label);
    labelView.setText(labelText);
    labelView.setSingleLine();
    labelView.setMaxEms(9);
    labelView.setEllipsize(TruncateAt.END);
    labelView.setGravity(Gravity.CENTER);
    labelView.setDuplicateParentStateEnabled(true);
    addView(labelView);

    setFocusable(true);
    setClickable(true);
    setEnabled(enabled);
    setOutlineProvider(null);
  }

  @Override
  public void refreshDrawableState() {
    super.refreshDrawableState();
    iconView.setAlpha(isEnabled() ? 1f : DISABLED_STATE_OPACITY);
    labelView.setAlpha(isEnabled() ? 1f : DISABLED_STATE_OPACITY);
  }

  public void setCheckedColor(@ColorInt int color) {
    iconView.setImageTintList(
        new ColorStateList(
            new int[][] {new int[] {android.R.attr.state_checked}, new int[] {}},
            new int[] {color, Color.WHITE}));
  }

  public Drawable getIconDrawable() {
    return iconView.getDrawable();
  }

  public void setIconDrawable(@DrawableRes int drawableRes) {
    if (iconResource != drawableRes) {
      iconView.setImageResource(drawableRes);
      iconResource = drawableRes;
    }
  }

  public void setLabelText(@StringRes int stringRes) {
    labelView.setText(stringRes);
  }

  /** Shows or hides a little down arrow to indicate that the button will pop up a menu. */
  public void setShouldShowMoreIndicator(boolean shouldShow) {
    iconView.setBackground(shouldShow ? backgroundMore : background);
  }

  @Override
  public boolean isChecked() {
    return isChecked;
  }

  @Override
  public void setChecked(boolean checked) {
    performSetChecked(checked);
  }

  @Override
  public void toggle() {
    userRequestedSetChecked(!isChecked());
  }

  @Override
  public int[] onCreateDrawableState(int extraSpace) {
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    if (isChecked()) {
      mergeDrawableStates(drawableState, CHECKED_STATE_SET);
    }
    return drawableState;
  }

  @Override
  protected void drawableStateChanged() {
    super.drawableStateChanged();
    invalidate();
  }

  public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    this.onCheckedChangeListener = listener;
  }

  @Override
  public boolean performClick() {
    if (!isCheckable()) {
      return super.performClick();
    }

    toggle();
    final boolean handled = super.performClick();
    if (!handled) {
      // View only makes a sound effect if the onClickListener was
      // called, so we'll need to make one here instead.
      playSoundEffect(SoundEffectConstants.CLICK);
    }
    return handled;
  }

  private boolean isCheckable() {
    return onCheckedChangeListener != null;
  }

  @Override
  protected void onRestoreInstanceState(Parcelable state) {
    SavedState savedState = (SavedState) state;
    super.onRestoreInstanceState(savedState.getSuperState());
    performSetChecked(savedState.isChecked);
    requestLayout();
  }

  @Override
  protected Parcelable onSaveInstanceState() {
    return new SavedState(isChecked(), super.onSaveInstanceState());
  }

  /**
   * Called when the state of the button should be updated, this should not be the result of user
   * interaction.
   *
   * @param checked {@code true} if the button should be in the checked state, {@code false}
   *     otherwise.
   */
  private void performSetChecked(boolean checked) {
    if (isChecked() == checked) {
      return;
    }
    isChecked = checked;
    refreshDrawableState();
  }

  /**
   * Called when the user interacts with a button. This should not result in the button updating
   * state, rather the request should be propagated to the associated listener.
   *
   * @param checked {@code true} if the button should be in the checked state, {@code false}
   *     otherwise.
   */
  private void userRequestedSetChecked(boolean checked) {
    if (isChecked() == checked) {
      return;
    }
    if (broadcasting) {
      return;
    }
    broadcasting = true;
    if (onCheckedChangeListener != null) {
      onCheckedChangeListener.onCheckedChanged(this, checked);
    }
    broadcasting = false;
  }

  /** Callback interface to notify when the button's checked state has changed */
  public interface OnCheckedChangeListener {

    void onCheckedChanged(CheckableLabeledButton checkableLabeledButton, boolean isChecked);
  }

  private static class SavedState extends BaseSavedState {

    public static final Creator<SavedState> CREATOR =
        new Creator<SavedState>() {
          @Override
          public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
          }

          @Override
          public SavedState[] newArray(int size) {
            return new SavedState[size];
          }
        };
    public final boolean isChecked;

    private SavedState(boolean isChecked, Parcelable superState) {
      super(superState);
      this.isChecked = isChecked;
    }

    protected SavedState(Parcel in) {
      super(in);
      isChecked = in.readByte() != 0;
    }

    @Override
    public int describeContents() {
      return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      super.writeToParcel(dest, flags);
      dest.writeByte((byte) (isChecked ? 1 : 0));
    }
  }
}
