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.layer;
  
  import android.graphics.Canvas;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.Rect;
  import android.support.annotation.ColorInt;
  import android.support.annotation.IntRange;
  import android.support.annotation.Nullable;
  import android.text.Layout;
  import android.text.StaticLayout;
  import android.text.TextPaint;
  import android.util.DisplayMetrics;
  import android.util.TypedValue;
  import android.view.Gravity;
  import com.actinarium.rhythm.ArgumentsBundle;
  import com.actinarium.rhythm.RhythmInflationException;
  import com.actinarium.rhythm.RhythmSpecLayer;
  import com.actinarium.rhythm.RhythmSpecLayerFactory;
  
  import java.util.Locale;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * A single horizontal keyline whose distance from the top is calculated from the width of current bounds and given
   * aspect ratio. Displays the label in the bottom right corner of the enclosed rectangle. <b>Experimental at the
   * moment, meaning its behavior, appearance, and parameters may change.</b> As of now, the keyline label is always 12dp
   * high, 24dp wide, has 10dp font that shrinks if not fitting, and is always within the enclosed rectangle, i.e.
   * overdrawing its bottom edge.
   *
   * @author Paul Danyliuk
   */
  public class RatioKeyline implements RhythmSpecLayer {
  
      public static final int DEFAULT_FILL_COLOR = 0xB03F51B5;
      public static final int DEFAULT_TEXT_COLOR = 0xC0FFFFFF;
  
      public static final int DEFAULT_THICKNESS = 2;         // px
  
      // todo: all defaults must be in px, not depending on density
      public static final int DEFAULT_TEXT_SIZE = 10;        // dp
      protected static final int DEFAULT_LABEL_HEIGHT = 12;  // dp
  
      @IntRange(from = 0)
      protected int mRatioX;
      @IntRange(from = 0)
      protected int mRatioY;
      @IntRange(from = 1)
      protected int mThickness;
  
      protected String mText;
      protected Paint mBackgroundPaint;
      protected TextPaint mTextPaint;
      protected Rect mTempRect;
      protected Path mLabelPath;
  
      // Text adjustment
      protected int mLabelRectWidth;
      protected int mLabelHeight;
      protected int mLabelSideWidth;
  
      public RatioKeyline(@IntRange(from = 0) int ratioX, @IntRange(from = 0) int ratioY, DisplayMetrics metrics) {
          this(metrics);
          mRatioX = ratioX;
          mRatioY = ratioY;
          mBackgroundPaint.setColor(DEFAULT_FILL_COLOR);
          mTextPaint.setColor(DEFAULT_TEXT_COLOR);
      }
  
      protected RatioKeyline(DisplayMetrics metrics) {
          mTempRect = new Rect();
          mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          mBackgroundPaint.setStyle(Paint.Style.FILL);
          mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
          mLabelPath = new Path();
  
          // Hard-coded defaults
          mTextPaint.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TEXT_SIZE, metrics));
  
          // Make default keyline path
          mLabelHeight = (int) ((DEFAULT_LABEL_HEIGHT) * metrics.density);
          mLabelRectWidth = mLabelHeight * 2;
          mLabelSideWidth = mLabelHeight * 3 / 4;
  
          mLabelPath.rLineTo(0, -mLabelHeight);
          mLabelPath.rLineTo(-mLabelRectWidth, 0);
          mLabelPath.rLineTo(-mLabelSideWidth, mLabelHeight);
          mLabelPath.close();
      }
  
      /**
       * Set ratio of the box this keyline should define, in form of two terms
       *
       * @param ratioX antecedent, horizontal component of the ratio (e.g. 16 in 16:9)
       * @param ratioY consequent, vertical component of the ratio (e.g. 9 in 16:9)
       * @return this for chaining
       */
      public RatioKeyline setRatio(@IntRange(from = 0) int ratioX, @IntRange(from = 0) int ratioY) {
          mRatioX = ratioX;
          mRatioY = ratioY;
          return this;
      }
  
      /**
       * Set arbitrary label text to this ratio keyline. Defaults to displaying ratio.
       *
       * @param text Text to display in keyline label. Set null to reset the label to display ratio.
       * @return this for chaining
       */
      public RatioKeyline setText(@Nullable String text) {
          mText = text;
          return this;
      }
  
      /**
       * Set ratio keyline thickness
       *
       * @param thickness Ratio keyline thickness, in pixels. At the moment, ratio keyline will be drawn within enclosed
       *                  bounds, so that regardless of thickness it doesn't cover pixels not within ratio box.
       * @return this for chaining
       */
      public RatioKeyline setThickness(@IntRange(from = 1) int thickness) {
          mThickness = thickness;
          return this;
      }
  
      /**
       * Set the color of ratio keyline and label background
       *
       * @param color Ratio keyline color, in #AARRGGBB format as usual
       * @return this for chaining
       */
      public RatioKeyline setKeylineColor(@ColorInt int color) {
          mBackgroundPaint.setColor(color);
          return this;
      }
  
      /**
       * Set ratio keyline label text color
       *
       * @param color Label text color, in #AARRGGBB format as usual
       * @return this for chaining
       */
      public RatioKeyline setTextColor(@ColorInt int color) {
          mTextPaint.setColor(color);
          return this;
      }
  
      @Override
      public void draw(Canvas canvas, Rect drawableBounds) {
          final int distanceTop;
          if (mRatioX == 0) {
              distanceTop = drawableBounds.top;
          } else {
              distanceTop = drawableBounds.top + drawableBounds.width() * mRatioY / mRatioX;
          }
          if (distanceTop > drawableBounds.height()) {
              return;
          }
  
          // Draw keyline
          canvas.drawRect(drawableBounds.left, distanceTop - mThickness, drawableBounds.right, distanceTop, mBackgroundPaint);
  
          // If no special text is set, display ratio
          if (mText == null) {
              mText = String.format(Locale.getDefault(), "%d:%d", mRatioX, mRatioY);
          }
  
          // Determine keyline label size/bounds
          StaticLayout layout = new StaticLayout(mText, mTextPaint, Integer.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
          int labelTextWidth = (int) (layout.getLineMax(0) + 0.5);
  
          // If text too big, re-measure
          while (labelTextWidth > mLabelRectWidth) {
              mTextPaint.setTextSize(mTextPaint.getTextSize() * 0.8f);
              layout = new StaticLayout(mText, mTextPaint, Integer.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
              labelTextWidth = (int) (layout.getLineMax(0) + 0.5);
          }
  
          // Draw label
          canvas.save();
          canvas.clipRect(drawableBounds.left, 0, drawableBounds.right, distanceTop - mThickness);
          canvas.translate(drawableBounds.right, distanceTop);
          canvas.drawPath(mLabelPath, mBackgroundPaint);
          canvas.restore();
  
          // Determine text position (on the baseline, in the center of
          canvas.save();
          mTempRect.set(-mLabelRectWidth, -mLabelHeight, 0, 0);
          Gravity.apply(Gravity.CENTER, labelTextWidth, layout.getHeight(), mTempRect, mTempRect);
          canvas.translate(drawableBounds.right + mTempRect.left, distanceTop + mTempRect.top);
          layout.draw(canvas);
          canvas.restore();
      }
  
      /**
       * A default factory that creates new {@link RatioKeyline} layers from config lines according to <a
       * href="https://github.com/Actinarium/Rhythm/wiki/Declarative-configuration#ratio-keyline">the docs</a>
       */
      public static class Factory implements RhythmSpecLayerFactory<RatioKeyline> {
  
          public static final String LAYER_TYPE = "ratio-keyline";
          public static final String ARG_RATIO = "ratio";
          public static final String ARG_TEXT = "text";
          public static final String ARG_THICKNESS = "thickness";
          public static final String ARG_COLOR = "color";
          public static final String ARG_TEXT_COLOR = "text-color";
  
          private static Pattern RATIO_VALUE_PATTERN = Pattern.compile("(\\d+):(\\d+)");
  
          @Override
          public RatioKeyline getForArguments(ArgumentsBundle argsBundle) {
              RatioKeyline keyline = new RatioKeyline(argsBundle.getDisplayMetrics());
  
              String ratio = argsBundle.getString(ARG_RATIO);
              if (ratio == null) {
                  throw new RhythmInflationException(
                          "Error when inflating ratio-keyline: 'ratio' argument is missing"
                  );
              }
              Matcher matcher = RATIO_VALUE_PATTERN.matcher(ratio);
              if (!matcher.matches()) {
                  throw new RhythmInflationException(
                          "Error when inflating ratio-keyline: 'ratio' argument is invalid, expected \"x:y\" pattern"
                  );
              }
              keyline.mRatioX = Integer.parseInt(matcher.group(1));
              keyline.mRatioY = Integer.parseInt(matcher.group(2));
              keyline.mText = argsBundle.getString(ARG_TEXT, ratio);
  
              keyline.mThickness = argsBundle.getDimensionPixelSize(ARG_THICKNESS, DEFAULT_THICKNESS);
              keyline.mBackgroundPaint.setColor(argsBundle.getColor(ARG_COLOR, DEFAULT_FILL_COLOR));
              keyline.mTextPaint.setColor(argsBundle.getColor(ARG_TEXT_COLOR, DEFAULT_TEXT_COLOR));
  
              return keyline;
          }
      }
  
  }