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.core.metamodel.services.objectlifecycle;
  
  import java.sql.Timestamp;
  import java.util.Comparator;
  
  import org.apache.causeway.applib.services.bookmark.Bookmark;
  import org.apache.causeway.applib.services.publishing.spi.EntityPropertyChange;
  import org.apache.causeway.applib.services.xactn.TransactionId;
  import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
  import org.apache.causeway.core.metamodel.object.ManagedObject;
  import org.apache.causeway.core.metamodel.object.MmUnwrapUtils;
  import org.apache.causeway.core.metamodel.services.deadlock.DeadlockRecognizer;
  import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
  
  import lombok.EqualsAndHashCode;
  import lombok.Getter;
  import org.jspecify.annotations.NonNull;
  import lombok.ToString;
  
  @EqualsAndHashCode(of = {"id"})
  @ToString(of = {"id"})
  public final class PropertyChangeRecord implements Comparable<PropertyChangeRecord> {
  
      @Getter private final PropertyChangeRecordId id;
      @Getter private PreAndPostValue preAndPostValue;
  
      public ManagedObject getEntity() {return id.getEntity();}
      public OneToOneAssociation getProperty() {return id.getProperty();}
      public Bookmark getBookmark() {return id.getBookmark();}
      public String getPropertyId() {return id.getPropertyId();}
  
      public static PropertyChangeRecord ofNew(
              final @NonNull PropertyChangeRecordId pcrId) {
          return new PropertyChangeRecord(pcrId)
                          .withPreValueSetToNew();
      }
  
      public static PropertyChangeRecord ofCurrent(
              final @NonNull PropertyChangeRecordId pcrId,
              final DeadlockRecognizer deadlockRecognizer) {
          return new PropertyChangeRecord(pcrId)
                          .withPreValueSetToCurrentElseUnknown(deadlockRecognizer);
      }
  
      public static PropertyChangeRecord ofCurrent(
              final @NonNull PropertyChangeRecordId pcrId,
              final Object currentValue) {
          return new PropertyChangeRecord(pcrId)
                          .withPreValueSetTo(currentValue);
      }
  
      public static PropertyChangeRecord ofDeleting(
              final @NonNull PropertyChangeRecordId id,
              final DeadlockRecognizer deadlockRecognizer) {
          return new PropertyChangeRecord(id)
                  .withPreValueSetToCurrentElseUnknown(deadlockRecognizer)
                  .withPostValueSetToDeleted();
      }
  
      private PropertyChangeRecord(final @NonNull PropertyChangeRecordId id) {
          this.id = id;
      }
  
      public String getLogicalMemberIdentifier() {
          var target = getBookmark();
          var propertyId = getPropertyId();
          return target.logicalTypeName() + "#" + propertyId;
      }
  
      public PropertyChangeRecord withPreValueSetToCurrentElseUnknown(DeadlockRecognizer deadlockRecognizer) {
          try {
              return withPreValueSetToCurrent();
          } catch (Exception ex) {
              deadlockRecognizer.rethrowIfDeadlock(ex);
              return withPreValueSetToUnknown();
          }
      }
  
      private PropertyChangeRecord withPreValueSetToCurrent() {
          return withPreValueSetTo(getPropertyValue());
      }
  
      private PropertyChangeRecord withPreValueSetToUnknown() {
          return withPreValueSetTo(PropertyValuePlaceholder.UNKNOWN);
      }
  
      private PropertyChangeRecord withPreValueSetToNew() {
          return withPreValueSetTo(PropertyValuePlaceholder.NEW);
      }
  
      private PropertyChangeRecord withPreValueSetTo(Object preValue) {
          this.preAndPostValue = PreAndPostValue.pre(preValue);
          return this;
      }
  
      public PropertyChangeRecord withPostValueSetToCurrentElseUnknown(DeadlockRecognizer deadlockRecognizer) {
          try {
              return withPostValueSetToCurrent();
          } catch (Exception ex) {
              deadlockRecognizer.rethrowIfDeadlock(ex);
              return withPostValueSetToUnknown();
          }
      }
  
      public PropertyChangeRecord withPostValueSetToDeleted() {
          return withPostValueSetTo(PropertyValuePlaceholder.DELETED);
      }
  
      public PropertyChangeRecord withPostValueSetToCurrent() {
          return withPostValueSetTo(getPropertyValue());
      }
  
      public PropertyChangeRecord withPostValueSetToUnknown() {
          return withPostValueSetTo(PropertyValuePlaceholder.UNKNOWN);
      }
  
      private PropertyChangeRecord withPostValueSetTo(Object postValue) {
          this.preAndPostValue = preAndPostValue.withPost(postValue);
          return this;
      }
  
      // -- UTILITY
  
      public EntityPropertyChange toEntityPropertyChange(
              final Timestamp timestamp,
              final String username,
              final TransactionId txId) {
  
          var target = getBookmark();
          var propertyId = getPropertyId();
          var preValue = getPreAndPostValue().getPreString();
          var postValue = getPreAndPostValue().getPostString();
          var interactionId = txId.getInteractionId();
          var sequence = txId.getSequence();
  
          String logicalMemberId = getLogicalMemberIdentifier();
          return EntityPropertyChange.of(
                  interactionId, sequence,
                  target, logicalMemberId, propertyId,
                  preValue, postValue,
                  username, timestamp);
      }
  
      // -- HELPER
  
      private Object getPropertyValue() {
          var referencedAdapter = getProperty().get(getEntity(), InteractionInitiatedBy.PASS_THROUGH);
          return MmUnwrapUtils.single(referencedAdapter);
      }
  
      @Override
      public int compareTo(PropertyChangeRecord o) {
          return Comparator
                  .comparing(PropertyChangeRecord::getId)
                  .compare(this, o);
      }
  }
  