//______________________________________________________________________________
//   |       |
//   | knipp |           Knipp Medien und Kommunikation GmbH
//    -------
//
//
//     Knipp RRI Toolkit
//
//
//     Copyright (C) 2005 by Knipp Medien und Kommunikation GmbH
//
//     This library is free software; you can redistribute it and/or
//     modify it under the terms of the GNU Lesser General Public License 
//     as published by the Free Software Foundation; either version 2.1 
//     of the License, or any later version.
// 
//     This library 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.
//
//     You should have received a copy of the GNU Lesser General
//     Public License along with this library; if not, write to the 
//     Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
//     Boston, MA 02111-1307 USA 
//
//
//     initial author:    Klaus Malorny
//
//     version:           1.3 (15112), 2011/05/27 16:00:14
//                        $Revision: 14936 $ 
//                        $Date: 2010-11-05 15:57:40 +0100 (Fri, 05 Nov 2010) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit;

import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.Collections;
import java.util.NoSuchElementException;

/**
 *  class that represents a key-value list as used in DENIC Key Value
 *  messages for requests and part of responses. Please note that the comparison 
 *  of keys is performed case-sensitive. Therefore, all keys are expected to 
 *  be lower-case normalized.
 *
 *  @version  $Revision: 14936 $
 *  @author   Klaus Malorny
 */

public class KVList implements Iterable<KVList.Pair>
{
  /** all key value pairs */
  
  private LinkedHashMap<String, ValueImpl> pairs;
  
  
  /**
   *  constructor
   */
  
  public KVList ()
  {
    pairs = new LinkedHashMap<String, ValueImpl> ();
  }
  
  
  
  /**
   *  add a value for the given key
   *
   *  @param key                the key
   *  @param value              the value
   */
  
  public void add (String key, String value)
  {
    ValueImpl v = pairs.get (key);
    
    if (v == null)
    {
      v = new ValueImpl ();
      pairs.put (key, v);
    }
    
    v.add (value);
  }
  
  
  /**
   *  add all values for the given key
   *
   *  @param key                the key
   *  @param values             the values
   */
  
  public void add (String key, String... values)
  {
    if (values.length == 0) return;
    
    ValueImpl v = pairs.get (key);
    
    if (v == null)
    {
      v = new ValueImpl ();
      pairs.put (key, v);
    }
    
    v.add (values);
  }
  
  
  /**
   *  add a collection of values for the given key
   *
   *  @param key                the key
   *  @param col                the values
   */
  
  public void add (String key, Collection<String> col)
  {
    if (col.size () == 0) return;
    
    ValueImpl v = pairs.get (key);
    
    if (v == null)
    {
      v = new ValueImpl ();
      pairs.put (key, v);
    }
    
    for (String value: col) v.add (value);
  }
  
  
  
  /**
   *  set the given key, replacing all previous contents
   *
   *  @param key                   the key
   *  @param value                 the value
   */
  
  public void set (String key, String value)
  {
    ValueImpl v = pairs.get (key);
    
    if (v == null)
    {
      v = new ValueImpl ();
      pairs.put (key, v);
    }
    
    v.set (value);
  }
  
  
  /**
   *  set the given key, replacing all previous contents
   *
   *  @param key                the key
   *  @param values             the values
   */
  
  public void set (String key, String... values)
  {
    if (values.length == 0)
    {
      pairs.remove (key);
      return;
    }
    
    ValueImpl v = pairs.get (key);
    
    if (v == null)
    {
      v = new ValueImpl ();
      pairs.put (key, v);
    }    
    
    v.set (values);
  }
  

  /**
   *  set the given key, replacing all previous contents
   *
   *  @param key                the key
   *  @param col                the values
   */
  
