/*
 * Copyright (C) 2019 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.car.settings.datausage;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.NumberPicker;

import androidx.annotation.VisibleForTesting;

import com.android.car.settings.R;
import com.android.car.ui.preference.CarUiDialogFragment;

/** Dialog that is used to pick the start day of month to track a data usage cycle. */
public class UsageCycleResetDayOfMonthPickerDialog extends CarUiDialogFragment {

    private static final String ARG_SELECTED_DAY_OF_MONTH = "arg_selected_day_of_month";

    /**
     * Defines the time frequency at which touch listener should be triggered when holding either
     * arrow button.
     */
    @VisibleForTesting
    static final int TIME_INTERVAL_MILLIS = 250;

    private static final int MIN_DAY = 1;
    private static final int MAX_DAY = 31;
    private ResetDayOfMonthPickedListener mResetDayOfMonthPickedListener;
    private NumberPicker mCycleDayOfMonthPicker;
    private View mUpArrow;
    private View mDownArrow;

    /**
     * Creates a new instance of the {@link UsageCycleResetDayOfMonthPickerDialog} with the {@link
     * NumberPicker} set to showing the value {@code startDayOfMonth}.
     */
    public static UsageCycleResetDayOfMonthPickerDialog newInstance(int startDayOfMonth) {
        UsageCycleResetDayOfMonthPickerDialog dialog = new UsageCycleResetDayOfMonthPickerDialog();
        Bundle args = new Bundle();
        args.putInt(ARG_SELECTED_DAY_OF_MONTH, startDayOfMonth);
        dialog.setArguments(args);
        return dialog;
    }

    /** Sets a {@link ResetDayOfMonthPickedListener}. */
    public void setResetDayOfMonthPickedListener(
            ResetDayOfMonthPickedListener resetDayOfMonthPickedListener) {
        mResetDayOfMonthPickedListener = resetDayOfMonthPickedListener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());

        // Use builder context to keep consistent theme.
        LayoutInflater inflater = LayoutInflater.from(builder.getContext());
        View view = inflater.inflate(R.layout.usage_cycle_reset_day_of_month_picker,
                /* root= */ null, /* attachToRoot= */ false);

        int cycleDayOfMonth = getArguments().getInt(ARG_SELECTED_DAY_OF_MONTH);
        if (cycleDayOfMonth < MIN_DAY) {
            cycleDayOfMonth = MIN_DAY;
        }
        if (cycleDayOfMonth > MAX_DAY) {
            cycleDayOfMonth = MAX_DAY;
        }

        mCycleDayOfMonthPicker = view.findViewById(R.id.cycle_reset_day_of_month);
        mCycleDayOfMonthPicker.setMinValue(MIN_DAY);
        mCycleDayOfMonthPicker.setMaxValue(MAX_DAY);
        mCycleDayOfMonthPicker.setValue(cycleDayOfMonth);
        mCycleDayOfMonthPicker.setWrapSelectorWheel(true);

        mUpArrow = view.findViewById(R.id.up_arrow_container);
        mUpArrow.setOnTouchListener(new CycleArrowTouchListener(
                () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() - 1),
                TIME_INTERVAL_MILLIS));

        mDownArrow = view.findViewById(R.id.down_arrow_container);
        mDownArrow.setOnTouchListener(new CycleArrowTouchListener(
                () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() + 1),
                TIME_INTERVAL_MILLIS));

        return builder
                .setTitle(R.string.cycle_reset_day_of_month_picker_title)
                .setView(view)
                .setPositiveButton(R.string.cycle_reset_day_of_month_picker_positive_button,
                        (dialog, which) -> {
                            if (which == DialogInterface.BUTTON_POSITIVE) {
                                if (mResetDayOfMonthPickedListener != null) {
                                    mResetDayOfMonthPickedListener.onDayOfMonthPicked(
                                            mCycleDayOfMonthPicker.getValue());
                                }
                            }
                        })
                .create();
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
    }

    /** Gets the current day of month selected by the {@link NumberPicker}. */
    public int getSelectedDayOfMonth() {
        return mCycleDayOfMonthPicker.getValue();
    }

    /** A listener that is called when a date is selected. */
    public interface ResetDayOfMonthPickedListener {
        /** A method that determines how to process the selected day of month. */
        void onDayOfMonthPicked(int dayOfMonth);
    }

    private static class CycleArrowTouchListener implements View.OnTouchListener {

        private final IntervalActionListener mIntervalActionListener;
        private final long mTimeIntervalMillis;

        private Handler mHandler = new Handler();
        private Runnable mAction;

        CycleArrowTouchListener(IntervalActionListener listener, long timeIntervalMillis) {
            mIntervalActionListener = listener;
            mTimeIntervalMillis = timeIntervalMillis;

            mAction = () -> {
                mHandler.postDelayed(this.mAction, mTimeIntervalMillis);
                maybeTriggerAction();
            };
        }

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mHandler.removeCallbacks(mAction);
                    mHandler.postDelayed(mAction, mTimeIntervalMillis);
                    maybeTriggerAction();
                    v.setPressed(true);
                    return true;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mHandler.removeCallbacks(mAction);
                    v.setPressed(false);
                    return true;
            }
            return false;
        }

        private void maybeTriggerAction() {
            if (mIntervalActionListener != null) {
                mIntervalActionListener.takeAction();
            }
        }

        /** Action that should be taken per time interval that the button is held. */
        interface IntervalActionListener {
            /** Defines the action to take at each time interval. */
            void takeAction();
        }
    }
}
