package eu.reply.lea.services.yard.entity;

import jakarta.persistence.Embeddable;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;

@Embeddable
public class YardContentChangeTracker implements Serializable {

    private Instant locationChangedOn;
    private Instant movingChangedOn;

    public YardContentChangeTracker(Instant initializedOn) {
        Objects.requireNonNull(initializedOn, "Initialization date must be provided.");
        this.locationChangedOn = initializedOn;
        this.movingChangedOn = initializedOn;
    }

    protected YardContentChangeTracker() {
        // Default constructor for JPA
    }

    /**
     * Determines if the location can be changed at the given timestamp.
     * The location can be changed if neither the location nor the moving status
     * has been modified after the provided timestamp.
     *
     * @param asOfDate the timestamp to check against
     * @return true if the location can be changed, false otherwise
     */
    public boolean canChangeLocation(Instant asOfDate) {
        Objects.requireNonNull(asOfDate, "Comparison date must be provided.");
        return !locationChangedOn.isAfter(asOfDate) && !movingChangedOn.isAfter(asOfDate);
    }

    /**
     * Determines if the moving status can be changed at the given timestamp.
     * The moving status can be changed if neither the moving status nor the location
     * has been modified after the provided timestamp.
     *
     * @param asOfDate the timestamp to check against
     * @return true if the moving status can be changed, false otherwise
     */
    public boolean canChangeMoving(Instant asOfDate) {
        Objects.requireNonNull(asOfDate, "Comparison date must be provided.");
        return !movingChangedOn.isAfter(asOfDate) && !locationChangedOn.isAfter(asOfDate);
    }

    /**
     * Returns a new instance of YardContentChangeTracker with updated locationChangedOn timestamp.
     *
     * @param asOfDate the timestamp when the location was changed
     * @return a new YardContentChangeTracker instance with updated locationChangedOn
     */
    public YardContentChangeTracker locationChangedOn(Instant asOfDate) {
        Objects.requireNonNull(asOfDate, "Date must be provided.");
        return new YardContentChangeTracker(asOfDate, this.movingChangedOn);
    }

    /**
     * Returns a new instance of YardContentChangeTracker with updated movingChangedOn timestamp.
     *
     * @param asOfDate the timestamp when the moving status was changed
     * @return a new YardContentChangeTracker instance with updated movingChangedOn
     */
    public YardContentChangeTracker movingChangedOn(Instant asOfDate) {
        Objects.requireNonNull(asOfDate, "Date must be provided.");
        return new YardContentChangeTracker(this.locationChangedOn, asOfDate);
    }

    private YardContentChangeTracker(Instant locationChangedOn, Instant movingChangedOn) {
        this.locationChangedOn = locationChangedOn;
        this.movingChangedOn = movingChangedOn;
    }

    public Instant getLocationChangedOn() {
        return this.locationChangedOn;
    }

    public Instant getMovingChangedOn() {
        return this.movingChangedOn;
    }


    @Override
    public int hashCode() {
        return (8325 * 67)+ Objects.hash(locationChangedOn, movingChangedOn);
    }

    @Override
    public boolean equals(Object anObject) {
        boolean equalObjects = false;

        if (anObject != null && this.getClass() == anObject.getClass()) {
            YardContentChangeTracker typedObject = (YardContentChangeTracker) anObject;
            equalObjects =
                this.getLocationChangedOn().equals(typedObject.getLocationChangedOn())
                    && this.getMovingChangedOn().equals(typedObject.getLocationChangedOn());
        }

        return equalObjects;
    }


}
package eu.reply.lea.services.yard.entity;

import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.time.Instant;
import java.util.Objects;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import eu.reply.lea.services.yard.event.YardContentCompletedMovementEvent;
import eu.reply.lea.services.yard.event.YardContentOutEvent;
import eu.reply.lea.services.yard.event.YardContentStartedMovementEvent;
import eu.reply.lea.services.yard.util.ColSizes;
import eu.reply.lea.services.yard.util.ZeroTrimmer;
import eu.reply.sce.backend.core.entity.JsonEntityReference;
import eu.reply.sce.backend.core.entity.StringCustomizableEntity;
import eu.reply.sce.backend.core.event.ConfigurableEventProducer;

@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@NamedQueries({@NamedQuery(name = YardContent.Q_BY_ENTITY_REF, query = "select yc from YardContent yc where "
        + "yc.logisticUnit.module = :module "
        + "and yc.logisticUnit.entityName = :entityName "
        + "and yc.logisticUnit.entityId = :entityId")})