  public void set (String key, Collection<String> col)
  {
    if (col.isEmpty ())
    {
      pairs.remove (key);
      return;
    }
    
    ValueImpl v = pairs.get (key);
    
    if (v == null)
    {
      v = new ValueImpl ();
      pairs.put (key, v);
    } else
    {
      v.clear ();
    }
    
    for (String value: col) v.add (value);
  }
  
  
  
  /**
   *  clear the given key
   *
   *  @param key               the key
   */
  
  public void clear (String key)
  { 
    pairs.remove (key);
  }
  
  
  
  /**
   *  return whether the given key exists
   *
   *  @param key                  the key
   *  @return                     {@code true} if so
   */
  
  public boolean containsKey (String key)
  {
    return pairs.containsKey (key);
  }
  
  
  
  /**
   *  return the number of strings associated with a key
   *
   *  @param key                   the key
   *  @return                      the number of strings, {@code 0}
   *                               indicating that the key does not exist
   */
  
  public int getCount (String key)
  {
    ValueImpl v = pairs.get (key);
    return v == null ? 0 : v.getCount ();
  }
  
  
  /**
   *  return the first string of the key
   *
   *  @param key                   the key
   *  @return                      the first string or {@code null} if
   *                               the key does not exist
   */
  
  public String getFirst (String key)
  {
    ValueImpl v = pairs.get (key);
    return v == null ? null : v.getFirst ();
  }
  
  
  /**
   *  return all strings associated with the key
   *
   *  @param key                   the key
   *  @param values                the array where the strings are stored
   *                               (may be {@code null})
   *  @return                      the given array or a new array if the given
   *                               one was too small or {@code null}. If
   *                               the key does not exist, {@code null}
   *                               is returned
   */
  
  public String[] getAll (String key, String[] values)
  {
    ValueImpl v = pairs.get (key);
    return v == null ? null : v.toArray (values);
  }
  
  
  
  /**
   *  return all strings associated with the key
   *
   *  @param key                   the key
   *  @return                      the array with the strings or 
   *                               {@code null}
   */
  
  public String[] getAll (String key)
  {
    return getAll (key, null);
  }
  
  
  
  /**
   *  return the strings associated with the key as a list
   *
   *  @param key                    the key
   *  @return                       the list, which is empty in case that the
   *                                key does not exist
   */
  
  public List<String> getList (String key)
  {
    ValueImpl v = pairs.get (key);
    if (v == null) return Collections.emptyList ();
    return v.toList ();
  }
  
  
  /**
   *  return an iterator over all strings of a key
   *
   *  @param key                    the key
   *  @return                       the iterator. If the key does not exist,
   *                                the returned iterator does not enumerate
   *                                any value
   */
  
  public Iterator<String> getIterator (String key)
  {
    ValueImpl v = pairs.get (key);
    return v == null ? new EmptyIterator<String> () : v.iterator ();
  }
  
  
  
  /**
   *  return an iterator over the keys
   *
   *  @return                       the iterator
   */
  
  public Iterator<String> getKeyIterator ()
  {
    return pairs.keySet ().iterator ();
  }
  
  
  
  /**
   *  return an iterator over the key-value pairs
   *
   *  @return                      the iterator
   */

  @Override
  public Iterator<Pair> iterator ()
  {
    return new Iterator<Pair> ()
      { 
        private final Iterator<Map.Entry<String, ValueImpl>> i = 
          pairs.entrySet ().iterator ();

        @Override
        public boolean hasNext ()
        {
          return i.hasNext ();
        }

        @Override
        public Pair next ()
          throws NoSuchElementException
        {
          Map.Entry<String, ValueImpl> e = i.next ();
          return new Pair (e.getKey (), e.getValue ());
        }

        @Override
        public void remove ()
          throws UnsupportedOperationException
        {
          throw new UnsupportedOperationException ();
        }
      };
  }
  
  
  
  
  /**
   *  small class holding a key value pair (used for the iterator
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  public static class Pair
  {
    /** the key */
    
    private String key;
    
    
    /** the value */
    
