package abi39_0_0.host.exp.exponent.modules.api.components.datetimepicker;

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.app.TimePickerDialog;
import android.content.DialogInterface;
import android.content.Context;
import android.os.Handler;
import android.widget.TimePicker;
import android.view.View;
import android.widget.EditText;
import android.widget.NumberPicker;

class MinuteIntervalSnappableTimePickerDialog extends TimePickerDialog {
    private TimePicker mTimePicker;
    private int mTimePickerInterval;
    private RNTimePickerDisplay mDisplay;
    private final OnTimeSetListener mTimeSetListener;
    private Handler handler = new Handler();
    private Runnable runnable;
    private Context mContext;

    public MinuteIntervalSnappableTimePickerDialog(
            Context context,
            OnTimeSetListener listener,
            int hourOfDay,
            int minute,
            int minuteInterval,
            boolean is24HourView,
            RNTimePickerDisplay display
    ) {
        super(context, listener, hourOfDay, minute, is24HourView);
        mTimePickerInterval = minuteInterval;
        mTimeSetListener = listener;
        mDisplay = display;
        mContext = context;
    }

    public MinuteIntervalSnappableTimePickerDialog(
            Context context,
            int theme,
            OnTimeSetListener listener,
            int hourOfDay,
            int minute,
            int minuteInterval,
            boolean is24HourView,
            RNTimePickerDisplay display
    ) {
        super(context, theme, listener, hourOfDay, minute, is24HourView);
        mTimePickerInterval = minuteInterval;
        mTimeSetListener = listener;
        mDisplay = display;
        mContext = context;
    }

    public static boolean isValidMinuteInterval(int interval) {
      return interval >= 1 && interval <= 30 && 60 % interval == 0;
    }

    private boolean timePickerHasCustomMinuteInterval() {
        return mTimePickerInterval != RNConstants.DEFAULT_TIME_PICKER_INTERVAL;
    }

    private boolean isSpinner() {
        return mDisplay == RNTimePickerDisplay.SPINNER;
    }

    /**
     * Converts values returned from picker to actual minutes
     *
     * @param minutesOrSpinnerIndex the internal value of what the user had selected
     * @return returns 'real' minutes (0-59)
     */
    private int getRealMinutes(int minutesOrSpinnerIndex) {
        if (mDisplay == RNTimePickerDisplay.SPINNER) {
            return minutesOrSpinnerIndex * mTimePickerInterval;
        }

        return minutesOrSpinnerIndex;
    }

    private int getRealMinutes() {
        int minute = mTimePicker.getCurrentMinute();
        return getRealMinutes(minute);
    }

    /**
     * 'Snaps' real minutes or spinner value index to nearest valid value
     * in spinner mode you need to make sure to transform the picked value (which is an index)
     * to a real value before passing!
     *
     * @param realMinutes 'real' minutes (0-59)
     * @return nearest valid real minute
     */
    private int snapRealMinutesToInterval(int realMinutes) {
        float stepsInMinutes = (float) realMinutes / (float) mTimePickerInterval;

        int rounded = Math.round(stepsInMinutes) * mTimePickerInterval;
        return rounded == 60 ? rounded - mTimePickerInterval : rounded;
    }

    private void assertNotSpinner(String s) {
        if (isSpinner()) {
            throw new RuntimeException(s);
        }
    }

    /**
     * Determines if picked real minutes are ok with the minuteInterval
     *
     * @param realMinutes 'real' minutes (0-59)
     */
    private boolean minutesNeedCorrection(int realMinutes) {
        assertNotSpinner("minutesNeedCorrection is not intended to be used with spinner, spinner won't allow picking invalid values");

        return timePickerHasCustomMinuteInterval() && realMinutes != snapRealMinutesToInterval(realMinutes);
    }

    /**
     * Determines if the picker is in text input mode (keyboard icon in 'clock' mode)
     */
    private boolean pickerIsInTextInputMode() {
        int textInputPickerId = mContext.getResources().getIdentifier("input_mode", "id", "android");
        final View textInputPicker = this.findViewById(textInputPickerId);

        return textInputPicker != null && textInputPicker.hasFocus();
    }

