/*
 * Copyright (C) 2017 Simon Vig Therkildsen
 *
 * 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.datetimepicker.date;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.fragment.app.DialogFragment;
import com.android.datetimepicker.HapticFeedbackController;
import com.android.datetimepicker.R;
import com.android.datetimepicker.Utils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;

/**
 * Dialog allowing users to select a date.
 */
public class DatePickerDialog extends DialogFragment
    implements OnClickListener, DatePickerController {

  private static final String TAG = "DatePickerDialog";

  private static final String ARG_YEAR =
      "net.simonvt.cathode.ui.dialog.SelectHistoryDateFragment.year";
  private static final String ARG_MONTH =
      "net.simonvt.cathode.ui.dialog.SelectHistoryDateFragment.month";
  private static final String ARG_DAY =
      "net.simonvt.cathode.ui.dialog.SelectHistoryDateFragment.day";

  private static final int UNINITIALIZED = -1;
  private static final int MONTH_AND_DAY_VIEW = 0;
  private static final int YEAR_VIEW = 1;

  private static final String KEY_SELECTED_YEAR = "year";
  private static final String KEY_SELECTED_MONTH = "month";
  private static final String KEY_SELECTED_DAY = "day";
  private static final String KEY_LIST_POSITION = "list_position";
  private static final String KEY_WEEK_START = "week_start";
  private static final String KEY_YEAR_START = "year_start";
  private static final String KEY_YEAR_END = "year_end";
  private static final String KEY_CURRENT_VIEW = "current_view";
  private static final String KEY_LIST_POSITION_OFFSET = "list_position_offset";

  private static final int DEFAULT_START_YEAR = 1900;
  private static final int DEFAULT_END_YEAR = 2100;

  private static final int ANIMATION_DURATION = 300;
  private static final int ANIMATION_DELAY = 500;

  private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
  private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());

  private final Calendar mCalendar = Calendar.getInstance();
  private OnDateSetListener mCallBack;
  private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();

  private AccessibleDateAnimator mAnimator;

  private TextView mDayOfWeekView;
  private LinearLayout mMonthAndDayView;
  private TextView mSelectedMonthTextView;
  private TextView mSelectedDayTextView;
  private TextView mYearView;
  private DayPickerView mDayPickerView;
  private YearPickerView mYearPickerView;
  private Button mDoneButton;

  private int mCurrentView = UNINITIALIZED;

  private int mWeekStart = mCalendar.getFirstDayOfWeek();
  private int mMinYear = DEFAULT_START_YEAR;
  private int mMaxYear = DEFAULT_END_YEAR;
  private Calendar mMinDate;
  private Calendar mMaxDate;

  private HapticFeedbackController mHapticFeedbackController;

  private boolean mDelayAnimation = true;

  // Accessibility strings.
  private String mDayPickerDescription;
  private String mSelectDay;
  private String mYearPickerDescription;
  private String mSelectYear;

  /**
   * The callback used to indicate the user is done filling in the date.
   */
  public interface OnDateSetListener {

    /**
     * @param dialog The dialog associated with this listener.
     * @param year The year that was set.
     * @param monthOfYear The month that was set (0-11) for compatibility
     * with {@link java.util.Calendar}.
     * @param dayOfMonth The day of the month that was set.
     */
    void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth);
  }

  /**
   * The callback used to notify other date picker components of a change in selected date.
   */
  public interface OnDateChangedListener {

    public void onDateChanged();
  }

  public DatePickerDialog() {
    // Empty constructor required for dialog fragment.
  }

  /**
   * @param year The initial year of the dialog.
   * @param monthOfYear The initial month of the dialog.
   * @param dayOfMonth The initial day of the dialog.
   */
  public static DatePickerDialog newInstance(int year, int monthOfYear, int dayOfMonth) {
    DatePickerDialog ret = new DatePickerDialog();

    Bundle args = new Bundle();
    args.putInt(ARG_YEAR, year);
    args.putInt(ARG_MONTH, monthOfYear);
    args.putInt(ARG_DAY, dayOfMonth);
    ret.setArguments(args);

    return ret;
  }

  @Override public void onAttach(Activity activity) {
    super.onAttach(activity);
    if (activity instanceof OnDateChangedListener) {
      mCallBack = (OnDateSetListener) activity;
    } else {
      mCallBack = (OnDateSetListener) getTargetFragment();
    }
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final Activity activity = getActivity();
    activity.getWindow()
        .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
    if (savedInstanceState != null) {
      mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
      mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
      mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
    } else {
      Bundle args = getArguments();
      mCalendar.set(Calendar.YEAR, args.getInt(ARG_YEAR));
      mCalendar.set(Calendar.MONTH, args.getInt(ARG_MONTH));
      mCalendar.set(Calendar.DAY_OF_MONTH, args.getInt(ARG_DAY));
    }
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR));
    outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH));
    outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH));
    outState.putInt(KEY_WEEK_START, mWeekStart);
    outState.putInt(KEY_YEAR_START, mMinYear);
    outState.putInt(KEY_YEAR_END, mMaxYear);
    outState.putInt(KEY_CURRENT_VIEW, mCurrentView);
    int listPosition = -1;
    if (mCurrentView == MONTH_AND_DAY_VIEW) {
      listPosition = mDayPickerView.getMostVisiblePosition();
    } else if (mCurrentView == YEAR_VIEW) {
      listPosition = mYearPickerView.getFirstVisiblePosition();
      outState.putInt(KEY_LIST_POSITION_OFFSET, mYearPickerView.getFirstPositionOffset());
    }
    outState.putInt(KEY_LIST_POSITION, listPosition);
  }

  @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);

    View view = inflater.inflate(R.layout.date_picker_dialog, null);

    //mDayOfWeekView = (TextView) view.findViewById(R.id.date_picker_header);
    mMonthAndDayView = (LinearLayout) view.findViewById(R.id.date_picker_month_and_day);
    mMonthAndDayView.setOnClickListener(this);
    mSelectedMonthTextView = (TextView) view.findViewById(R.id.date_picker_month);
    mSelectedDayTextView = (TextView) view.findViewById(R.id.date_picker_day);
    mYearView = (TextView) view.findViewById(R.id.date_picker_year);
    mYearView.setOnClickListener(this);

    int listPosition = -1;
    int listPositionOffset = 0;
    int currentView = MONTH_AND_DAY_VIEW;
    if (savedInstanceState != null) {
      mWeekStart = savedInstanceState.getInt(KEY_WEEK_START);
      mMinYear = savedInstanceState.getInt(KEY_YEAR_START);
      mMaxYear = savedInstanceState.getInt(KEY_YEAR_END);
      currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW);
      listPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
      listPositionOffset = savedInstanceState.getInt(KEY_LIST_POSITION_OFFSET);
    }

    final Activity activity = getActivity();
    mDayPickerView = new DayPickerView(activity, this);
    mYearPickerView = new YearPickerView(activity, this);

    Resources res = getResources();
    mDayPickerDescription = res.getString(R.string.day_picker_description);
    mSelectDay = res.getString(R.string.select_day);
    mYearPickerDescription = res.getString(R.string.year_picker_description);
    mSelectYear = res.getString(R.string.select_year);

    mAnimator = (AccessibleDateAnimator) view.findViewById(R.id.animator);
    mAnimator.addView(mDayPickerView);
    mAnimator.addView(mYearPickerView);
    mAnimator.setDateMillis(mCalendar.getTimeInMillis());
    // TODO: Replace with animation decided upon by the design team.
    Animation animation = new AlphaAnimation(0.0f, 1.0f);
    animation.setDuration(ANIMATION_DURATION);
    mAnimator.setInAnimation(animation);
    // TODO: Replace with animation decided upon by the design team.
    Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
    animation2.setDuration(ANIMATION_DURATION);
    mAnimator.setOutAnimation(animation2);

    mDoneButton = (Button) view.findViewById(R.id.ok);
    mDoneButton.setOnClickListener(new OnClickListener() {

      @Override public void onClick(View v) {
        tryVibrate();
        if (mCallBack != null) {
          mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR),
              mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
        }
        dismiss();
      }
    });

    View cancelButton = view.findViewById(R.id.cancel);
    cancelButton.setOnClickListener(new OnClickListener() {
      @Override public void onClick(View v) {
        tryVibrate();
        dismiss();
      }
    });

    updateDisplay(false);
    setCurrentView(currentView);

    if (listPosition != -1) {
      if (currentView == MONTH_AND_DAY_VIEW) {
        mDayPickerView.setSelection(listPosition);
      } else if (currentView == YEAR_VIEW) {
        mYearPickerView.postSetSelectionFromTop(listPosition, listPositionOffset);
      }
    }

    mHapticFeedbackController = new HapticFeedbackController(activity);
    return view;
  }

  @Override public void onResume() {
    super.onResume();
    mHapticFeedbackController.start();
  }

  @Override public void onPause() {
    super.onPause();
    mHapticFeedbackController.stop();
  }

  private void setCurrentView(final int viewIndex) {
    long millis = mCalendar.getTimeInMillis();

    switch (viewIndex) {
      case MONTH_AND_DAY_VIEW:
        ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f, 1.05f);
        if (mDelayAnimation) {
          pulseAnimator.setStartDelay(ANIMATION_DELAY);
          mDelayAnimation = false;
        }
        mDayPickerView.onDateChanged();
        if (mCurrentView != viewIndex) {
          mMonthAndDayView.setSelected(true);
          mYearView.setSelected(false);
          mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
          mCurrentView = viewIndex;
        }
        pulseAnimator.start();

        int flags = DateUtils.FORMAT_SHOW_DATE;
        String dayString = DateUtils.formatDateTime(getActivity(), millis, flags);
        mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
        Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay);
        break;
      case YEAR_VIEW:
        pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f);
        if (mDelayAnimation) {
          pulseAnimator.setStartDelay(ANIMATION_DELAY);
          mDelayAnimation = false;
        }
        mYearPickerView.onDateChanged();
        if (mCurrentView != viewIndex) {
          mMonthAndDayView.setSelected(false);
          mYearView.setSelected(true);
          mAnimator.setDisplayedChild(YEAR_VIEW);
          mCurrentView = viewIndex;
        }
        pulseAnimator.start();

        CharSequence yearString = YEAR_FORMAT.format(millis);
        mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
        Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear);
        break;
    }
  }

  private void updateDisplay(boolean announce) {
    if (mDayOfWeekView != null) {
      mDayOfWeekView.setText(
          mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault())
              .toUpperCase(Locale.getDefault()));
    }

    mSelectedMonthTextView.setText(
        mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())
            .toUpperCase(Locale.getDefault()));
    mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime()));
    mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));

    // Accessibility.
    long millis = mCalendar.getTimeInMillis();
    mAnimator.setDateMillis(millis);
    int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
    String monthAndDayText = DateUtils.formatDateTime(getActivity(), millis, flags);
    mMonthAndDayView.setContentDescription(monthAndDayText);

    if (announce) {
      flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
      String fullDateText = DateUtils.formatDateTime(getActivity(), millis, flags);
      Utils.tryAccessibilityAnnounce(mAnimator, fullDateText);
    }
  }

  public void setFirstDayOfWeek(int startOfWeek) {
    if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
      throw new IllegalArgumentException(
          "Value must be between Calendar.SUNDAY and " + "Calendar.SATURDAY");
    }
    mWeekStart = startOfWeek;
    if (mDayPickerView != null) {
      mDayPickerView.onChange();
    }
  }

  public void setYearRange(int startYear, int endYear) {
    if (endYear <= startYear) {
      throw new IllegalArgumentException("Year end must be larger than year start");
    }
    mMinYear = startYear;
    mMaxYear = endYear;
    if (mDayPickerView != null) {
      mDayPickerView.onChange();
    }
  }

  /**
   * Sets the minimal date supported by this DatePicker. Dates before (but not including) the
   * specified date will be disallowed from being selected.
   *
   * @param calendar a Calendar object set to the year, month, day desired as the mindate.
   */
  public void setMinDate(Calendar calendar) {
    mMinDate = calendar;

    if (mDayPickerView != null) {
      mDayPickerView.onChange();
    }
  }

  /**
   * @return The minimal date supported by this DatePicker. Null if it has not been set.
   */
  @Override public Calendar getMinDate() {
    return mMinDate;
  }

  /**
   * Sets the minimal date supported by this DatePicker. Dates after (but not including) the
   * specified date will be disallowed from being selected.
   *
   * @param calendar a Calendar object set to the year, month, day desired as the maxdate.
   */
  public void setMaxDate(Calendar calendar) {
    mMaxDate = calendar;

    if (mDayPickerView != null) {
      mDayPickerView.onChange();
    }
  }

  /**
   * @return The maximal date supported by this DatePicker. Null if it has not been set.
   */
  @Override public Calendar getMaxDate() {
    return mMaxDate;
  }

  public void setOnDateSetListener(OnDateSetListener listener) {
    mCallBack = listener;
  }

  // If the newly selected month / year does not contain the currently selected day number,
  // change the selected day number to the last day of the selected month or year.
  //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
  //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
  private void adjustDayInMonthIfNeeded(int month, int year) {
    int day = mCalendar.get(Calendar.DAY_OF_MONTH);
    int daysInMonth = Utils.getDaysInMonth(month, year);
    if (day > daysInMonth) {
      mCalendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
    }
  }

  @Override public void onClick(View v) {
    tryVibrate();
    if (v.getId() == R.id.date_picker_year) {
      setCurrentView(YEAR_VIEW);
    } else if (v.getId() == R.id.date_picker_month_and_day) {
      setCurrentView(MONTH_AND_DAY_VIEW);
    }
  }

  @Override public void onYearSelected(int year) {
    adjustDayInMonthIfNeeded(mCalendar.get(Calendar.MONTH), year);
    mCalendar.set(Calendar.YEAR, year);
    updatePickers();
    setCurrentView(MONTH_AND_DAY_VIEW);
    updateDisplay(true);
  }

  @Override public void onDayOfMonthSelected(int year, int month, int day) {
    mCalendar.set(Calendar.YEAR, year);
    mCalendar.set(Calendar.MONTH, month);
    mCalendar.set(Calendar.DAY_OF_MONTH, day);
    updatePickers();
    updateDisplay(true);
  }

  private void updatePickers() {
    Iterator<OnDateChangedListener> iterator = mListeners.iterator();
    while (iterator.hasNext()) {
      iterator.next().onDateChanged();
    }
  }

  @Override public CalendarDay getSelectedDay() {
    return new CalendarDay(mCalendar);
  }

  @Override public int getMinYear() {
    return mMinYear;
  }

  @Override public int getMaxYear() {
    return mMaxYear;
  }

  @Override public int getFirstDayOfWeek() {
    return mWeekStart;
  }

  @Override public void registerOnDateChangedListener(OnDateChangedListener listener) {
    mListeners.add(listener);
  }

  @Override public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
    mListeners.remove(listener);
  }

  @Override public void tryVibrate() {
    mHapticFeedbackController.tryVibrate();
  }
}