/*
 * Copyright 2003-2014 JetBrains s.r.o.
 *
 * Licensed 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 jetbrains.mps.generator.impl.plan;

import jetbrains.mps.generator.impl.MapCfgGroups;
import jetbrains.mps.generator.impl.MapCfgGroups.ByModule;
import jetbrains.mps.generator.runtime.TemplateMappingConfiguration;
import jetbrains.mps.util.CollectionUtil;
import jetbrains.mps.util.NameUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.annotations.Immutable;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Collection of TemplateMappingConfigurations as a unit of generation plan.
* @author Artem Tikhomirov
*/
@Immutable
final class Group {
  private final Set<TemplateMappingConfiguration> myMappings;
  private final boolean myIsTopPriority;
  private final int myHash;

  private Group(Set<TemplateMappingConfiguration> mappings, boolean topPri) {
    myMappings = mappings;
    myIsTopPriority = topPri;
    myHash = myMappings.hashCode();
  }

  public Group() {
    this(Collections.<TemplateMappingConfiguration>emptySet(), false);
  }

  public Group(@NotNull TemplateMappingConfiguration cfg) {
    this(Collections.singleton(cfg), cfg.isTopPriority());
  }

  public Group(Iterable<Group> other) {
    HashSet<TemplateMappingConfiguration> mappings = new HashSet<TemplateMappingConfiguration>();
    HashMap<Boolean, Group> topPri = new HashMap<Boolean, Group>();
    for (Group g : other) {
      mappings.addAll(g.myMappings);
      topPri.put(g.isTopPriority(), g);
    }
    if (topPri.size() != 1) {
      throw new IllegalArgumentException(String.format("Can't create a group from a set of groups with different 'top priority' setting: %s", other));
    }
    myMappings = mappings;
    myIsTopPriority = topPri.keySet().iterator().next();
    myHash = myMappings.hashCode();
  }

  public Group subtract(Group other) {
    final HashSet<TemplateMappingConfiguration> mc = new HashSet<TemplateMappingConfiguration>(myMappings);
    mc.removeAll(other.myMappings);
    return new Group(mc, myIsTopPriority);
  }

  public Group union(Group other) {
    return new Group(Arrays.asList(this, other));
  }

  public Collection<TemplateMappingConfiguration> getElements() {
    return myMappings;
  }

  public boolean isEmpty() {
    return myMappings.isEmpty();
  }

  public boolean isTopPriority() {
    return myIsTopPriority;
  }

  @Override
  public int hashCode() {
    return myHash;
  }

  @Override
  public boolean equals(Object other) {
    if (other == this) {
      return true;
    }
    if (false == other instanceof Group) {
      return false;
    }
    return myMappings.equals(((Group) other).myMappings);
  }

  public boolean hasCommonMappings(Group other) {
    return CollectionUtil.intersects(myMappings, other.myMappings);
  }

  public boolean includes(Group other) {
    if (other.myMappings.size() > myMappings.size()) {
      return false;
    }
    if (other.isEmpty()) {
      return isEmpty();
    }
    return myMappings.containsAll(other.myMappings);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    if (isTopPriority()) {
      sb.append("Top");
    }
    if (isEmpty()) {
      sb.append("Empty");
    }
    sb.append("Group[");
    for (ByModule chunk : new MapCfgGroups(myMappings).groupByModule()) {
      sb.append(NameUtil.compactNamespace(chunk.getKey().getAlias()));
      chunk.getElements().map(TemplateMappingConfiguration::getName).collect(Collectors.joining(",", ":{", "}; "));
    }
    sb.append(']');
    return sb.toString();
  }
}