public class YardContent extends StringCustomizableEntity {

    public static final String Q_BY_ENTITY_REF = "YardContent.Q_BY_ENTITY_REF";

    public enum ContainerStatus {
        GOOD, DAMAGED;
    }

    public enum PriorityLevel {
        LOW, MEDIUM, HIGH;
    }


    @Column(length = ColSizes.CODE)
    private String code;

    @Column(length = ColSizes.CODE)
    private String visibleLabel;

    @AttributeOverrides({
        @AttributeOverride(column = @Column(name = "logUnit_businesskey", length = ColSizes.ER_BK), name = "businessKey"),
        @AttributeOverride(column = @Column(name = "logUnit_entityid", length = ColSizes.ER_ID), name = "entityId"),
        @AttributeOverride(column = @Column(name = "logUnit_entityname", length = ColSizes.ER_ENT), name = "entityName"),
        @AttributeOverride(column = @Column(name = "logUnit_module", length = ColSizes.ER_MOD), name = "module"),
        @AttributeOverride(column = @Column(name = "logUnit_description", length = ColSizes.ER_DESC), name = "description")})
    @Embedded
    private JsonEntityReference logisticUnit;

    @Column(length = ColSizes.CODE)
    private String siteCode;

    @Column(length = ColSizes.ENUM)
    @Enumerated(EnumType.STRING)
    private PriorityLevel priorityLevel;

    @Column(length = ColSizes.ENUM)
    @Enumerated(EnumType.STRING)
    private ContainerStatus containerStatus;

    private boolean emptyContainer;

    private boolean moving;

    // level for stacks
    @Min(value = 0)
    @Max(value = 99)
    private Integer level;

    /**
     * Number of containers in a transit
     */
    private Integer containerCount;

    /**
     * Activates look and feel with colored header on FE.
     */
    private boolean transitStyle;

    @Embedded
    private YardContentChangeTracker yardContentChangeTracker;

    @ManyToOne
    private YardLocation location;

    public YardContent(final JsonEntityReference logisticUnit, final Boolean isTransit, final Instant initializedOn) {
        this.code = logisticUnit.getBusinessKey();
        this.logisticUnit = logisticUnit;
        this.transitStyle = isTransit;
        this.containerCount = 0;
        this.moving = false;
        this.updateVisibleLabel();
        this.yardContentChangeTracker = new YardContentChangeTracker(initializedOn);
    }

    private void updateVisibleLabel() {
        String label = code != null ? code : (logisticUnit != null ? logisticUnit.getBusinessKey() : "");
        this.visibleLabel = ZeroTrimmer.trimLeadingZeros(label);
    }

    public void addLoadingUnit() {
        if (!this.transitStyle) {
            throw new IllegalStateException("It's only possible to add a loading unit to a transit");
        }
        this.containerCount++;
    }

    public void removeLoadingUnit() {
        if (!this.transitStyle) {
            throw new IllegalStateException("It's only possible to add a loading unit to a transit");
        }
        this.containerCount = this.containerCount == 0 ? 0 : this.containerCount - 1;
    }

    private void updateLocation(final YardLocation location) {
        Objects.requireNonNull(location);
        if (this.location != null) {
            this.location.removeContent(this);
        }
        location.addContent(this);
    }

    public void move(final YardLocation location, final ConfigurableEventProducer<YardContentCompletedMovementEvent> eventProducer) {
        updateLocation(location);
        completeMovement(eventProducer);
    }

    public void completeMovement(final ConfigurableEventProducer<YardContentCompletedMovementEvent> eventProducer) {
        this.moving = false;
        eventProducer.fire(new YardContentCompletedMovementEvent(this));

    }

    public void startMovement(final YardLocation location, final ConfigurableEventProducer<YardContentStartedMovementEvent> eventProducer) {
        updateLocation(location);
        this.moving = true;
        eventProducer.fire(new YardContentStartedMovementEvent(this));
    }

    public void out(final ConfigurableEventProducer<YardContentOutEvent> eventProducer) {
        if (this.getLocation() != null) {
            YardLocation lastLocation = this.getLocation();
            this.location.removeContent(this);
            eventProducer.fire(new YardContentOutEvent(this, lastLocation));
        }
    }
}

can you use the yardcontentchangetracker in yard content?