// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth;
// inexhaustible as the great rivers.
// When they come to an end;
// they begin again;
// like the days and months;
// they die and are reborn;
// like the four seasons."
//
// - Sun Tsu;
// "The Art of War"

package net.realify.lib.androidimagecropper;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;

/**
 * All the possible options that can be set to customize crop image.<br>
 * Initialized with default values.
 */
public class CropImageOptions implements Parcelable {

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

        @Override
        public CropImageOptions[] newArray(int size) {
          return new CropImageOptions[size];
        }
      };

  /** The shape of the cropping window. */
  public CropImageView.CropShape cropShape;

  /**
   * An edge of the crop window will snap to the corresponding edge of a specified bounding box when
   * the crop window edge is less than or equal to this distance (in pixels) away from the bounding
   * box edge. (in pixels)
   */
  public float snapRadius;

  /**
   * The radius of the touchable area around the handle. (in pixels)<br>
   * We are basing this value off of the recommended 48dp Rhythm.<br>
   * See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm
   */
  public float touchRadius;

  /** whether the guidelines should be on, off, or only showing when resizing. */
  public CropImageView.Guidelines guidelines;

  /** The initial scale type of the image in the crop image view */
  public CropImageView.ScaleType scaleType;

  /**
   * if to show crop overlay UI what contains the crop window UI surrounded by background over the
   * cropping image.<br>
   * default: true, may disable for animation or frame transition.
   */
  public boolean showCropOverlay;

  /**
   * if to show progress bar when image async loading/cropping is in progress.<br>
   * default: true, disable to provide custom progress bar UI.
   */
  public boolean showProgressBar;

  /**
   * if auto-zoom functionality is enabled.<br>
   * default: true.
   */
  public boolean autoZoomEnabled;

  /** if multi-touch should be enabled on the crop box default: false */
  public boolean multiTouchEnabled;

  /** The max zoom allowed during cropping. */
  public int maxZoom;

  /**
   * The initial crop window padding from image borders in percentage of the cropping image
   * dimensions.
   */
  public float initialCropWindowPaddingRatio;

  /** whether the width to height aspect ratio should be maintained or free to change. */
  public boolean fixAspectRatio;

  /** the X value of the aspect ratio. */
  public int aspectRatioX;

  /** the Y value of the aspect ratio. */
  public int aspectRatioY;

  /** the thickness of the guidelines lines in pixels. (in pixels) */
  public float borderLineThickness;

  /** the color of the guidelines lines */
  public int borderLineColor;

  /** thickness of the corner line. (in pixels) */
  public float borderCornerThickness;

  /** the offset of corner line from crop window border. (in pixels) */
  public float borderCornerOffset;

  /** the length of the corner line away from the corner. (in pixels) */
  public float borderCornerLength;

  /** the color of the corner line */
  public int borderCornerColor;

  /** the thickness of the guidelines lines. (in pixels) */
  public float guidelinesThickness;

  /** the color of the guidelines lines */
  public int guidelinesColor;

  /**
   * the color of the overlay background around the crop window cover the image parts not in the
   * crop window.
   */
  public int backgroundColor;

  /** the min width the crop window is allowed to be. (in pixels) */
  public int minCropWindowWidth;

  /** the min height the crop window is allowed to be. (in pixels) */
  public int minCropWindowHeight;

  /**
   * the min width the resulting cropping image is allowed to be, affects the cropping window
   * limits. (in pixels)
   */
  public int minCropResultWidth;

  /**
   * the min height the resulting cropping image is allowed to be, affects the cropping window
   * limits. (in pixels)
   */
  public int minCropResultHeight;

  /**
   * the max width the resulting cropping image is allowed to be, affects the cropping window
   * limits. (in pixels)
   */
  public int maxCropResultWidth;

  /**
   * the max height the resulting cropping image is allowed to be, affects the cropping window
   * limits. (in pixels)
   */
  public int maxCropResultHeight;

  /** the title of the {@link CropImageActivity} */
  public CharSequence activityTitle;

  /** the color to use for action bar items icons */
  public int activityMenuIconColor;

  /** the Android Uri to save the cropped image to */
  public Uri outputUri;

  /** the compression format to use when writing the image */
  public Bitmap.CompressFormat outputCompressFormat;

  /** the quality (if applicable) to use when writing the image (0 - 100) */
  public int outputCompressQuality;

  /** the width to resize the cropped image to (see options) */
  public int outputRequestWidth;

  /** the height to resize the cropped image to (see options) */
  public int outputRequestHeight;

  /** the resize method to use on the cropped bitmap (see options documentation) */
  public CropImageView.RequestSizeOptions outputRequestSizeOptions;

  /** if the result of crop image activity should not save the cropped image bitmap */
  public boolean noOutputImage;

  /** the initial rectangle to set on the cropping image after loading */
  public Rect initialCropWindowRectangle;

  /** the initial rotation to set on the cropping image after loading (0-360 degrees clockwise) */
  public int initialRotation;

  /** if to allow (all) rotation during cropping (activity) */
  public boolean allowRotation;

  /** if to allow (all) flipping during cropping (activity) */
  public boolean allowFlipping;

  /** if to allow counter-clockwise rotation during cropping (activity) */
  public boolean allowCounterRotation;

  /** the amount of degrees to rotate clockwise or counter-clockwise */
  public int rotationDegrees;

  /** whether the image should be flipped horizontally */
  public boolean flipHorizontally;

  /** whether the image should be flipped vertically */
  public boolean flipVertically;

  /** optional, the text of the crop menu crop button */
  public CharSequence cropMenuCropButtonTitle;

  /** optional image resource to be used for crop menu crop icon instead of text */
  public int cropMenuCropButtonIcon;

  /** Init options with defaults. */
  public CropImageOptions() {

    DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();

    cropShape = CropImageView.CropShape.RECTANGLE;
    snapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);
    touchRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, dm);
    guidelines = CropImageView.Guidelines.ON_TOUCH;
    scaleType = CropImageView.ScaleType.FIT_CENTER;
    showCropOverlay = true;
    showProgressBar = true;
    autoZoomEnabled = true;
    multiTouchEnabled = false;
    maxZoom = 4;
    initialCropWindowPaddingRatio = 0.1f;

    fixAspectRatio = false;
    aspectRatioX = 1;
    aspectRatioY = 1;

    borderLineThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);
    borderLineColor = Color.argb(170, 255, 255, 255);
    borderCornerThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm);
    borderCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, dm);
    borderCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, dm);
    borderCornerColor = Color.WHITE;

    guidelinesThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, dm);
    guidelinesColor = Color.argb(170, 255, 255, 255);
    backgroundColor = Color.argb(119, 0, 0, 0);

    minCropWindowWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm);
    minCropWindowHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm);
    minCropResultWidth = 40;
    minCropResultHeight = 40;
    maxCropResultWidth = 99999;
    maxCropResultHeight = 99999;

    activityTitle = "";
    activityMenuIconColor = 0;

    outputUri = Uri.EMPTY;
    outputCompressFormat = Bitmap.CompressFormat.JPEG;
    outputCompressQuality = 90;
    outputRequestWidth = 0;
    outputRequestHeight = 0;
    outputRequestSizeOptions = CropImageView.RequestSizeOptions.NONE;
    noOutputImage = false;

    initialCropWindowRectangle = null;
    initialRotation = -1;
    allowRotation = true;
    allowFlipping = true;
    allowCounterRotation = false;
    rotationDegrees = 90;
    flipHorizontally = false;
    flipVertically = false;
    cropMenuCropButtonTitle = null;

    cropMenuCropButtonIcon = 0;
  }

  /** Create object from parcel. */
  protected CropImageOptions(Parcel in) {
    cropShape = CropImageView.CropShape.values()[in.readInt()];
    snapRadius = in.readFloat();
    touchRadius = in.readFloat();
    guidelines = CropImageView.Guidelines.values()[in.readInt()];
    scaleType = CropImageView.ScaleType.values()[in.readInt()];
    showCropOverlay = in.readByte() != 0;
    showProgressBar = in.readByte() != 0;
    autoZoomEnabled = in.readByte() != 0;
    multiTouchEnabled = in.readByte() != 0;
    maxZoom = in.readInt();
    initialCropWindowPaddingRatio = in.readFloat();
    fixAspectRatio = in.readByte() != 0;
    aspectRatioX = in.readInt();
    aspectRatioY = in.readInt();
    borderLineThickness = in.readFloat();
    borderLineColor = in.readInt();
    borderCornerThickness = in.readFloat();
    borderCornerOffset = in.readFloat();
    borderCornerLength = in.readFloat();
    borderCornerColor = in.readInt();
    guidelinesThickness = in.readFloat();
    guidelinesColor = in.readInt();
    backgroundColor = in.readInt();
    minCropWindowWidth = in.readInt();
    minCropWindowHeight = in.readInt();
    minCropResultWidth = in.readInt();
    minCropResultHeight = in.readInt();
    maxCropResultWidth = in.readInt();
    maxCropResultHeight = in.readInt();
    activityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    activityMenuIconColor = in.readInt();
    outputUri = in.readParcelable(Uri.class.getClassLoader());
    outputCompressFormat = Bitmap.CompressFormat.valueOf(in.readString());
    outputCompressQuality = in.readInt();
    outputRequestWidth = in.readInt();
    outputRequestHeight = in.readInt();
    outputRequestSizeOptions = CropImageView.RequestSizeOptions.values()[in.readInt()];
    noOutputImage = in.readByte() != 0;
    initialCropWindowRectangle = in.readParcelable(Rect.class.getClassLoader());
    initialRotation = in.readInt();
    allowRotation = in.readByte() != 0;
    allowFlipping = in.readByte() != 0;
    allowCounterRotation = in.readByte() != 0;
    rotationDegrees = in.readInt();
    flipHorizontally = in.readByte() != 0;
    flipVertically = in.readByte() != 0;
    cropMenuCropButtonTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    cropMenuCropButtonIcon = in.readInt();
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(cropShape.ordinal());
    dest.writeFloat(snapRadius);
    dest.writeFloat(touchRadius);
    dest.writeInt(guidelines.ordinal());
    dest.writeInt(scaleType.ordinal());
    dest.writeByte((byte) (showCropOverlay ? 1 : 0));
    dest.writeByte((byte) (showProgressBar ? 1 : 0));
    dest.writeByte((byte) (autoZoomEnabled ? 1 : 0));
    dest.writeByte((byte) (multiTouchEnabled ? 1 : 0));
    dest.writeInt(maxZoom);
    dest.writeFloat(initialCropWindowPaddingRatio);
    dest.writeByte((byte) (fixAspectRatio ? 1 : 0));
    dest.writeInt(aspectRatioX);
    dest.writeInt(aspectRatioY);
    dest.writeFloat(borderLineThickness);
    dest.writeInt(borderLineColor);
    dest.writeFloat(borderCornerThickness);
    dest.writeFloat(borderCornerOffset);
    dest.writeFloat(borderCornerLength);
    dest.writeInt(borderCornerColor);
    dest.writeFloat(guidelinesThickness);
    dest.writeInt(guidelinesColor);
    dest.writeInt(backgroundColor);
    dest.writeInt(minCropWindowWidth);
    dest.writeInt(minCropWindowHeight);
    dest.writeInt(minCropResultWidth);
    dest.writeInt(minCropResultHeight);
    dest.writeInt(maxCropResultWidth);
    dest.writeInt(maxCropResultHeight);
    TextUtils.writeToParcel(activityTitle, dest, flags);
    dest.writeInt(activityMenuIconColor);
    dest.writeParcelable(outputUri, flags);
    dest.writeString(outputCompressFormat.name());
    dest.writeInt(outputCompressQuality);
    dest.writeInt(outputRequestWidth);
    dest.writeInt(outputRequestHeight);
    dest.writeInt(outputRequestSizeOptions.ordinal());
    dest.writeInt(noOutputImage ? 1 : 0);
    dest.writeParcelable(initialCropWindowRectangle, flags);
    dest.writeInt(initialRotation);
    dest.writeByte((byte) (allowRotation ? 1 : 0));
    dest.writeByte((byte) (allowFlipping ? 1 : 0));
    dest.writeByte((byte) (allowCounterRotation ? 1 : 0));
    dest.writeInt(rotationDegrees);
    dest.writeByte((byte) (flipHorizontally ? 1 : 0));
    dest.writeByte((byte) (flipVertically ? 1 : 0));
    TextUtils.writeToParcel(cropMenuCropButtonTitle, dest, flags);
    dest.writeInt(cropMenuCropButtonIcon);
  }

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

  /**
   * Validate all the options are withing valid range.
   *
   * @throws IllegalArgumentException if any of the options is not valid
   */
  public void validate() {
    if (maxZoom < 0) {
      throw new IllegalArgumentException("Cannot set max zoom to a number < 1");
    }
    if (touchRadius < 0) {
      throw new IllegalArgumentException("Cannot set touch radius value to a number <= 0 ");
    }
    if (initialCropWindowPaddingRatio < 0 || initialCropWindowPaddingRatio >= 0.5) {
      throw new IllegalArgumentException(
          "Cannot set initial crop window padding value to a number < 0 or >= 0.5");
    }
    if (aspectRatioX <= 0) {
      throw new IllegalArgumentException(
          "Cannot set aspect ratio value to a number less than or equal to 0.");
    }
    if (aspectRatioY <= 0) {
      throw new IllegalArgumentException(
          "Cannot set aspect ratio value to a number less than or equal to 0.");
    }
    if (borderLineThickness < 0) {
      throw new IllegalArgumentException(
          "Cannot set line thickness value to a number less than 0.");
    }
    if (borderCornerThickness < 0) {
      throw new IllegalArgumentException(
          "Cannot set corner thickness value to a number less than 0.");
    }
    if (guidelinesThickness < 0) {
      throw new IllegalArgumentException(
          "Cannot set guidelines thickness value to a number less than 0.");
    }
    if (minCropWindowHeight < 0) {
      throw new IllegalArgumentException(
          "Cannot set min crop window height value to a number < 0 ");
    }
    if (minCropResultWidth < 0) {
      throw new IllegalArgumentException("Cannot set min crop result width value to a number < 0 ");
    }
    if (minCropResultHeight < 0) {
      throw new IllegalArgumentException(
          "Cannot set min crop result height value to a number < 0 ");
    }
    if (maxCropResultWidth < minCropResultWidth) {
      throw new IllegalArgumentException(
          "Cannot set max crop result width to smaller value than min crop result width");
    }
    if (maxCropResultHeight < minCropResultHeight) {
      throw new IllegalArgumentException(
          "Cannot set max crop result height to smaller value than min crop result height");
    }
    if (outputRequestWidth < 0) {
      throw new IllegalArgumentException("Cannot set request width value to a number < 0 ");
    }
    if (outputRequestHeight < 0) {
      throw new IllegalArgumentException("Cannot set request height value to a number < 0 ");
    }
    if (rotationDegrees < 0 || rotationDegrees > 360) {
      throw new IllegalArgumentException(
          "Cannot set rotation degrees value to a number < 0 or > 360");
    }
  }
}
