/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.uima.analysis_engine.impl;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * This object holds the set of RsTypes for a result spec There is one instance of this per
 * ResultSpecification_impl
 */
public class RsTypesMap implements Iterable<RsType> {

  private final Map<String, RsType> types;

  RsTypesMap() {
    types = new HashMap<>();
  }

  /**
   * cloning constructor - clones its arg
   * 
   * @param src
   */
  RsTypesMap(RsTypesMap src) {
    types = new HashMap<>(src.types);
    for (Map.Entry<String, RsType> e : types.entrySet()) {
      e.setValue(new RsType(e.getValue())); // copy
    }
  }

  /**
   * add a type (not a type:feat)
   * 
   * @param typeName
   * @param isAllFeat
   * @param languages
   * @param replace
   */
  void add(String typeName, boolean isAllFeat, String[] languages, boolean replace) {
    RsType t = types.get(typeName);
    if (null == t) {
      t = new RsType(typeName);
      types.put(typeName, t);
    }
    if (isAllFeat) {
      if (!t.isAllFeatures) {
        replace = true; // if setting for the 1st time, replace the x-unspec which is the default
      }
      t.isAllFeatures = true;
      t.languagesAllFeat = addLanguages(t.languagesAllFeat, languages, replace);
    } else {
      if (!t.isSpecified) {
        replace = true;
      }
      t.isSpecified = true;
      t.languagesNotAllFeat = addLanguages(t.languagesNotAllFeat, languages, replace);
    }
  }

  void add(String typeName, boolean isAllFeat, RsLangs rslangs, boolean replace) {
    RsType t = types.get(typeName);
    if (null == t) {
      t = new RsType(typeName);
      types.put(typeName, t);
    }
    if (isAllFeat) {
      if (!t.isAllFeatures) {
        replace = true;
      }
      t.isAllFeatures = true;
      t.languagesAllFeat = addLanguages(t.languagesAllFeat, rslangs, replace);
    } else {
      if (!t.isSpecified) {
        replace = true;
      }
      t.isSpecified = true;
      t.languagesNotAllFeat = addLanguages(t.languagesNotAllFeat, rslangs, replace);
    }
  }

  /**
   * add a feature (not a plain type) If feature exists, augments (union) its languages or replaces
   * it
   * 
   * @param typeName
   * @param featName
   * @param languages
   * @param replace
   */

  void add(String typeName, String shortFeatName, Object rslangs, boolean replace) {
    RsType t = types.get(typeName);
    if (null == t) {
      t = new RsType(typeName);
      types.put(typeName, t);
    }
    RsFeats feats = t.features;
    RsFeat feat = null;
    if (null == feats) {
      feats = new RsFeats();
      t.features = feats;
    } else {
      feat = feats.get(shortFeatName);
    }
    if (null == feat) {
      feats.add(shortFeatName, rslangs);
    } else {
      feat.languages = addLanguages(feat.languages, rslangs, replace);
    }
  }

  RsLangs addLanguages(RsLangs existing, Object langs, boolean replace) {
    String[] saLangs = null;
    RsLangs rsLangs = null;
    if (langs instanceof String[]) {
      saLangs = (String[]) langs;
    } else {
      rsLangs = (RsLangs) langs;
    }

    boolean noLangs = (null == langs) ? true
            : (null != saLangs) ? (0 == ((String[]) langs).length) : RsLangs.isEmpty(rsLangs);

    if (noLangs) {
      if (replace) {
        return null;
      }
      return existing;
    }
    if (null == existing && !replace) { // existing is x-unspecified
      return null; // subsumes everything
    }
    if (replace) {
      if (saLangs != null) {
        return RsLangs.createOrNull(saLangs);
      } else {
        rsLangs.setShared();
        return rsLangs;
      }
    }
    // not replace, and existing
    existing = (null != saLangs) ? RsLangs.addAll(existing, saLangs)
            : RsLangs.addAll(existing, rsLangs);
    return existing;
  }

  RsLangs addLanguages(RsLangs existing, RsLangs rslangs, boolean replace) {
    if (RsLangs.isEmpty(rslangs)) {
      if (replace) {
        return null;
      }
      return existing;
    }
    if (null == existing && !replace) { // existing is x-unspecified
      return null;
    }
    if (replace) {
      rslangs.setShared();
      return rslangs;
    }
    existing = RsLangs.addAll(existing, rslangs);
    return existing;
  }

  /**
   * Remove a type, regardless of languages NOTE: doesn't remove type:feature entries associated
   * with that type
   * 
   * @param type
   */
  void remove(String type) {
    RsType t = types.get(type);
    if (null != t) {
      if (null == t.features || t.features.size() == 0) {
        types.remove(type);
        return;
      }
      t.isAllFeatures = false;
      t.isSpecified = false;
      t.languagesAllFeat = null;
      t.languagesNotAllFeat = null;
    }
  }

  /**
   * remove a feature, regardless of languages If all features are removed, null out the rsFeats
   * slot. If all features are removed, and no type instance, remove the type also.
   * 
   * @param typeName
   * @param feature
   *          Short Name
   */
  void remove(String type, String feature) {
    RsType t = types.get(type);
    if (null != t) {
      if (null != t.features) {
        t.features.remove(type, feature);
        if (0 == t.features.size()) {
          t.features = null;
          if (!t.isAllFeatures && !t.isSpecified) {
            types.remove(type);
          }
        }
      }
    }
  }

  RsType getRsType(String typeName) {
    return types.get(typeName);
  }

  RsFeat get(String typeName, String shortFeatName) {
    RsType t = types.get(typeName);
    if (t == null || t.features == null) {
      return null;
    }
    return t.features.get(shortFeatName);
  }

  int nbrOfTypes() {
    return types.size();
  }

  @Override
  public Iterator<RsType> iterator() {
    return types.values().iterator();
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((types == null) ? 0 : types.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    RsTypesMap other = (RsTypesMap) obj;
    if (types == null) {
      if (other.types != null) {
        return false;
      }
    } else if (!types.equals(other.types)) {
      return false;
    }
    return true;
  }

}
