package com.conveyal.analysis.models;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * We could add defaultImpl = CustomModificationHolder.class, which might allow us to eliminate having separate type vs.
 * r5type properties in custom backend modifications. But we'd need to define a TypeResolver that would serialize
 * arbitrary type codes unknown to the backend.
 *
 * Created by matthewc on 2/9/16.
 */
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.PROPERTY, property="type")
@JsonSubTypes({
        @JsonSubTypes.Type(name = "add-trip-pattern", value = AddTripPattern.class),
        @JsonSubTypes.Type(name = "remove-trips", value = RemoveTrips.class),
        @JsonSubTypes.Type(name = "remove-stops", value = RemoveStops.class),
        @JsonSubTypes.Type(name = "adjust-speed", value = AdjustSpeed.class),
        @JsonSubTypes.Type(name = "adjust-dwell-time", value = AdjustDwellTime.class),
        @JsonSubTypes.Type(name = "convert-to-frequency", value = ConvertToFrequency.class),
        @JsonSubTypes.Type(name = "reroute", value = Reroute.class),
        @JsonSubTypes.Type(name = "custom", value = CustomModificationHolder.class),
        @JsonSubTypes.Type(name = "add-streets", value = AddStreets.class),
        @JsonSubTypes.Type(name = "modify-streets", value = ModifyStreets.class)
})
public abstract class Modification extends Model implements Cloneable {
    /** the type of this modification, see JsonSubTypes annotation above */
    public abstract String getType ();

    public Modification clone () throws CloneNotSupportedException {
        return (Modification) super.clone();
    }

    /** What project is this modification a part of? */
    public String projectId;

    /** what variants is this modification a part of? */
    public boolean[] variants;

    /** A description/comment about this modification */
    public String description;

    /**
     * Add scope to each ID in an array of IDs and return as an unordered Set. Used in converting internal
     * analysis-backend modification types to R5 modifications sent to the workers. See feedScopeId() for more details.
     *
     * This preserves null arrays coming in from MongoDB (in the internal analysis-backend modification types) because
     * in R5 modifications, the null set has a distinct meaning from an empty set (null matches everything, empty set
     * matches nothing). TODO We should probably disallow and fail on empty sets though, since they must be mistakes.
     */
    public static Set<String> feedScopedIdSet (String feed, String[] ids) {
        if (ids == null) {
            return null;
        } else {
            Set<String> feedScopedIdSet = new HashSet<>(ids.length);
            for (String id : ids) {
                feedScopedIdSet.add(feedScopeId(feed, id));
            }
            return feedScopedIdSet;
        }
    }

    /**
     * This is similar to feedScopedIdSet but it preserves order (by returning an array instead of a set) and it does
     * not tolerate nulls, because it is never directly used on top level fields of a modification where nulls have
     * meaning.
     */
    public static String[] feedScopedIdArray (String feed, String[] ids) {
        checkNotNull(ids);
        String[] scopedIds = new String[ids.length];
        for (int i = 0; i < ids.length; i++) {
            scopedIds[i] = feedScopeId(feed, ids[i]);
        }
        return scopedIds;
    }

    /**
     * Combine a feed ID and an entity ID into a single ID string that will be unique within an R5 TransportNetwork.
     * A modification can only refer to entities from one specific feed. UI/backend modifications have a "feed" field
     * with the ID of the feed uniquely identifying it within a Bundle. R5 (worker) modifications do not have this feed
     * field, so every individual entity reference has to be prepended with a feed scope.
     */
    public static String feedScopeId (String feed, String id) {
        return String.format("%s:%s", feed, id);
    }

    public abstract com.conveyal.r5.analyst.scenario.Modification toR5 ();
}

@@ -139,16 +139,55 @@
 ypeInfo;
+%0Aimport com.google.common.collect.Sets;
 %0A%0Aimport
@@ -2944,148 +2944,48 @@
     
-Set%3CString%3E feedScopedIdSet = 
+return Sets.
 new
- 
 HashSet
-%3C%3E(ids.length);%0A            for (String id : ids) %7B%0A                feedScopedIdSet.add(feedScopeId
+(feedScopedIdArray
 (fee
@@ -2993,62 +2993,13 @@
 , id
+s
 ));%0A
-            %7D%0A            return feedScopedIdSet;%0A
     

simplify method definition #687