/*
 * Copyright (C) 2019 xuexiangjys(xuexiangjys@163.com)
 *
 * 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.xuexiang.xui.widget.imageview.crop;

import android.graphics.Rect;


/**
 * Abstract helper class to handle operations on a crop window Handle.
 */
abstract class HandleHelper {

    // Member Variables ////////////////////////////////////////////////////////

    private static final float UNFIXED_ASPECT_RATIO_CONSTANT = 1;
    private Edge mHorizontalEdge;
    private Edge mVerticalEdge;

    // Save the Pair object as a member variable to avoid having to instantiate
    // a new Object every time getActiveEdges() is called.
    private EdgePair mActiveEdges;

    // Constructor /////////////////////////////////////////////////////////////

    /**
     * Constructor.
     * 
     * @param horizontalEdge the horizontal edge associated with this handle;
     *            may be null
     * @param verticalEdge the vertical edge associated with this handle; may be
     *            null
     */
    HandleHelper(Edge horizontalEdge, Edge verticalEdge) {
        mHorizontalEdge = horizontalEdge;
        mVerticalEdge = verticalEdge;
        mActiveEdges = new EdgePair(mHorizontalEdge, mVerticalEdge);
    }

    // Package-Private Methods /////////////////////////////////////////////////

    /**
     * Updates the crop window by directly setting the Edge coordinates.
     * 
     * @param x the new x-coordinate of this handle
     * @param y the new y-coordinate of this handle
     * @param imageRect the bounding rectangle of the image
     * @param snapRadius the maximum distance (in pixels) at which the crop
     *            window should snap to the image
     */
    void updateCropWindow(float x,
                          float y,
                          Rect imageRect,
                          float snapRadius) {

        final EdgePair activeEdges = getActiveEdges();
        final Edge primaryEdge = activeEdges.primary;
        final Edge secondaryEdge = activeEdges.secondary;

        if (primaryEdge != null) {
            primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT);
        }

        if (secondaryEdge != null) {
            secondaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT);
        }
    }

    /**
     * Updates the crop window by directly setting the Edge coordinates; this
     * method maintains a given aspect ratio.
     * 
     * @param x the new x-coordinate of this handle
     * @param y the new y-coordinate of this handle
     * @param targetAspectRatio the aspect ratio to maintain
     * @param imageRect the bounding rectangle of the image
     * @param snapRadius the maximum distance (in pixels) at which the crop
     *            window should snap to the image
     */
    abstract void updateCropWindow(float x,
                                   float y,
                                   float targetAspectRatio,
                                   Rect imageRect,
                                   float snapRadius);

    /**
     * Gets the Edges associated with this handle (i.e. the Edges that should be
     * moved when this handle is dragged). This is used when we are not
     * maintaining the aspect ratio.
     * 
     * @return the active edge as a pair (the pair may contain null values for
     *         the <code>primary</code>, <code>secondary</code> or both fields)
     */
    EdgePair getActiveEdges() {
        return mActiveEdges;
    }

    /**
     * Gets the Edges associated with this handle as an ordered Pair. The
     * <code>primary</code> Edge in the pair is the determining side. This
     * method is used when we need to maintain the aspect ratio.
     * 
     * @param x the x-coordinate of the touch point
     * @param y the y-coordinate of the touch point
     * @param targetAspectRatio the aspect ratio that we are maintaining
     * @return the active edges as an ordered pair
     */
    EdgePair getActiveEdges(float x, float y, float targetAspectRatio) {

        // Calculate the aspect ratio if this handle were dragged to the given
        // x-y coordinate.
        final float potentialAspectRatio = getAspectRatio(x, y);

        // If the touched point is wider than the aspect ratio, then x
        // is the determining side. Else, y is the determining side.
        if (potentialAspectRatio > targetAspectRatio) {
            mActiveEdges.primary = mVerticalEdge;
            mActiveEdges.secondary = mHorizontalEdge;
        } else {
            mActiveEdges.primary = mHorizontalEdge;
            mActiveEdges.secondary = mVerticalEdge;
        }
        return mActiveEdges;
    }

    // Private Methods /////////////////////////////////////////////////////////

    /**
     * Gets the aspect ratio of the resulting crop window if this handle were
     * dragged to the given point.
     * 
     * @param x the x-coordinate
     * @param y the y-coordinate
     * @return the aspect ratio
     */
    private float getAspectRatio(float x, float y) {

        // Replace the active edge coordinate with the given touch coordinate.
        final float left = (mVerticalEdge == Edge.LEFT) ? x : Edge.LEFT.getCoordinate();
        final float top = (mHorizontalEdge == Edge.TOP) ? y : Edge.TOP.getCoordinate();
        final float right = (mVerticalEdge == Edge.RIGHT) ? x : Edge.RIGHT.getCoordinate();
        final float bottom = (mHorizontalEdge == Edge.BOTTOM) ? y : Edge.BOTTOM.getCoordinate();

        final float aspectRatio = AspectRatioUtil.calculateAspectRatio(left, top, right, bottom);

        return aspectRatio;
    }
}
