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));
        }
    }
}
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;

    public boolean canChangeLocation(final Instant asOfDate) {
        return this.getLocationChangedOn().isBefore(asOfDate);
    }

    public boolean canChangeMoving(final Instant asOfDate) {
        return this.getLocationChangedOn().isBefore(asOfDate);
    }

    public YardContentChangeTracker locationChangedOn(final Instant asOfDate) {
        return new YardContentChangeTracker(asOfDate);
    }

    public YardContentChangeTracker movingChangedOn(final Instant asOfDate) {
        return new YardContentChangeTracker(asOfDate);
    }


    @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());
        }

        return equalObjects;
    }

    @Override
    public int hashCode() {
        int hashCodeValue =
            +(8705 * 67)
                + this.getLocationChangedOn().hashCode();

        return hashCodeValue;
    }

    protected YardContentChangeTracker(
        Instant anEmailAddressChangedOn) {

        this();

        this.setLocationChangedOn(anEmailAddressChangedOn);
    }

    protected YardContentChangeTracker() {
        super();
    }

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

    private void setLocationChangedOn(final Instant locationChangedOn) {
        Objects.requireNonNull(locationChangedOn, "Location changed on date must be provided.");

        this.locationChangedOn = locationChangedOn;
    }

}
can you complete and improve the implementation of the tracker