/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * Copyright (c) 2006 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.  All rights reserved.
 */

package org.pentaho.reporting.libraries.xmlns.writer;

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

import org.pentaho.reporting.libraries.xmlns.common.AttributeList;

/**
 * A immutable namespace collection. Any attempt to modify the declared namespaces creates a new copy of the map.
 *
 * @author Thomas Morgner
 */
public final class DeclaredNamespaces
{
  private HashMap namespaces;

  /**
   * Creates a new namespaces collection.
   */
  public DeclaredNamespaces()
  {
  }

  /**
   * Creates a new namespaces collection using the given namespaces as initial values.
   *
   * @param namespaces the namespaces, never null.
   */
  public DeclaredNamespaces(final DeclaredNamespaces namespaces)
  {
    if (namespaces == null)
    {
      throw new NullPointerException();
    }

    if (namespaces.namespaces != null)
    {
      this.namespaces = (HashMap) namespaces.namespaces.clone();
    }
  }

  /**
   * Adds all namespaces from the given hashmap into this map. The namespaces map must only contain string keys and
   * string values and must not contain either null-keys or null-values.
   *
   * @param newNamespaces the namespaces collection.
   * @return the created namespaces object.
   */
  public DeclaredNamespaces add(final HashMap newNamespaces)
  {
    if (newNamespaces == null)
    {
      throw new NullPointerException();
    }

    final DeclaredNamespaces retval = new DeclaredNamespaces();
    if (this.namespaces == null)
    {
      retval.namespaces = new HashMap();
    }
    else
    {
      retval.namespaces = (HashMap) this.namespaces.clone();
    }

    final Iterator iterator = newNamespaces.entrySet().iterator();
    while (iterator.hasNext())
    {
      final Map.Entry entry = (Map.Entry) iterator.next();
      final String value = (String) entry.getValue();
      final String o = (String) entry.getKey();
      if (value == null || o == null)
      {
        throw new NullPointerException();
      }
      retval.namespaces.put(o, value);

    }
    return retval;
  }

  /**
   * Adds all declared namespaces from the given attribute-list into the namespaces collection.
   *
   * @param attributes the attribute list containing namespace definitions.
   * @return the new namespaces collection.
   */
  public DeclaredNamespaces add(final AttributeList attributes)
  {
    if (attributes == null)
    {
      throw new NullPointerException();
    }

    DeclaredNamespaces retval = null;
    final AttributeList.AttributeEntry[] entries = attributes.toArray();
    for (int i = 0; i < entries.length; i++)
    {
      final AttributeList.AttributeEntry entry = entries[i];
      final String prefix = entry.getName();
      if ("xmlns".equals(prefix))
      {
        if (entry.getNamespace() == null || "".equals(entry.getNamespace()))
        {
          if (retval == null)
          {
            retval = new DeclaredNamespaces();
            if (namespaces != null)
            {
              retval.namespaces = (HashMap) namespaces.clone();
            }
            else
            {
              retval.namespaces = new HashMap();
            }
          }
          retval.namespaces.put(entry.getValue(), "");
        }
      }
      else if (AttributeList.XMLNS_NAMESPACE.equals(entry.getNamespace()))
      {
        if (retval == null)
        {
          retval = new DeclaredNamespaces();
          if (namespaces != null)
          {
            retval.namespaces = (HashMap) namespaces.clone();
          }
          else
          {
            retval.namespaces = new HashMap();
          }
        }
        retval.namespaces.put(entry.getValue(), prefix);
      }
    }

    if (retval == null)
    {
      return this;
    }
    return retval;
  }

  /**
   * Adds a single namespace definition to the collection.
   *
   * @param uri the URI of the namespace.
   * @param prefix the prefix to be used for the namespace.
   * @return the new namespaces collection.
   */
  public DeclaredNamespaces add(final String uri, final String prefix)
  {
    if (uri == null)
    {
      throw new NullPointerException();
    }
    if (prefix == null)
    {
      throw new NullPointerException();
    }
    final DeclaredNamespaces retval = new DeclaredNamespaces();
    if (namespaces == null)
    {
      retval.namespaces = new HashMap();
    }
    else
    {
      retval.namespaces = (HashMap) namespaces.clone();
    }
    retval.namespaces.put(uri, prefix);
    return retval;
  }

  /**
   * Looksup the prefix for the given URI. This returns null if the URI is not a declared namespace.
   *
   * @param uri the URI.
   * @return the prefix for the given URI or null, if the URI is not part of this collection.
   */
  public String getPrefix(final String uri)
  {
    if (uri == null)
    {
      throw new NullPointerException();
    }

    if (namespaces == null)
    {
      return null;
    }
    return (String) namespaces.get(uri);
  }

  /**
   * Checks, whether the namespace marked by the given URI is defined in this collection.
   *
   * @param uri the URI to be checked.
   * @return true, if the URI is known, false otherwise.
   */
  public boolean isNamespaceDefined(final String uri)
  {
    if (uri == null)
    {
      throw new NullPointerException();
    }

    if (namespaces == null)
    {
      return false;
    }
    return namespaces.containsKey(uri);
  }

  /**
   * Returns all known namespaces as unmodifiable map.
   *
   * @return the namespaces.
   */
  public Map getNamespaces()
  {
    return Collections.unmodifiableMap(namespaces);
  }

  /**
   * Checks whether the given prefix is already defined in the collection.
   *
   * @param prefix the prefix.
   * @return true, if the prefix is already used, false otherwise.
   */
  public boolean isPrefixDefined(final String prefix)
  {
    return namespaces.containsValue(prefix);
  }
}
