content: /*
   * Copyright (C) 2015 AChep@xda <artemchep@gmail.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   * MA  02110-1301, USA.
   */
  package com.achep.acdisplay.ui.widgets.notification;
  
  import android.content.Context;
  import android.graphics.ColorFilter;
  import android.graphics.ColorMatrixColorFilter;
  import android.graphics.Typeface;
  import android.graphics.drawable.Drawable;
  import androidx.annotation.LayoutRes;
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.core.app.RemoteInput;
  import android.text.Editable;
  import android.text.TextUtils;
  import android.text.TextWatcher;
  import android.transition.TransitionManager;
  import android.util.AttributeSet;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.ArrayAdapter;
  import android.widget.EditText;
  import android.widget.LinearLayout;
  import android.widget.Spinner;
  import android.widget.TextView;
  
  import com.achep.acdisplay.R;
  import com.achep.acdisplay.notifications.Action;
  import com.achep.acdisplay.notifications.NotificationUtils;
  import com.achep.acdisplay.notifications.OpenNotification;
  import com.achep.base.Device;
  import com.achep.base.tests.Check;
  
  import java.util.HashMap;
  
  /**
   * @author Artem Chepurnoy
   * @since 3.1
   */
  public class NotificationActions extends LinearLayout {
  
      public interface Callback {
  
          void onRiiStateChanged(@NonNull NotificationActions na, boolean shown);
  
          /**
           * Called on action's button click.
           */
          void onActionClick(@NonNull NotificationActions na,
                             @NonNull View view, @NonNull Action action);
  
          /**
           * Called on action's button click.
           *
           * @param remoteInput the chosen {@link androidx.core.app.RemoteInput} to reply to
           * @param text        the text of the quick reply
           */
          void onActionClick(@NonNull NotificationActions na,
                             @NonNull View view, @NonNull Action action,
                             @NonNull RemoteInput remoteInput,
                             @NonNull CharSequence text);
      }
  
      /**
       * Disables the {@link #mView current action view} if the text is
       * {@link android.text.TextUtils#isEmpty(CharSequence) empty}.
       */
      protected final Textable.OnTextChangedListener mOnTextChangedListener =
              new Textable.OnTextChangedListener() {
                  @Override
                  public void onTextChanged(@Nullable CharSequence text) {
                      assert mView != null;
                      mView.setEnabled(!TextUtils.isEmpty(text));
                  }
              };
  
      private final HashMap<Action, RemoteInput> mRemoteInputsMap = new HashMap<>();
      private final HashMap<View, Action> mActionsMap = new HashMap<>();
      private final OnClickListener mActionsOnClick = new OnClickListener() {
          @Override
          public void onClick(View v) {
              Action action = mActionsMap.get(v);
              assert action != null;
              onActionClick(v, action);
          }
      };
  
      private final OnLongClickListener mActionsOnLongClick = new OnLongClickListener() {
          @Override
          public boolean onLongClick(View v) {
              sendAction(v, mActionsMap.get(v));
              hideRii();
              return true;
          }
      };
  
      /**
       * You know what is it for.
       */
      @Nullable
      private Callback mCallback;
  
      @Nullable
      private RemoteInput mRemoteInput;
      @Nullable
      private Textable mTextable;
      @Nullable
      private View mView;
  
      private LinearLayout.LayoutParams mLayoutParams;
      private Typeface mTypeface;
  
      public NotificationActions(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public void setCallback(@Nullable Callback callback) {
          mCallback = callback;
      }
  
      protected void onActionClick(@NonNull View view, @NonNull Action action) {
          if (isRiiShowing()) {
              if (mView != view) {
                  // Ignore this click. This may happen because of
                  // the animation delays.
                  return;
              }
              // Send the callback with performed remote input.
  
              assert mRemoteInput != null;
              assert mTextable != null;
              CharSequence text = mTextable.getText();
              Check.getInstance().isFalse(TextUtils.isEmpty(text));
  
              assert text != null;
              sendActionWithRemoteInput(view, action, mRemoteInput, text);
              hideRii();
          } else if ((mRemoteInput = mRemoteInputsMap.get(action)) != null) {
              // Initialize and show the remote input graphic
              // user interface.
  
              mView = view;
              mTextable = onCreateTextable(mRemoteInput);
              mOnTextChangedListener.onTextChanged(mTextable.getText());
  
              if (Device.hasKitKatApi() && isLaidOut()) {
                  TransitionManager.beginDelayedTransition(this);
              }
  
              mLayoutParams = (LayoutParams) mView.getLayoutParams();
              LayoutParams lp = new LayoutParams(
                      ViewGroup.LayoutParams.WRAP_CONTENT,
                      ViewGroup.LayoutParams.MATCH_PARENT);
              mView.setLayoutParams(lp);
              // Hide all other actions
              for (int i = getChildCount() - 1; i >= 0; i--) {
                  View v = getChildAt(i);
                  if (v != mView) v.setVisibility(GONE);
              }
              // Add the textable view
              addView(mTextable.getView(), 0);
              mTextable.getView().requestFocus();
  
              if (mCallback != null) mCallback.onRiiStateChanged(this, true);
          } else {
              sendAction(view, action);
          }
      }
  
      private void sendAction(@NonNull View view, @NonNull Action action) {
          if (mCallback != null) mCallback.onActionClick(this, view, action);
      }
  
      private void sendActionWithRemoteInput(@NonNull View view, @NonNull Action action,
                                             @NonNull RemoteInput remoteInput,
                                             @NonNull CharSequence text) {
          if (mCallback != null) mCallback.onActionClick(this, view, action, remoteInput, text);
      }
  
      /**
       * Returns the appropriate {@link NotificationActions.Textable} for this
       * {@link androidx.core.app.RemoteInput remote input}.
       *
       * @see androidx.core.app.RemoteInput#getAllowFreeFormInput()
       */
      @NonNull
      protected Textable onCreateTextable(@NonNull RemoteInput remoteInput) {
          return remoteInput.getAllowFreeFormInput()
                  ? new TextableFreeForm(this, remoteInput, mOnTextChangedListener)
                  : new TextableRestrictedForm(this, remoteInput, mOnTextChangedListener);
      }
  
      public void hideRii() {
          Check.getInstance().isInMainThread();
          if (!isRiiShowing()) return;
          assert mTextable != null;
          assert mView != null;
  
          removeView(mTextable.getView());
          mView.setLayoutParams(mLayoutParams);
          // Pop-up all other actions back.
          for (int i = getChildCount() - 1; i >= 0; i--) {
              View v = getChildAt(i);
              if (v != mView) v.setVisibility(VISIBLE);
          }
  
          mView = null;
          mTextable = null;
          mRemoteInput = null;
          mLayoutParams = null;
  
          if (mCallback != null) mCallback.onRiiStateChanged(this, false);
      }
  
      public boolean isRiiShowing() {
          return mRemoteInput != null;
      }
  
      /**
       * Sets new actions.
       *
       * @param notification the host notification
       * @param actions      the actions to set
       */
      public void setActions(@Nullable OpenNotification notification, @Nullable Action[] actions) {
          Check.getInstance().isInMainThread();
  
          mRemoteInputsMap.clear();
          mActionsMap.clear();
          hideRii();
  
          if (actions == null) {
              // Free actions' container.
              removeAllViews();
              return;
          } else {
              assert notification != null;
          }
  
          int count = actions.length;
          View[] views = new View[count];
  
          // Find available views.
          int childCount = getChildCount();
          int a = Math.min(childCount, count);
          for (int i = 0; i < a; i++) {
              views[i] = getChildAt(i);
          }
  
          // Remove redundant views.
          for (int i = childCount - 1; i >= count; i--) {
              removeViewAt(i);
          }
  
          LayoutInflater inflater = null;
          for (int i = 0; i < count; i++) {
              final Action action = actions[i];
              View root = views[i];
  
              if (root == null) {
                  // Initialize layout inflater only when we really need it.
                  if (inflater == null) {
                      inflater = (LayoutInflater) getContext()
                              .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                      assert inflater != null;
                  }
  
                  root = inflater.inflate(getActionLayoutResource(), this, false);
                  root = onCreateActionView(root);
                  // We need to keep all IDs unique to make
                  // TransitionManager.beginDelayedTransition(viewGroup, null)
                  // work correctly!
                  root.setId(getChildCount() + 1);
                  addView(root);
              }
  
              mActionsMap.put(root, action);
  
              int style = Typeface.NORMAL;
              root.setOnLongClickListener(null);
              if (action.intent != null) {
                  root.setEnabled(true);
                  root.setOnClickListener(mActionsOnClick);
  
                  RemoteInput remoteInput = getRemoteInput(action);
                  if (remoteInput != null) {
                      mRemoteInputsMap.put(action, remoteInput);
                      root.setOnLongClickListener(mActionsOnLongClick);
  
                      // Highlight the action
                      style = Typeface.ITALIC;
                  }
              } else {
                  root.setEnabled(false);
                  root.setOnClickListener(null);
              }
  
              // Get message view and apply the content.
  
              TextView textView = root instanceof TextView
                      ? (TextView) root
                      : (TextView) root.findViewById(R.id.title);
              textView.setText(action.title);
              if (mTypeface == null) mTypeface = textView.getTypeface();
              textView.setTypeface(mTypeface, style);
  
              Drawable icon = NotificationUtils.getDrawable(getContext(), notification, action.icon);
              if (icon != null) icon = onCreateActionIcon(icon);
  
              if (Device.hasJellyBeanMR1Api()) {
                  textView.setCompoundDrawablesRelative(icon, null, null, null);
              } else {
                  textView.setCompoundDrawables(icon, null, null, null);
              }
          }
      }
  
      @NonNull
      protected View onCreateActionView(@NonNull View view) {
          return view;
      }
  
      @Nullable
      protected Drawable onCreateActionIcon(@NonNull Drawable icon) {
          int size = getResources().getDimensionPixelSize(R.dimen.notification_action_icon_size);
          icon = icon.mutate();
          icon.setBounds(0, 0, size, size);
  
          // The matrix is stored in a single array, and its treated as follows:
          // [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]
          // When applied to a color [r, g, b, a], the resulting color is computed as (after clamping)
          //   R' = a*R + b*G + c*B + d*A + e;
          //   G' = f*R + g*G + h*B + i*A + j;
          //   B' = k*R + l*G + m*B + n*A + o;
          //   A' = p*R + q*G + r*B + s*A + t;
          ColorFilter colorFilter = new ColorMatrixColorFilter(new float[]{
                  0, 0, 0, 0, 255, // Red
                  0, 0, 0, 0, 255, // Green
                  0, 0, 0, 0, 255, // Blue
                  0, 0, 0, 1, 0 //    Alpha
          });
          icon.setColorFilter(colorFilter); // force white color
          return icon;
      }
  
      @Nullable
      // FIXME: Which RemoteInput should I use?
      protected RemoteInput getRemoteInput(@NonNull Action action) {
          return null;
          /*
          if (action.remoteInputs == null || action.remoteInputs.length == 0) return null;
          for (RemoteInput ri : action.remoteInputs) {
              if (ri.getAllowFreeFormInput()) {
                  return ri;
              }
          }
          return null;
          */
      }
  
      @LayoutRes
      protected int getActionLayoutResource() {
          return R.layout.notification_action2;
      }
  
      //-- TEXTABLE -------------------------------------------------------------
  
      /**
       * Base class for the {@link androidx.core.app.RemoteInput} view fields. For example:
       * the UI should provide the dropdown only if the
       * {@link androidx.core.app.RemoteInput#getAllowFreeFormInput()} if {@code false},
       * free text form otherwise.
       *
       * @author Artem Chepurnoy
       * @see androidx.core.app.RemoteInput
       * @since 3.1
       */
      private static abstract class Textable {
  
          public interface OnTextChangedListener {
  
              /**
               * Called on {@link #getText()} text has changed.
               */
              void onTextChanged(@Nullable CharSequence text);
  
          }
  
          @NonNull
          protected final Context mContext;
          @NonNull
          protected final RemoteInput mRemoteInput;
          @NonNull
          protected final NotificationActions mContainer;
          @NonNull
          protected final OnTextChangedListener mListener;
  
          public Textable(@NonNull NotificationActions container,
                          @NonNull RemoteInput remoteInput,
                          @NonNull OnTextChangedListener listener) {
              mContainer = container;
              mRemoteInput = remoteInput;
              mListener = listener;
              mContext = container.getContext();
          }
  
          /**
           * @return the view of this {@code Textable}.
           */
          @NonNull
          public abstract View getView();
  
          /**
           * @return the text the {@code Textable} is displaying.
           */
          @Nullable
          public abstract CharSequence getText();
  
          /**
           * Inflates a new view hierarchy from the specified xml resource. The view's root
           * is the {@link #mContainer}.
           *
           * @return the root View of the inflated hierarchy.
           */
          @NonNull
          protected final View inflate(@LayoutRes int layoutRes) {
              LayoutInflater inflater = (LayoutInflater) mContext
                      .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              return inflater.inflate(layoutRes, mContainer, false);
          }
  
      }
  
      /**
       * @author Artem Chepurnoy
       * @since 3.1
       */
      protected static class TextableFreeForm extends Textable {
  
          private EditText mEditText;
  
          private final TextWatcher mTextWatcher = new TextWatcher() {
              @Override
              public void beforeTextChanged(CharSequence s, int start, int count, int after) { /* unused */ }
  
              @Override
              public void onTextChanged(CharSequence s, int start, int before, int count) {
                  mListener.onTextChanged(s);
              }
  
              @Override
              public void afterTextChanged(Editable s) { /* unused */ }
          };
  
          public TextableFreeForm(@NonNull NotificationActions container,
                                  @NonNull RemoteInput remoteInput,
                                  @NonNull OnTextChangedListener listener) {
              super(container, remoteInput, listener);
              mEditText = onCreateEditText();
              mEditText.setHint(remoteInput.getLabel());
              mEditText.addTextChangedListener(mTextWatcher);
          }
  
          /**
           * {@inheritDoc}
           */
          @NonNull
          @Override
          public View getView() {
              return mEditText;
          }
  
          /**
           * {@inheritDoc}
           */
          @Nullable
          @Override
          public CharSequence getText() {
              return mEditText.getText();
          }
  
          @NonNull
          protected EditText onCreateEditText() {
              return (EditText) inflate(R.layout.notification_reply_free_form);
          }
  
      }
  
      /**
       * @author Artem Chepurnoy
       * @since 3.1
       */
      protected static class TextableRestrictedForm extends Textable {
  
          private final Spinner mSpinner;
  
          public TextableRestrictedForm(@NonNull NotificationActions container,
                                        @NonNull RemoteInput remoteInput,
                                        @NonNull OnTextChangedListener listener) {
              super(container, remoteInput, listener);
              ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(
                      mContext, android.R.layout.simple_spinner_dropdown_item,
                      remoteInput.getChoices());
              mSpinner = onCreateSpinner();
              mSpinner.setAdapter(adapter);
          }
  
          /**
           * {@inheritDoc}
           */
          @NonNull
          @Override
          public View getView() {
              return mSpinner;
          }
  
          /**
           * {@inheritDoc}
           */
          @Nullable
          @Override
          public CharSequence getText() {
              int pos = mSpinner.getSelectedItemPosition();
              return mRemoteInput.getChoices()[pos];
          }
  
          @NonNull
          protected Spinner onCreateSpinner() {
              return (Spinner) inflate(R.layout.notification_reply_restricted_form);
          }
  
      }
  
  }
  