/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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.android.systemui.accessibility;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;

import com.android.systemui.R;

/**
 * A basic control to move the mirror window.
 */
class SimpleMirrorWindowControl extends MirrorWindowControl implements View.OnClickListener,
        View.OnTouchListener, View.OnLongClickListener {

    private static final String TAG = "SimpleMirrorWindowControl";
    private static final int MOVE_FRAME_DURATION_MS = 100;
    private final int mMoveFrameAmountShort;
    private final int mMoveFrameAmountLong;

    private boolean mIsDragState;
    private boolean mShouldSetTouchStart;

    @Nullable private MoveWindowTask mMoveWindowTask;
    private PointF mLastDrag = new PointF();
    private final Handler mHandler;

    SimpleMirrorWindowControl(Context context, Handler handler) {
        super(context);
        mHandler = handler;
        final Resources resource = context.getResources();
        mMoveFrameAmountShort = resource.getDimensionPixelSize(
                R.dimen.magnification_frame_move_short);
        mMoveFrameAmountLong = resource.getDimensionPixelSize(
                R.dimen.magnification_frame_move_long);
    }

    @Override
    String getWindowTitle() {
        return mContext.getString(R.string.magnification_controls_title);
    }

    @Override
    View onCreateView(LayoutInflater layoutInflater, Point viewSize) {
        final View view = layoutInflater.inflate(R.layout.magnifier_controllers, null);
        final View leftControl = view.findViewById(R.id.left_control);
        final View upControl = view.findViewById(R.id.up_control);
        final View rightControl = view.findViewById(R.id.right_control);
        final View bottomControl = view.findViewById(R.id.down_control);

        leftControl.setOnClickListener(this);
        upControl.setOnClickListener(this);
        rightControl.setOnClickListener(this);
        bottomControl.setOnClickListener(this);

        leftControl.setOnLongClickListener(this);
        upControl.setOnLongClickListener(this);
        rightControl.setOnLongClickListener(this);
        bottomControl.setOnLongClickListener(this);

        leftControl.setOnTouchListener(this);
        upControl.setOnTouchListener(this);
        rightControl.setOnTouchListener(this);
        bottomControl.setOnTouchListener(this);

        view.setOnTouchListener(this);
        view.setOnLongClickListener(this::onViewRootLongClick);
        return view;
    }

    private Point findOffset(View v, int moveFrameAmount) {
        final Point offset = mTmpPoint;
        offset.set(0, 0);
        if (v.getId() == R.id.left_control) {
            mTmpPoint.x = -moveFrameAmount;
        } else if (v.getId() == R.id.up_control) {
            mTmpPoint.y = -moveFrameAmount;
        } else if (v.getId() == R.id.right_control) {
            mTmpPoint.x = moveFrameAmount;
        } else if (v.getId() == R.id.down_control) {
            mTmpPoint.y = moveFrameAmount;
        } else {
            Log.w(TAG, "findOffset move is zero ");
        }
        return mTmpPoint;
    }

    @Override
    public void onClick(View v) {
        if (mMirrorWindowDelegate != null) {
            Point offset = findOffset(v, mMoveFrameAmountShort);
            mMirrorWindowDelegate.move(offset.x, offset.y);
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (handleDragState(event)) {
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mMoveWindowTask != null) {
                    mMoveWindowTask.cancel();
                    mMoveWindowTask = null;
                }
                break;
        }
        return false;
    }

    @Override
    public boolean onLongClick(View v) {
        Point offset = findOffset(v, mMoveFrameAmountLong);
        mMoveWindowTask = new MoveWindowTask(mMirrorWindowDelegate, mHandler, offset.x, offset.y,
                MOVE_FRAME_DURATION_MS);
        mMoveWindowTask.schedule();
        return true;
    }

    private boolean onViewRootLongClick(View view) {
        mIsDragState = true;
        mShouldSetTouchStart = true;
        return true;
    }

    private boolean handleDragState(final MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (mIsDragState) {
                    if (mShouldSetTouchStart) {
                        mLastDrag.set(event.getRawX(), event.getRawY());
                        mShouldSetTouchStart = false;
                    }
                    int xDiff = (int) (event.getRawX() - mLastDrag.x);
                    int yDiff = (int) (event.getRawY() - mLastDrag.y);
                    move(xDiff, yDiff);
                    mLastDrag.set(event.getRawX(), event.getRawY());
                    return true;
                }
                return false;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mIsDragState) {
                    mIsDragState = false;
                    return true;
                }
                return false;
            default:
                return false;
        }
    }

    /**
     * A timer task to move the mirror window periodically.
     */
    static class MoveWindowTask implements Runnable {
        private final MirrorWindowDelegate mMirrorWindowDelegate;
        private final int mXOffset;
        private final int mYOffset;
        private final Handler mHandler;
        /** Time in milliseconds between successive task executions.*/
        private long mPeriod;
        private boolean mCancel;

        MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
                int yOffset, long period) {
            mMirrorWindowDelegate = windowDelegate;
            mXOffset = xOffset;
            mYOffset = yOffset;
            mHandler = handler;
            mPeriod = period;
        }

        @Override
        public void run() {
            if (mCancel) {
                return;
            }
            mMirrorWindowDelegate.move(mXOffset, mYOffset);
            schedule();
        }

        /**
         * Schedules the specified task periodically and immediately.
         */
        void schedule() {
            mHandler.postDelayed(this, mPeriod);
            mCancel = false;
        }

        void cancel() {
            mHandler.removeCallbacks(this);
            mCancel = true;
        }
    }
}
