/**
 * 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.hadoop.tools.rumen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.hadoop.tools.rumen.datatypes.NodeName;
import org.codehaus.jackson.annotate.JsonAnySetter;

/**
 * A {@link LoggedLocation} is a representation of a point in an hierarchical
 * network, represented as a series of membership names, broadest first.
 * 
 * For example, if your network has <i>hosts</i> grouped into <i>racks</i>, then
 * in onecluster you might have a node {@code node1} on rack {@code rack1}. This
 * would be represented with a ArrayList of two layers, with two {@link String}
 * s being {@code "rack1"} and {@code "node1"}.
 * 
 * The details of this class are set up to meet the requirements of the Jackson
 * JSON parser/generator.
 * 
 * All of the public methods are simply accessors for the instance variables we
 * want to write out in the JSON files.
 * 
 */
public class LoggedLocation implements DeepCompare {
   static final Map<List<String>, List<NodeName>> layersCache = 
    new HashMap<List<String>, List<NodeName>>();

  /**
   * The full path from the root of the network to the host.
   * 
   * NOTE that this assumes that the network topology is a tree.
   */
  List<NodeName> layers = Collections.emptyList();

  static private Set<String> alreadySeenAnySetterAttributes =
      new TreeSet<String>();

  public List<NodeName> getLayers() {
    return layers;
  }

  void setLayers(List<String> layers) {
    if (layers == null || layers.isEmpty()) {
      this.layers = Collections.emptyList();
    } else {
      synchronized (layersCache) {
        List<NodeName> found = layersCache.get(layers);
        if (found == null) {
          // make a copy with interned string.
          List<NodeName> clone = new ArrayList<NodeName>(layers.size());
          clone.add(new NodeName(layers.get(0).intern(), null)); 
          clone.add(new NodeName(null, layers.get(1).intern()));
          
          // making it read-only as we are sharing them.
          List<NodeName> readonlyLayers = Collections.unmodifiableList(clone);
          List<String> readonlyLayersKey = Collections.unmodifiableList(layers);
          layersCache.put(readonlyLayersKey, readonlyLayers);
          this.layers = readonlyLayers;
        } else {
          this.layers = found;
        }
      }
    }
  }

  // for input parameter ignored.
  @JsonAnySetter
  public void setUnknownAttribute(String attributeName, Object ignored) {
    if (!alreadySeenAnySetterAttributes.contains(attributeName)) {
      alreadySeenAnySetterAttributes.add(attributeName);
      System.err.println("In LoggedJob, we saw the unknown attribute "
          + attributeName + ".");
    }
  }

  // I'll treat this as an atomic object type
  private void compareStrings(List<NodeName> c1, List<NodeName> c2, 
                              TreePath loc, String eltname) 
  throws DeepInequalityException {
    if (c1 == null && c2 == null) {
      return;
    }

    TreePath recursePath = new TreePath(loc, eltname);

    if (c1 == null || c2 == null || (c1.size() != c2.size())) {
      throw new DeepInequalityException(eltname + " miscompared", recursePath);
    }
    
    for (NodeName n1 : c1) {
      boolean found = false;
      for (NodeName n2 : c2) {
        if (n1.getValue().equals(n2.getValue())) {
          found = true;
          break;
        }
      }
      
      if (!found) {
        throw new DeepInequalityException(eltname 
                  + " miscompared [" + n1.getValue() +"]", recursePath);
      }
    }
  }

  public void deepCompare(DeepCompare comparand, TreePath loc)
      throws DeepInequalityException {
    if (!(comparand instanceof LoggedLocation)) {
      throw new DeepInequalityException("comparand has wrong type", loc);
    }

    LoggedLocation other = (LoggedLocation) comparand;

    compareStrings(layers, other.layers, loc, "layers");

  }
}