    private Value value;
    
    
    /**
     *  constructor
     *
     *  @param theKey            the key
     *  @param theValue          the value
     */
    
    public Pair (String theKey, Value theValue)
    {
      key = theKey;
      value = theValue;
    }
    
    
    /**
     *  return the key
     *
     *  @return                  the key
     */
    
    public String getKey ()
    {
      return key;
    }
    
    
    /**
     *  return the value
     *
     *  @return                  the value
     */
    
    public Value getValue ()
    {
      return value;
    }
  }
  
  
  
  
  
  /**
   *  public interface to values. It contains the read-only methods
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  public interface Value extends Iterable<String>
  {
    /**
     *  return the number of strings stored in the value
     *
     *  @return                       the count
     */
    
    public int getCount ();
    
    /**
     *  return the first value
     *
     *  @return                       the first value or {@code null} if
     *                                the value does not contain any string
     */
    
    public String getFirst ();
    
    
    
    /**
     *  copy the contents into the given array. If the array is
     *  {@code null} or too small, a new array is allocated
     *
     *  @param values                 the array, may be {@code null}
     *  @return                       the given array or a new array
     */
    
    public String[] toArray (String[] values);
    
    
    /**
     *  return the content as a list
     *
     *  @return                       the list (read-only)
     */
    
    public List<String> toList ();
    
  }
  
  
  /**
   *  inner class that implements a value
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  protected static class ValueImpl implements Value
  {
    /** string value for a single entry, may be {@code null} */
    
    private String value;
    
    
    /** list for multiple entries, may be {@code null} */
    
    private List<String> list;
    
    
    
    /**
     *  constructor
     */
    
    public ValueImpl ()
    {
      // empty
    }
  
    
    
    /**
     *  constructor
     *
     *  @param v                  the value to initialize with
     */
    
    public ValueImpl (String v)
    { 
      add (v);
    }
    
    
    /**
     *  add the given string as a value
     *
     *  @param v                  the value
     */
    
    public void add (String v)
    { 
      if (list != null)
      {
        list.add (v);
        
      } else if (value != null)
      {
        list = new ArrayList<String> ();
        list.add (value);
        list.add (v);
        value = null;
        
      } else
      {
        value = v;
      }
    }
    
   
    
    /**
     *  add all strings to the value
     *
     *  @param values              the values
     */
    
    public void add (String[] values)
    {
      for (String v: values) add (v);
    }
    
    
    /**
     *  clear the content
     */
    
    public void clear ()
    {
      value = null;
      list = null;
    }
    
    
    /**
     *  replace the contents of the value with the given one
     *
     *  @param v                      the value
     */
    
    public void set (String v)
    {
      clear ();
      add (v);
    }
    
    
    /**
     *  replace the contents of the value with the given strings
     *
     *  @param values                 the values
     */
    
    public void set (String[] values)
    {
      clear ();
      for (String v: values) add (v);
    }
    
    
    
    
    
    /**
     *  return the number of strings stored in the value
     *
     *  @return                       the count
     */

    @Override
    public int getCount ()
    {
      return list != null ? list.size () : value == null ? 0 : 1;
    }
    
    /**
     *  return the first value
     *
     *  @return                       the first value or {@code null} if
     *                                the value does not contain any string
     */

    @Override
    public String getFirst ()
    {
      return list != null ? list.get (0) : value;
    }
    
    
    
    /**
     *  copy the contents into the given array. If the array is
     *  {@code null} or too small, a new array is allocated
     *
     *  @param values                 the array, may be {@code null}
     *  @return                       the given array or a new array
     */

    @Override
    public String[] toArray (String[] values)
    {
      int count = getCount ();
      
      if (values == null || values.length < count)
        values = new String[count];
      
      if (list != null)
      {
        list.toArray (values);
      
      } else if (values != null)
      {
        values[0] = value;
      }
      
      return values;
    }
    

    /**
     *  return the content as a list
     *
     *  @return                       the list (read-only)
     */

    @Override
    public List<String> toList ()
    {
      if (list != null) return Collections.unmodifiableList (list);
      if (value != null) return Collections.singletonList (value);
      return Collections.emptyList ();
    }
    
    
    
    
    /**
     *  return an iterator for the value that iterates thru the strings
     *
     *  @return                        the iterator
     */

    @Override
    public Iterator<String> iterator ()
    {
      if (list != null) 
        return new RemoveDisabledIterator<String> (list.iterator ());
      
      if (value != null) return new SingletonIterator<String> (value);
      
      return new EmptyIterator<String> ();
    }
  }
  
  
  
 

  
  /**
   *  small class repesenting a singleton iterator
   *
   *  @param <E>                   the value type
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  private static class SingletonIterator<E> implements Iterator<E>
  {
    /** the value */
    
    private E value;
    
    
    /** flag whether we are at the first element */
    
    private boolean first;
    
    
    /**
     *  constructor
     *
     *  @param v                    the value
     */
    
    public SingletonIterator (E v)
    {
      value = v;
      first = true;
    }
    
    //--- implementation of Iterator
    
    
    /**
     *  return whether there are more elements
     *
     *  @return                   {@code true} if so
     */

    @Override
    public boolean hasNext ()
    {
      return first;
    }
    
    
    /**
     *  return the first element
     *
     *  @return                          the element
     *  @throws NoSuchElementException   on attempts to read past the end
     */

    @Override
    public E next ()
      throws NoSuchElementException
    {
      if (first)
      {
        first = false;
        return value;
      }
      
      throw new NoSuchElementException ();
    }
    
 
    /**
     *  remove the current element
     *
     *  @throws UnsupportedOperationException     always
     */

    @Override
    public void remove ()
      throws UnsupportedOperationException
    {
      throw new UnsupportedOperationException ();
    }
  }
  
  
  
  /**
   *  small class implementing an iterator for no elements
   *
   *  @param <E>              the value type
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  private static class EmptyIterator<E> implements Iterator<E>
  {
    /**
     *  return whether there are more elements
     *
     *  @return                   {@code true} if so
     */

    @Override
    public boolean hasNext ()
    {
      return false;
    }
    
    
    /**
     *  return the first element
     *
     *  @return                          the element
     *  @throws NoSuchElementException   always
     */

    @Override
    public E next ()
      throws NoSuchElementException
    {
      throw new NoSuchElementException ();
    }
    
 
    /**
     *  remove the current element
     *
     *  @throws UnsupportedOperationException     always
     */

    @Override
    public void remove ()
      throws UnsupportedOperationException
    {
      throw new UnsupportedOperationException ();
    }
  }
  
  
  
  /**
   *  small wrapper class of an Iterator the suppresses the use of the
   *  remove method
   *
   *  @param <E>               the value type
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  private static class RemoveDisabledIterator<E> implements Iterator<E>
  {
    /** the original iterator */
    
    private Iterator<E> iterator;

    
    /**
     *  constructor
     *
     *  @param i                        the iterator
     */
    
    public RemoveDisabledIterator (Iterator<E> i)
    {
      iterator = i;
    }

    
    //--- implementation of Iterator
    
    
    /**
     *  return whether there are more elements
     *
     *  @return                   {@code true} if so
     */

    @Override
    public boolean hasNext ()
    {
      return iterator.hasNext ();
    }
    
    
    /**
     *  return the first element
     *
     *  @return                          the element
     *  @throws NoSuchElementException   on attempts to read past the end
     */

    @Override
    public E next ()
      throws NoSuchElementException
    {
      return iterator.next ();
    }
    
 
    /**
     *  remove the current element
     *
     *  @throws UnsupportedOperationException     always
     */

    @Override
    public void remove ()
      throws UnsupportedOperationException
    {
      throw new UnsupportedOperationException ();
    }
  }
}