    /**
     * Corrects minute values if they don't align with minuteInterval
     * <p>
     * in text input mode, correction will be postponed slightly to let the user finish the input
     * in clock mode we also delay it to give user visual cue about the correction
     * <p>
     *
     * @param view        the picker's view
     * @param hourOfDay   the picker's selected hours
     * @param correctedMinutes 'real' minutes (0-59) aligned to minute interval
     */
    private void correctEnteredMinutes(final TimePicker view, final int hourOfDay, final int correctedMinutes) {
        assertNotSpinner("spinner never needs to be corrected because wrong values are not offered to user (both in scrolling and textInput mode)!");
        final EditText textInput = (EditText) view.findFocus();

        // 'correction' callback
        runnable = new Runnable() {
            @Override
            public void run() {
                if (pickerIsInTextInputMode()) {
                    // set valid minutes && move caret to the end of input
                    view.setCurrentHour(hourOfDay);
                    view.setCurrentMinute(correctedMinutes);
                    textInput.setSelection(textInput.getText().length());
                } else {
                    view.setCurrentHour(hourOfDay);
                    // we need to set minutes to 0 for this to work on older android devices
                    view.setCurrentMinute(0);
                    view.setCurrentMinute(correctedMinutes);
                }
            }
        };

        handler.postDelayed(runnable, 500);
    }

    @Override
    public void onTimeChanged(final TimePicker view, final int hourOfDay, final int minute) {
        final int realMinutes = getRealMinutes(minute);
        // *always* remove pending 'validation' callbacks, otherwise a valid value might be rewritten
        handler.removeCallbacks(runnable);

        if (!isSpinner() && minutesNeedCorrection(realMinutes)) {
            int correctedMinutes = snapRealMinutesToInterval(realMinutes);

            // will fire another onTimeChanged
            correctEnteredMinutes(view, hourOfDay, correctedMinutes);
        } else {
            super.onTimeChanged(view, hourOfDay, minute);
        }
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (mTimePicker != null && which == BUTTON_POSITIVE && timePickerHasCustomMinuteInterval()) {
            final int hours = mTimePicker.getCurrentHour();

            final int realMinutes = getRealMinutes();
            int validMinutes = isSpinner() ? realMinutes : snapRealMinutesToInterval(realMinutes);

            if (mTimeSetListener != null) {
                mTimeSetListener.onTimeSet(mTimePicker, hours, validMinutes);
            }
        } else {
            super.onClick(dialog, which);
        }
    }

    @Override
    public void updateTime(int hourOfDay, int minuteOfHour) {
        if (timePickerHasCustomMinuteInterval()) {
            if (isSpinner()) {
                final int realMinutes = getRealMinutes();
                int selectedIndex = snapRealMinutesToInterval(realMinutes) / mTimePickerInterval;
                super.updateTime(hourOfDay, selectedIndex);
            } else {
                super.updateTime(hourOfDay, snapRealMinutesToInterval(minuteOfHour));
            }
        } else {
            super.updateTime(hourOfDay, minuteOfHour);
        }
    }

    /**
     * Apply visual style in 'spinner' mode
     * Adjust minutes to correspond selected interval
     */
    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (timePickerHasCustomMinuteInterval()) {
            setupPickerDialog();
        }
    }

    private void setupPickerDialog() {
        int timePickerId = mContext.getResources().getIdentifier("timePicker", "id", "android");
        mTimePicker = this.findViewById(timePickerId);

        int realMinuteBackup = mTimePicker.getCurrentMinute();

        if (isSpinner()) {
            setSpinnerDisplayedValues();
            int selectedIndex = snapRealMinutesToInterval(realMinuteBackup) / mTimePickerInterval;
            mTimePicker.setCurrentMinute(selectedIndex);
        } else {
            int snappedRealMinute = snapRealMinutesToInterval(realMinuteBackup);
            mTimePicker.setCurrentMinute(snappedRealMinute);
        }
    }

    @SuppressLint("DefaultLocale")
    private void setSpinnerDisplayedValues() {
        int minutePickerId = mContext.getResources().getIdentifier("minute", "id", "android");
        NumberPicker minutePicker = this.findViewById(minutePickerId);

        minutePicker.setMinValue(0);
        minutePicker.setMaxValue((60 / mTimePickerInterval) - 1);

        List<String> displayedValues = new ArrayList<>(60 / mTimePickerInterval);
        for (int displayedMinute = 0; displayedMinute < 60; displayedMinute += mTimePickerInterval) {
            displayedValues.add(String.format("%02d", displayedMinute));
        }

        minutePicker.setDisplayedValues(displayedValues.toArray(new String[0]));
    }

    @Override
    public void onDetachedFromWindow() {
        handler.removeCallbacks(runnable);
        super.onDetachedFromWindow();
    }
}
