/*
 * 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.nutch.metadata;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

/**
 * A decorator to Metadata that adds spellchecking capabilities to property
 * names. Currently used spelling vocabulary contains just the httpheaders from
 * {@link HttpHeaders} class.
 * 
 */
public class SpellCheckedMetadata extends Metadata {

  /**
   * Treshold divider.
   * 
   * <code>threshold = searched.length() / TRESHOLD_DIVIDER;</code>
   */
  private static final int TRESHOLD_DIVIDER = 3;

  /**
   * Normalized name to name mapping.
   */
  private final static Map<String, String> NAMES_IDX = new HashMap<String, String>();

  /**
   * Array holding map keys.
   */
  private static String[] normalized = null;

  static {

    // Uses following array to fill the metanames index and the
    // metanames list.
    Class<?>[] spellthese = { HttpHeaders.class };

    for (Class<?> spellCheckedNames : spellthese) {
      for (Field field : spellCheckedNames.getFields()) {
        int mods = field.getModifiers();
        if (Modifier.isFinal(mods) && Modifier.isPublic(mods)
            && Modifier.isStatic(mods) && field.getType().equals(String.class)) {
          try {
            String val = (String) field.get(null);
            NAMES_IDX.put(normalize(val), val);
          } catch (Exception e) {
            // Simply ignore...
          }
        }
      }
    }
    normalized = NAMES_IDX.keySet().toArray(new String[NAMES_IDX.size()]);
  }

  /**
   * Normalizes String.
   * 
   * @param str
   *          the string to normalize
   * @return normalized String
   */
  private static String normalize(final String str) {
    char c;
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < str.length(); i++) {
      c = str.charAt(i);
      if (Character.isLetter(c)) {
        buf.append(Character.toLowerCase(c));
      }
    }
    return buf.toString();
  }

  /**
   * Get the normalized name of metadata attribute name. This method tries to
   * find a well-known metadata name (one of the metadata names defined in this
   * class) that matches the specified name. The matching is error tolerent. For
   * instance,
   * <ul>
   * <li>content-type gives Content-Type</li>
   * <li>CoNtEntType gives Content-Type</li>
   * <li>ConTnTtYpe gives Content-Type</li>
   * </ul>
   * If no matching with a well-known metadata name is found, then the original
   * name is returned.
   * 
   * @param name
   *          Name to normalize
   * @return normalized name
   */
  public static String getNormalizedName(final String name) {
    String searched = normalize(name);
    String value = NAMES_IDX.get(searched);

    if ((value == null) && (normalized != null)) {
      int threshold = searched.length() / TRESHOLD_DIVIDER;
      for (int i = 0; i < normalized.length && value == null; i++) {
        if (StringUtils.getLevenshteinDistance(searched, normalized[i]) < threshold) {
          value = NAMES_IDX.get(normalized[i]);
        }
      }
    }
    return (value != null) ? value : name;
  }

  @Override
  public void remove(final String name) {
    super.remove(getNormalizedName(name));
  }

  @Override
  public void add(final String name, final String value) {
    super.add(getNormalizedName(name), value);
  }

  @Override
  public String[] getValues(final String name) {
    return super.getValues(getNormalizedName(name));
  }

  @Override
  public String get(final String name) {
    return super.get(getNormalizedName(name));
  }

  @Override
  public void set(final String name, final String value) {
    super.set(getNormalizedName(name), value);
  }

}
