// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser.input;

import android.view.View;

import com.google.common.annotations.VisibleForTesting;

import org.chromium.content.browser.PositionObserver;

/**
 * CursorController for selecting a range of text.
 */
public abstract class SelectionHandleController implements CursorController {

    // The following constants match the ones in
    // third_party/WebKit/public/web/WebTextDirection.h
    private static final int TEXT_DIRECTION_DEFAULT = 0;
    private static final int TEXT_DIRECTION_LTR = 1;
    private static final int TEXT_DIRECTION_RTL = 2;

    /** The cursor controller images, lazily created when shown. */
    private HandleView mStartHandle, mEndHandle;

    /** Whether handles should show automatically when text is selected. */
    private boolean mAllowAutomaticShowing = true;

    /** Whether selection anchors are active. */
    private boolean mIsShowing;

    private View mParent;

    private int mFixedHandleX;
    private int mFixedHandleY;

    private PositionObserver mPositionObserver;

    public SelectionHandleController(View parent, PositionObserver positionObserver) {
        mParent = parent;
        mPositionObserver = positionObserver;
    }

    /** Automatically show selection anchors when text is selected. */
    public void allowAutomaticShowing() {
        mAllowAutomaticShowing = true;
    }

    /** Hide selection anchors, and don't automatically show them. */
    public void hideAndDisallowAutomaticShowing() {
        hide();
        mAllowAutomaticShowing = false;
    }

    @Override
    public boolean isShowing() {
        return mIsShowing;
    }

    @Override
    public void hide() {
        if (mIsShowing) {
            if (mStartHandle != null) mStartHandle.hide();
            if (mEndHandle != null) mEndHandle.hide();
            mIsShowing = false;
        }
    }

    void cancelFadeOutAnimation() {
        hide();
    }

    /**
     * Updates the selection for a movement of the given handle (which
     * should be the start handle or end handle) to coordinates x,y.
     * Note that this will not actually result in the handle moving to (x,y):
     * selectBetweenCoordinates(x1,y1,x2,y2) will trigger the selection and set the
     * actual coordinates later via set[Start|End]HandlePosition.
     */
    @Override
    public void updatePosition(HandleView handle, int x, int y) {
        selectBetweenCoordinates(mFixedHandleX, mFixedHandleY, x, y);
    }

    @Override
    public void beforeStartUpdatingPosition(HandleView handle) {
        HandleView fixedHandle = (handle == mStartHandle) ? mEndHandle : mStartHandle;
        mFixedHandleX = fixedHandle.getAdjustedPositionX();
        mFixedHandleY = fixedHandle.getLineAdjustedPositionY();
    }

    /**
     * The concrete implementation must trigger a selection between the given
     * coordinates and (possibly asynchronously) set the actual handle positions
     * after the selection is made via set[Start|End]HandlePosition.
     */
    protected abstract void selectBetweenCoordinates(int x1, int y1, int x2, int y2);

    /**
     * @return true iff this controller is being used to move the selection start.
     */
    boolean isSelectionStartDragged() {
        return mStartHandle != null && mStartHandle.isDragging();
    }

    /**
     * @return true iff this controller is being used to drag either the selection start or end.
     */
    public boolean isDragging() {
        return (mStartHandle != null && mStartHandle.isDragging()) ||
               (mEndHandle != null && mEndHandle.isDragging());
    }

    @Override
    public void onTouchModeChanged(boolean isInTouchMode) {
        if (!isInTouchMode) {
            hide();
        }
    }

    @Override
    public void onDetached() {}

    /**
     * Moves the start handle so that it points at the given coordinates.
     * @param x The start handle position X in physical pixels.
     * @param y The start handle position Y in physical pixels.
     */
    public void setStartHandlePosition(float x, float y) {
        mStartHandle.positionAt((int) x, (int) y);
    }

    /**
     * Moves the end handle so that it points at the given coordinates.
     * @param x The end handle position X in physical pixels.
     * @param y The end handle position Y in physical pixels.
     */
    public void setEndHandlePosition(float x, float y) {
        mEndHandle.positionAt((int) x, (int) y);
    }

    /**
     * If the handles are not visible, sets their visibility to View.VISIBLE and begins fading them
     * in.
     */
    public void beginHandleFadeIn() {
        mStartHandle.beginFadeIn();
        mEndHandle.beginFadeIn();
    }

    /**
     * Sets the start and end handles to the given visibility.
     */
    public void setHandleVisibility(int visibility) {
        mStartHandle.setVisibility(visibility);
        mEndHandle.setVisibility(visibility);
    }

    /**
     * Shows the handles if allowed.
     *
     * @param startDir Direction (left/right) of start handle.
     * @param endDir Direction (left/right) of end handle.
     */
    public void onSelectionChanged(int startDir, int endDir) {
        if (mAllowAutomaticShowing) {
            showHandles(startDir, endDir);
        }
    }

    /**
     * Sets both start and end position and show the handles.
     * Note: this method does not trigger a selection, see
     * selectBetweenCoordinates()
     *
     * @param startDir Direction (left/right) of start handle.
     * @param endDir Direction (left/right) of end handle.
     */
    public void showHandles(int startDir, int endDir) {
        createHandlesIfNeeded(startDir, endDir);
        showHandlesIfNeeded();
    }

    @VisibleForTesting
    public HandleView getStartHandleViewForTest() {
        return mStartHandle;
    }

    @VisibleForTesting
    public HandleView getEndHandleViewForTest() {
        return mEndHandle;
    }

    private void createHandlesIfNeeded(int startDir, int endDir) {
        if (mStartHandle == null) {
            mStartHandle = new HandleView(this,
                    startDir == TEXT_DIRECTION_RTL ? HandleView.RIGHT : HandleView.LEFT, mParent,
                    mPositionObserver);
        }
        if (mEndHandle == null) {
            mEndHandle = new HandleView(this,
                    endDir == TEXT_DIRECTION_RTL ? HandleView.LEFT : HandleView.RIGHT, mParent,
                    mPositionObserver);
        }
    }

    private void showHandlesIfNeeded() {
        if (!mIsShowing) {
            mIsShowing = true;
            mStartHandle.show();
            mEndHandle.show();
            setHandleVisibility(HandleView.VISIBLE);
        }
    }
}
