package com.capgemini.framework.teamcenter.object.pom.status;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.object.types.SnecmaObjectTypes;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import com.teamcenter.soa.client.model.strong.ReleaseStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Enumeration of status. Use {@link StatusEnum#toString()} for the user-friendly name, and {@link StatusEnum#getIdentifier()} for the actual Tc code name.
 */
@Slf4j
@Getter
@AllArgsConstructor
public enum StatusEnum {
    AD_CANCELLED("AD Cancelled"),
    AD("AD"),
    J0("J0"),
    J1("J1"),
    J2("J2"),
    J3A("J3A"),
    J3B("J3B"),
    STUDY_CLOSED("Study closed"),
    STUDY_FINISHED("PV4_STU_FINISHED"),
    STUDY_FINISHED_A("PV4_STU_FINISHED_A"),
    PRELIM_DEF_READY("Prelim Def Ready"),
    DEF_READY("Def Ready"),
    PRELIM_DEF_APPROVED("Prelim Def Approved"),
    DEF_APPROVED("Def Approved"),
    DEF_FINISHED("PV4_DEF_FINISHED"),
    PUBLISHED("Published"),
    FROZEN_IN_PROCESS("Frozen in process"),
    FROZEN("Frozen"),
    CREATED("Created"),
    PRELIM_CREATED("Prelim Created"),
    OPENING_IN_PROCESS("Opening in process"),
    OPEN("Open"),
    CANCELLED("Cancelled"),
    FINISHED("Finished"),
    PB_REPORT_APPROVED("Pb Report Approved"),
    SB_DISTRIBUTED("SB distributed"),

    INTERFACE_TODO("Interface TODO"),
    DMU_KO_PART_R("DMU KO PART R"),
    DMU_KO_PART_S("DMU KO PART S"),
    DMU_KO_PART_P("DMU KO PART P"),
    DMU_KO_PART_K("DMU KO PART K"),
    DMU_KO_PART_M("DMU KO PART M"),
    DMU_KO_PART_I("DMU KO PART I"),
    DMU_KO_PART_C("DMU KO PART C"),
    DMU_OK("DMU OK"),
    DMU_READY("DMU Ready"),

    EFFECTIVITY(SnecmaObjectTypes.EFFECTIVITY),
    SET_EFFECTIVITY("Set_Effectivity"),
    OLD_EFFECTIVITY("Old effectivity"),
    UNKNOWN_STATUS("Unknown");

    /**
     * Filter that keeps only DMU statuses.
     */
    public static final AbstractStatusFilter ONLY_DMU =
            new StatusInclusionFilter(DMU_KO_PART_C, DMU_KO_PART_I, DMU_KO_PART_K, DMU_KO_PART_M, DMU_KO_PART_P, DMU_KO_PART_R, DMU_KO_PART_S);
    /**
     * Filter that keeps only TcEffectivity statuses.
     */
    public static final AbstractStatusFilter ONLY_EFFECTIVITIES = new StatusInclusionFilter(EFFECTIVITY, SET_EFFECTIVITY, OLD_EFFECTIVITY);
    /**
     * Filter that keeps Change Maturity statuses.
     */
    public static final AbstractStatusFilter CHANGE_MATURITY = new StatusInclusionFilter(AD, AD_CANCELLED, CANCELLED, CREATED, DEF_APPROVED,
            DEF_READY, DEF_FINISHED, FINISHED, J0, J1, J2, J3A, J3B, PB_REPORT_APPROVED, PRELIM_CREATED, PRELIM_DEF_APPROVED, PRELIM_DEF_READY,
            SB_DISTRIBUTED, STUDY_CLOSED, STUDY_FINISHED, STUDY_FINISHED_A);
    /**
     * Filter that keeps Unit Maturity statuses.
     */
    public static final AbstractStatusFilter UNIT_MATURITY =
            new StatusInclusionFilter(OPENING_IN_PROCESS, OPEN, J3A, J3B, FROZEN_IN_PROCESS, FROZEN, CANCELLED);
    private final String identifier;

    /**
     * Give the appropriate {@link StatusEnum} associate to the string value.<br/>
     * Else return null.
     *
     * @param value String - the string value
     * @return the appropriate {@link StatusEnum}. Return {@link StatusEnum#UNKNOWN_STATUS} if the string value doesn't exist.
     */
    public static StatusEnum getStatusEnum(final String value) {
        for (StatusEnum status : StatusEnum.values()) {
            if (status.identifier.equals(value)) {
                return status;
            }
        }
        log.warn("Unsupported status found. The string {} is not valid.", value);
        return UNKNOWN_STATUS;
    }

    /**
     * Creates a {@link AbstractStatusFilter} object which will only allow the provided StatusEnum.
     *
     * @param authorized the authorized {@link StatusEnum}s.
     * @return the {@link AbstractStatusFilter}
     */
    public static AbstractStatusFilter allow(final StatusEnum... authorized) {
        return new StatusInclusionFilter(authorized);
    }

    /**
     * Creates a {@link AbstractStatusFilter} object which will only not allow the provided StatusEnum.
     *
     * @param forbidden the forbidden {@link StatusEnum}s.
     * @return the {@link AbstractStatusFilter}
     */
    public static AbstractStatusFilter forbid(final StatusEnum... forbidden) {
        return new StatusExclusionFilter(forbidden);
    }

    /**
     * Definition of a filter that may allow or not a StatusEnum based on its (String) name, or on its StatusEnum instance.
     */
    public abstract static class AbstractStatusFilter {

        /**
         * Returns true if the provided status should be excluded.
         *
         * @param status the status to evaluate
         * @return true if it should be excluded, false otherwise
         */
        public abstract boolean isExcluded(final StatusEnum status);

        /**
         * Returns true if the provided status should be included.
         *
         * @param status the status to evaluate
         * @return true if it should be included, false otherwise
         */
        public abstract boolean isIncluded(final StatusEnum status);

        /**
         * Returns true if the provided status name should be excluded.
         *
         * @param status the status to evaluate
         * @return true if it should be excluded, false otherwise.
         */
        public boolean isExcluded(final TcReleaseStatus status) {
            StatusEnum statusEnum = status.getStatusEnum();
            return isExcluded(statusEnum);
        }

        /**
         * Returns true if the provided status name should be included.
         *
         * @param status the status to evaluate
         * @return true if it should be included, false otherwise.
         */
        public boolean isIncluded(final TcReleaseStatus status) {
            StatusEnum statusEnum = status.getStatusEnum();
            return isIncluded(statusEnum);
        }

        /**
         * Returns true if the provided status name should be excluded.
         *
         * @param status the name of the status to evaluate
         * @return true if it should be excluded, false otherwise.
         */
        public boolean isExcluded(final String status) {
            StatusEnum statusEnum = getStatusEnum(status);
            return isExcluded(statusEnum);
        }

        /**
         * Returns true if the provided status name should be included.
         *
         * @param status the name of the status to evaluate
         * @return true if it should be included, false otherwise.
         */
        public boolean isIncluded(final String status) {
            StatusEnum statusEnum = getStatusEnum(status);
            return isIncluded(statusEnum);
        }

        /**
         * Returns true if the provided {@link ReleaseStatus} name should be excluded.
         *
         * @param status the status to evaluate
         * @return true if it should be excluded, false otherwise.
         */
        public boolean isExcluded(final ReleaseStatus status) {
            StatusEnum statusEnum = getStatusEnum(status.get_name());
            return isExcluded(statusEnum);
        }

        /**
         * Returns true if the provided {@link ReleaseStatus} name should be included.
         *
         * @param status the status to evaluate
         * @return true if it should be included, false otherwise.
         */
        public boolean isIncluded(final ReleaseStatus status) {
            StatusEnum statusEnum = getStatusEnum(status.get_name());
            return isIncluded(statusEnum);
        }

        /**
         * Filter the statuses of the provided {@link ItemRevision}.
         *
         * @param curItemRevision the item carrying some statuses.
         * @return a {@link List} of {@link ReleaseStatus} that comply with this filter.
         */
        public List<ReleaseStatus> filterStatusOf(final TcSession session, final ItemRevision curItemRevision) {
            List<ReleaseStatus> selected = new ArrayList<>();
            session.loadProperties(curItemRevision, "release_status_list");
            ReleaseStatus[] releaseStatuses = curItemRevision.get_release_status_list();
            session.loadProperties(releaseStatuses);
            for (ReleaseStatus status : releaseStatuses) {
                String statusName = status.get_name();
                if (isIncluded(statusName)) {
                    selected.add(status);
                }
            }
            return selected;
        }
    }

    static class StatusInclusionFilter extends AbstractStatusFilter {

        private final StatusEnum[] authorized;

        protected StatusInclusionFilter(final StatusEnum... authorized) {
            super();
            this.authorized = authorized;
            Arrays.sort(authorized);
        }

        @Override
        public boolean isExcluded(final StatusEnum status) {
            return !isIncluded(status);
        }

        @Override
        public boolean isIncluded(final StatusEnum status) {
            return Arrays.binarySearch(authorized, status) >= 0;
        }
    }

    private static class StatusExclusionFilter extends AbstractStatusFilter {

        private final StatusEnum[] forbidden;

        protected StatusExclusionFilter(final StatusEnum... forbidden) {
            super();
            this.forbidden = forbidden;
            Arrays.sort(forbidden);
        }

        @Override
        public boolean isExcluded(final StatusEnum type) {
            return Arrays.binarySearch(forbidden, type) >= 0;
        }

        @Override
        public boolean isIncluded(final StatusEnum type) {
            return !isExcluded(type);
        }
    }
}
