content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you 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 org.apache.causeway.applib.value.semantics;
  
  import java.time.Duration;
  import java.time.temporal.Temporal;
  
  import org.apache.causeway.applib.annotation.TimePrecision;
  import org.apache.causeway.commons.internal.exceptions._Exceptions;
  
  import lombok.Data;
  import org.jspecify.annotations.NonNull;
  
  /**
   * Common base for {@link java.time.temporal.Temporal} value types.
   *
   * @since 2.0
   *
   * @param <T> implementing {@link java.time.temporal.Temporal} type
   */
  public interface TemporalValueSemantics<T extends Temporal>
  extends
      OrderRelation<T, Duration>,
      Parser<T>,
      Renderer<T>,
      TemporalSupport<T> {
  
      static enum EditingFormatDirection {
  
          /**
           * Input parsable text.
           */
          INPUT,
  
          /**
           * Output parsable text.
           */
          OUTPUT;
  
          public boolean isInput() {return this == INPUT;}
          public boolean isOutput() {return this == OUTPUT;}
      }
  
      @Data
      public static class TemporalDisplayPattern {
  
          /**
           * The locale-independent (canonical) pattern used for displaying dates in the UI.
           *
           * <p>
           *     By default, a locale-specific date pattern is used (using {@link java.time.format.FormatStyle#MEDIUM} format).
           * </p>
           */
          private String datePattern;
  
          /**
           * The locale-independent (canonical) pattern used for displaying date/times in the UI.
           *
           * <p>
           *     By default, a locale-specific date/time pattern is used (using {@link java.time.format.FormatStyle#MEDIUM} format) for both the date and time parts.
           * </p>
           */
          private String dateTimePattern;
      }
  
      @Data
      public static class TemporalEditingPattern {
  
          /**
           * The locale-independent (canonical) pattern used for editing dates in the UI.
           */
          @NonNull //@NotEmpty
          private String datePattern = "yyyy-MM-dd";
  
          // -- TIME PATTERNS - SECOND
  
          /**
           * The locale-independent (canonical) input pattern used for editing time in the UI.
           * Yielding {@link TimeFormatPrecision#NANO_SECOND}.
           * <p>
           * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
           *
           * @apiNote Supports various input forms as denoted by optional blocks (square brackets).
           * The output format is inferred by removal of the square brackets (not their content).
           */
          @NonNull //@NotEmpty
          private String timePatternNanoSecond = "HH[:mm[:ss][.SSSSSSSSS]]";
  
          /**
           * The locale-independent (canonical) input pattern used for editing time in the UI.
           * Yielding {@link TimeFormatPrecision#MICRO_SECOND}.
           * <p>
           * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
           *
           * @apiNote Supports various input forms as denoted by optional blocks (square brackets).
           * The output format is inferred by removal of the square brackets (not their content).
           */
          @NonNull //@NotEmpty
          private String timePatternMicroSecond = "HH[:mm[:ss][.SSSSSS]]";
  
          /**
           * The locale-independent (canonical) input pattern used for editing time in the UI.
           * Yielding {@link TimeFormatPrecision#MILLI_SECOND}.
           * <p>
           * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
           *
           * @apiNote Supports various input forms as denoted by optional blocks (square brackets).
           * The output format is inferred by removal of the square brackets (not their content).
           */
          @NonNull //@NotEmpty
          private String timePatternMilliSecond = "HH[:mm[:ss][.SSS]]";
  
          /**
           * The locale-independent (canonical) input pattern used for editing time in the UI.
           * Yielding {@link TimeFormatPrecision#SECOND}.
           * <p>
           * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
           *
           * @apiNote Supports various input forms as denoted by optional blocks (square brackets).
           * The output format is inferred by removal of the square brackets (not their content).
           */
          @NonNull //@NotEmpty
          private String timePatternSecond = "HH[:mm[:ss]]";
  
          /**
           * The locale-independent (canonical) input pattern used for editing time in the UI.
           * Yielding {@link TimeFormatPrecision#MINUTE}.
           * <p>
           * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
           *
           * @apiNote Supports various input forms as denoted by optional blocks (square brackets).
           * The output format is inferred by removal of the square brackets (not their content).
           */
          @NonNull //@NotEmpty
          private String timePatternMinute = "HH[:mm]";
  
          /**
           * The locale-independent (canonical) input pattern used for editing time in the UI.
           * Yielding {@link TimeFormatPrecision#HOUR}.
           * <p>
           * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
           *
           * @apiNote Supports various input forms as denoted by optional blocks (square brackets).
           * The output format is inferred by removal of the square brackets (not their content).
           */
          @NonNull //@NotEmpty
          private String timePatternHour = "HH";
  
          // -- ZONE PATTERN
  
          /**
           * The locale-independent (canonical) pattern used for editing time-zone in the UI.
           * <p>
           * Java time-zone formats<pre>
           * V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
           * z       time-zone name              zone-name         Pacific Standard Time; PST
           * O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
           * X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
           * x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
           * Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
           *</pre>
           *
           * TODO no <i>tempus-dominus</i> date/time-picker support yet.
           */
          @NonNull //@NotEmpty
          private String zoneIdPatternForOutput = "VV";
  
          /**
           * TODO no <i>tempus-dominus</i> date/time-picker support yet.
           */
          @NonNull //@NotEmpty
          private String zoneIdPatternForInput = "VV";
  
          /**
           * The locale-independent (canonical) pattern used for editing time-offset in the UI.
           * <p>
           * Java time-zone formats<pre>
           * V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
           * z       time-zone name              zone-name         Pacific Standard Time; PST
           * O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
           * X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
           * x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
           * Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
           *</pre>
           *
           * @apiNote Yet only tested with {@literal XXX}, as there needs to be a format correspondence with
           * <i>momentJs</i> for the <i>tempus-dominus</i> date/time-picker to work
           * (as used by the <i>Wicket Viewer</i>).
           * {@link org.apache.causeway.viewer.wicket.ui.components.scalars.datepicker._TimeFormatUtil}
           * does the format conversion.
           */
          @NonNull //@NotEmpty
          private String offsetPatternForOutput = "XXX";
  
          /**
           * Support both forms for parsing, with or without colon.
           * <p>
           * (Order of optional blocks matter, eg. {@literal [X][XXX]} would not work.)
           * @see "https://stackoverflow.com/questions/34637626/java-datetimeformatter-for-time-zone-with-an-optional-colon-separator"
           */
          @NonNull //@NotEmpty
          private String offsetPatternForInput = "[XXX][X]";
  
          // -- JOINING PATTERNS
  
          /**
           * The locale-independent (canonical) pattern used for editing date and time in the UI.
           * <p>
           * Uses {@code String.format(dateTimeJoiningPattern, datePattern, timePattern)}
           * to interpolate the effective date-time format.
           * @see String#format(String, Object...)
           */
          @NonNull //@NotEmpty
          private String dateTimeJoiningPattern = "%1$s %2$s";
  
          /**
           * The locale-independent (canonical) pattern used for editing zoned temporals
           * (date, time or date-time) in the UI.
           * <p>
           * Uses {@code String.format(zoneJoiningPattern, temporalPattern, zonePattern)}
           * to interpolate the effective zoned temporal format.
           * @see String#format(String, Object...)
           */
          @NonNull //@NotEmpty
          private String zoneJoiningPattern = "%1$s %2$s";
  
          public String getEditingFormatAsPattern(
                  final @NonNull TemporalCharacteristic temporalCharacteristic,
                  final @NonNull OffsetCharacteristic offsetCharacteristic,
                  final @NonNull TimePrecision timePrecision,
                  final @NonNull EditingFormatDirection direction) {
  
              switch (temporalCharacteristic) {
              case DATE_TIME:
                  var dateTimePattern =
                      String.format(getDateTimeJoiningPattern(),
                              getDatePattern(),
                              timePattern(timePrecision, direction));
                  return offsetCharacteristic.isLocal()
                          ? dateTimePattern
                          : String.format(getZoneJoiningPattern(),
                                  dateTimePattern,
                                  zonePattern(offsetCharacteristic, direction));
              case DATE_ONLY:
                  return offsetCharacteristic.isLocal()
                          ? getDatePattern()
                          : String.format(getZoneJoiningPattern(),
                                  getDatePattern(),
                                  zonePattern(offsetCharacteristic,direction));
              case TIME_ONLY:
                  return offsetCharacteristic.isLocal()
                          ? timePattern(timePrecision, direction)
                          : String.format(getZoneJoiningPattern(),
                                  timePattern(timePrecision, direction),
                                  zonePattern(offsetCharacteristic, direction));
              default:
                  throw _Exceptions.unmatchedCase(temporalCharacteristic);
              }
          }
  
          private String zonePattern(
                  final @NonNull OffsetCharacteristic offsetCharacteristic,
                  final @NonNull EditingFormatDirection direction) {
  
              switch(offsetCharacteristic) {
              case OFFSET:
                  return direction.isInput()
                          ? getOffsetPatternForInput()
                          : getOffsetPatternForOutput();
              case ZONED:
                  return direction.isInput()
                          ? getZoneIdPatternForInput()
                          : getZoneIdPatternForOutput();
              default:
                  throw _Exceptions.unexpectedCodeReach();
              }
          }
  
          // -- HELPER
  
          private String timePattern(
                  final @NonNull TimePrecision timePrecision,
                  final @NonNull EditingFormatDirection direction) {
              switch (direction) {
              case INPUT:
                  return timePattern(timePrecision);
              case OUTPUT:
                  return timePattern(timePrecision)
                          .replace("[", "").replace("]", ""); // remove brackets for optional temporal parts
              }
              throw _Exceptions.unmatchedCase(direction);
          }
  
          private String timePattern(final @NonNull TimePrecision timePrecision) {
              switch (timePrecision) {
              case NANO_SECOND:
                  return getTimePatternNanoSecond();
              case MICRO_SECOND:
                  return getTimePatternMicroSecond();
              case MILLI_SECOND:
                  return getTimePatternMilliSecond();
              case UNSPECIFIED:
              case SECOND:
                  return getTimePatternSecond();
              case MINUTE:
                  return getTimePatternMinute();
              case HOUR:
                  return getTimePatternHour();
              }
              throw _Exceptions.unmatchedCase(timePrecision);
          }
  
      }
  
  }
  