/*
 * Copyright 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.google.android.material.datepicker;

import com.google.android.material.R;

import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.internal.TextWatcherAdapter;
import com.google.android.material.textfield.TextInputLayout;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

abstract class DateFormatTextWatcher extends TextWatcherAdapter {

  private static final int VALIDATION_DELAY = 1000;

  @NonNull private final TextInputLayout textInputLayout;

  private final DateFormat dateFormat;
  private final CalendarConstraints constraints;
  private final String outOfRange;
  private final Runnable setErrorCallback;

  private Runnable setRangeErrorCallback;

  DateFormatTextWatcher(
      final String formatHint,
      DateFormat dateFormat,
      @NonNull TextInputLayout textInputLayout,
      CalendarConstraints constraints) {

    this.dateFormat = dateFormat;
    this.textInputLayout = textInputLayout;
    this.constraints = constraints;
    this.outOfRange = textInputLayout.getContext().getString(R.string.mtrl_picker_out_of_range);
    setErrorCallback =
        new Runnable() {
          @Override
          public void run() {
            TextInputLayout textLayout = DateFormatTextWatcher.this.textInputLayout;
            DateFormat df = DateFormatTextWatcher.this.dateFormat;
            Context context = textLayout.getContext();
            String invalidFormat = context.getString(R.string.mtrl_picker_invalid_format);
            String useLine =
                String.format(
                    context.getString(R.string.mtrl_picker_invalid_format_use), formatHint);
            String exampleLine =
                String.format(
                    context.getString(R.string.mtrl_picker_invalid_format_example),
                    df.format(new Date(UtcDates.getTodayCalendar().getTimeInMillis())));
            textLayout.setError(invalidFormat + "\n" + useLine + "\n" + exampleLine);
            onInvalidDate();
          }
        };
  }

  abstract void onValidDate(@Nullable Long day);

  void onInvalidDate() {}

  @Override
  public void onTextChanged(@NonNull CharSequence s, int start, int before, int count) {
    textInputLayout.removeCallbacks(setErrorCallback);
    textInputLayout.removeCallbacks(setRangeErrorCallback);
    textInputLayout.setError(null);
    onValidDate(null);
    if (TextUtils.isEmpty(s)) {
      return;
    }

    try {
      Date date = dateFormat.parse(s.toString());
      textInputLayout.setError(null);
      final long milliseconds = date.getTime();
      if (constraints.getDateValidator().isValid(milliseconds)
          && constraints.isWithinBounds(milliseconds)) {
        onValidDate(date.getTime());
        return;
      }

      setRangeErrorCallback = createRangeErrorCallback(milliseconds);
      runValidation(textInputLayout, setRangeErrorCallback);
    } catch (ParseException e) {
      runValidation(textInputLayout, setErrorCallback);
    }
  }

  private Runnable createRangeErrorCallback(final long milliseconds) {
    return new Runnable() {
      @Override
      public void run() {
        textInputLayout.setError(
            String.format(outOfRange, DateStrings.getDateString(milliseconds)));
        onInvalidDate();
      }
    };
  }

  public void runValidation(View view, Runnable validation) {
    view.postDelayed(validation, VALIDATION_DELAY);
  }
}

@@ -1880,77 +1880,16 @@
     
-new Runnable() %7B%0A          @Override%0A          public void run()
+() -%3E
  %7B%0A
-  
     
@@ -1967,18 +1967,16 @@
 Layout;%0A
-  
         
@@ -2042,18 +2042,16 @@
         
-  
 Context 
@@ -2081,26 +2081,24 @@
 tContext();%0A
-  
           St
@@ -2170,26 +2170,24 @@
 id_format);%0A
-  
           St
@@ -2207,34 +2207,32 @@
 =%0A              
-  
 String.format(%0A 
@@ -2222,34 +2222,32 @@
  String.format(%0A
-  
                 
@@ -2307,17 +2307,54 @@
 at_use),
- 
+%0A                  sanitizeDateString(
 formatHi
@@ -2356,20 +2356,19 @@
 matHint)
+)
 ;%0A
-  
         
@@ -2396,34 +2396,32 @@
 =%0A              
-  
 String.format(%0A 
@@ -2411,34 +2411,32 @@
  String.format(%0A
-  
                 
@@ -2497,24 +2497,64 @@
 t_example),%0A
+                  sanitizeDateString(%0A  
             
@@ -2628,20 +2628,19 @@
 lis())))
+)
 ;%0A
-  
         
@@ -2721,26 +2721,24 @@
 ;%0A          
-  
 onInvalidDat
@@ -2746,20 +2746,8 @@
 ();%0A
-          %7D%0A
     
@@ -3808,69 +3808,83 @@
 urn 
-new Runnable() %7B%0A      @Override%0A      public void run() %7B%0A  
+() -%3E %7B%0A      String dateString = DateStrings.getDateString(milliseconds);%0A
     
@@ -3910,29 +3910,16 @@
 etError(
-%0A            
 String.f
@@ -3936,16 +3936,24 @@
 fRange, 
+sanitize
 DateStri
@@ -3946,38 +3946,34 @@
 nitizeDateString
-s.getD
+(d
 ateString(millis
@@ -3969,28 +3969,13 @@
 ring
-(milliseconds
 )));%0A
-  
     
@@ -4001,17 +4001,212 @@
     
+%7D;%0A
   %7D%0A
+%0A
   
-  %7D
+private String sanitizeDateString(String dateString) %7B%0A    // Replace all regular spaces with non-breaking spaces so the date wraps as a single unit.%0A    return dateString.replace(' ', '%5Cu00A0')
 ;%0A  

Improve error formatting