content: /*
   * Copyright (C) 2016 Actinarium
   *
   * 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.actinarium.rhythm.control;
  
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.os.Build;
  import android.support.annotation.IntDef;
  import android.support.annotation.Nullable;
  import android.util.AttributeSet;
  import android.widget.FrameLayout;
  import com.actinarium.rhythm.RhythmDrawable;
  import com.actinarium.rhythm.RhythmOverlay;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  /**
   * A {@link FrameLayout} implementation with rich Rhythm support. You can use this layout to wrap existing views and
   * draw a Rhythm overlay from specified group. The overlay can be positioned either under the view, over the view, or
   * just under/over the content (see {@link #setOverlayPosition(int)}). Both the group and overlay position can be set in
   * the layout XML with attributes <code>app:rhythmGroup</code> and <code>app:overlayPosition</code> respectively.
   *
   * @author Paul Danyliuk
   * @version $Id$
   */
  public class RhythmFrameLayout extends FrameLayout {
  
      /**
       * Use this value to indicate that this view is not connected to any {@link RhythmGroup} and shouldn’t display any
       * overlay
       */
      public static final int NO_GROUP = -1;
  
      // The following are to control where the overlay will be drawn
      // todo: test how overlay position affects its properties on view's translation, rotation, scaling etc
      /**
       * Draw the overlay under the background of this view. Pretty useless if this view has opaque background.
       */
      public static final int OVERLAY_POSITION_UNDER_BACKGROUND = 0;
      /**
       * Draw the overlay over view’s background but under child views. Default choice: useful yet non-obtrusive.
       */
      public static final int OVERLAY_POSITION_UNDER_CONTENT = 1;
      /**
       * Draw the overlay over the view’s content (sans foreground). Use this mode if you have nested opaque views that
       * occlude the overlay, and there are elements within, which you still need to align.
       */
      public static final int OVERLAY_POSITION_OVER_CONTENT = 2;
      /**
       * Same as {@link #OVERLAY_POSITION_OVER_CONTENT}, but this also draws over any foreground (ripples, touch
       * highlights etc).
       */
      public static final int OVERLAY_POSITION_OVER_FOREGROUND = 3;
  
      /**
       * Index of the group this view should get its {@link RhythmDrawable} from, or {@link #NO_GROUP}.
       */
      protected int mRhythmGroupIndex;
      @OverlayPosition
      protected int mOverlayPosition;
  
      /**
       * Obtained from {@link RhythmGroup}, which then controls this drawable, telling it what {@link RhythmOverlay} to
       * draw. Or <code>null</code>.
       */
      protected RhythmDrawable mRhythmDrawable;
      /**
       * Overlay bounds, relative to this view (since canvas is already translated to the origin point of this view)
       */
      protected Rect mBounds = new Rect();
  
      // Constructors
  
      public RhythmFrameLayout(Context context) {
          super(context);
          mRhythmGroupIndex = NO_GROUP;
          mOverlayPosition = OVERLAY_POSITION_UNDER_CONTENT;
          setWillNotDraw(false);
      }
  
      public RhythmFrameLayout(Context context, AttributeSet attrs) {
          super(context, attrs);
          initFromAttrs(context, attrs, 0, 0);
      }
  
      public RhythmFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          initFromAttrs(context, attrs, defStyleAttr, 0);
      }
  
      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public RhythmFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          initFromAttrs(context, attrs, defStyleAttr, defStyleRes);
      }
  
      private void initFromAttrs(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          TypedArray array = context
                  .getTheme()
                  .obtainStyledAttributes(attrs, R.styleable.RhythmFrameLayout, defStyleAttr, defStyleRes);
          try {
              int position = array.getInteger(R.styleable.RhythmFrameLayout_overlayPosition, OVERLAY_POSITION_UNDER_CONTENT);
              if (position == OVERLAY_POSITION_UNDER_BACKGROUND
                      || position == OVERLAY_POSITION_UNDER_CONTENT
                      || position == OVERLAY_POSITION_OVER_CONTENT
                      || position == OVERLAY_POSITION_OVER_FOREGROUND) {
                  //noinspection ResourceType
                  mOverlayPosition = position;
                  // We need to ensure draw()/onDraw() is called when overlay position is other than over content (otherwise it's drawn elsewhere)
                  setWillNotDraw(position == OVERLAY_POSITION_OVER_CONTENT);
              } else {
                  mOverlayPosition = OVERLAY_POSITION_UNDER_CONTENT;
                  setWillNotDraw(false);
              }
  
              mRhythmGroupIndex = array.getInteger(R.styleable.RhythmFrameLayout_rhythmGroup, NO_GROUP);
          } finally {
              array.recycle();
          }
      }
  
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          super.onLayout(changed, left, top, right, bottom);
  
          if (changed) {
              mBounds.set(0, 0, right - left, bottom - top);
          }
  
          if (mRhythmDrawable != null && changed) {
              // If there's a drawable, and layout has changed, we need to update its bounds
              mRhythmDrawable.setBounds(mBounds);
          } else if (mRhythmDrawable == null && mRhythmGroupIndex != NO_GROUP) {
              // If the group is set but there's no drawable yet, try to pull it from the group
              onRhythmGroupSet();
          }
      }
  
      @Override
      public void draw(Canvas canvas) {
          // Draw before or after everything? (if there's anything to draw)
          if (mRhythmDrawable == null) {
              super.draw(canvas);
          } else if (mOverlayPosition == OVERLAY_POSITION_UNDER_BACKGROUND) {
              mRhythmDrawable.draw(canvas);
              super.draw(canvas);
          } else if (mOverlayPosition == OVERLAY_POSITION_OVER_FOREGROUND) {
              super.draw(canvas);
              mRhythmDrawable.draw(canvas);
          } else {
              super.draw(canvas);
          }
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          // Draw before content? (if there's anything to draw)
          if (mRhythmDrawable != null && mOverlayPosition == OVERLAY_POSITION_UNDER_CONTENT) {
              mRhythmDrawable.draw(canvas);
          }
          super.onDraw(canvas);
      }
  
      @Override
      protected void dispatchDraw(Canvas canvas) {
          super.dispatchDraw(canvas);
          // Draw over content and children? (if there's anything to draw)
          if (mRhythmDrawable != null && mOverlayPosition == OVERLAY_POSITION_OVER_CONTENT) {
              mRhythmDrawable.draw(canvas);
          }
      }
  
      @Override
      protected boolean verifyDrawable(Drawable who) {
          return (mRhythmDrawable != null && mRhythmDrawable == who) || super.verifyDrawable(who);
      }
  
      // Getters/setters
  
      /**
       * @return Current Rhythm drawable
       */
      public RhythmDrawable getRhythmDrawable() {
          return mRhythmDrawable;
      }
  
      /**
       * Set a different {@link RhythmDrawable} to this view.
       *
       * @param drawable Rhythm drawable to set, or <code>null</code> to unlink this view from Rhythm
       */
      public void setRhythmDrawable(@Nullable RhythmDrawable drawable) {
          doSetRhythmDrawable(drawable);
          invalidate();
      }
  
      /**
       * @return Index of the Rhythm group this view is linked to, or {@link #NO_GROUP}
       */
      public int getRhythmGroupIndex() {
          return mRhythmGroupIndex;
      }
  
      /**
       * Link this drawable to a different {@link RhythmGroup} (identified by index), or to no group at all.
       *
       * @param rhythmGroupIndex Index of required {@link RhythmGroup} in {@link RhythmControl}, or {@link #NO_GROUP}
       */
      public void setRhythmGroupIndex(int rhythmGroupIndex) {
          mRhythmGroupIndex = rhythmGroupIndex;
          if (mRhythmGroupIndex != NO_GROUP) {
              onRhythmGroupSet();
          } else {
              doSetRhythmDrawable(null);
          }
          invalidate();
      }
  
      /**
       * @return Overlay position
       * @see #OVERLAY_POSITION_UNDER_BACKGROUND
       * @see #OVERLAY_POSITION_UNDER_CONTENT
       * @see #OVERLAY_POSITION_OVER_CONTENT
       * @see #OVERLAY_POSITION_OVER_FOREGROUND
       */
      public int getOverlayPosition() {
          return mOverlayPosition;
      }
  
      /**
       * Set overlay position
       *
       * @param overlayPosition New overlay position, one of overlay position constants
       * @see #OVERLAY_POSITION_UNDER_BACKGROUND
       * @see #OVERLAY_POSITION_UNDER_CONTENT
       * @see #OVERLAY_POSITION_OVER_CONTENT
       * @see #OVERLAY_POSITION_OVER_FOREGROUND
       */
      public void setOverlayPosition(@OverlayPosition int overlayPosition) {
          if (mOverlayPosition != overlayPosition) {
              mOverlayPosition = overlayPosition;
              setWillNotDraw(overlayPosition == OVERLAY_POSITION_OVER_CONTENT);
              invalidate();
          }
      }
  
      /**
       * Retrieves proper drawable from current group and links it to this view
       */
      private void onRhythmGroupSet() {
          // Request rhythm group from application context
          Context context = getContext().getApplicationContext();
          if (context instanceof RhythmControl.Host) {
              // This may fail with index out of bounds exception if incorrect group index is provided
              // But, IMHO, it is better to throw the exception than suppress it and leave the developer clueless
              final RhythmDrawable drawable = ((RhythmControl.Host) context).getRhythmControl()
                      .getGroup(mRhythmGroupIndex)
                      .makeDrawable();
              doSetRhythmDrawable(drawable);
          } else {
              // Uh-oh
              throw new ClassCastException(this + " cannot connect to RhythmControl. " +
                      "Check if your Application implements RhythmControl.Host");
          }
      }
  
      /**
       * Links new drawable to this view
       */
      private void doSetRhythmDrawable(@Nullable RhythmDrawable drawable) {
          if (mRhythmDrawable != null) {
              mRhythmDrawable.setCallback(null);
          }
          mRhythmDrawable = drawable;
          if (mRhythmDrawable != null) {
              mRhythmDrawable.setBounds(mBounds);
              mRhythmDrawable.setCallback(this);
          }
      }
  
      /**
       * Type def annotation for overlay position enum
       */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({OVERLAY_POSITION_UNDER_BACKGROUND, OVERLAY_POSITION_UNDER_CONTENT, OVERLAY_POSITION_OVER_CONTENT, OVERLAY_POSITION_OVER_FOREGROUND})
      public @interface OverlayPosition {
      }
  
  }
  