/*
 * Copyright (C) 2007 Helge Hess <helge.hess@opengroupware.org>
 * 
 * This file is part of JOPE.
 * 
 * JOPE 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, or (at your option) any later version.
 * 
 * JOPE 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 JOPE; see the file COPYING. If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.opengroupware.jope.foundation;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/*
 * UMap
 * 
 * Map related utility functions.
 */
public class UMap extends NSObject {

  private UMap() {} /* do not allow construction */
  
  /* simple construction */
  
  /**
   * This method creates a new Map from a set of given key/value arguments.
   * <p>
   * Example:
   * <pre>Map adr = UMap.create("zip", 39112, "city", "Magdeburg");</pre>
   * 
   * This function always returns a mutable map, even if no arguments are
   * given.
   */
  @SuppressWarnings("unchecked")
  public static final Map create(Object... _values) {
    int len = _values != null ? _values.length : 0;
    if (len == 0) return new HashMap(1); /* 0 makes the map choose a value? */
    
    Map map = new HashMap(len / 2 + 1);
    for (int i = 0; i < len; i += 2)
      map.put(_values[i], (i + 1 == len) ? null : _values[i + 1]);

    return map;
  }

  /**
   * This method creates a new Map from a set of given array containing
   * key/value arguments. The function is almost exactly the same like create()
   * except that it does not take a varargs array.
   * <p>
   * 
   * This function always returns a mutable map, even if no arguments are
   * given.
   */
  @SuppressWarnings("unchecked")
  public static final Map createArgs(Object[] _values) {
    int len = _values != null ? _values.length : 0;
    if (len == 0) return new HashMap(1); /* 0 makes the map choose a value? */
    
    Map map = new HashMap(len / 2 + 1);
    for (int i = 0; i < len; i += 2)
      map.put(_values[i], (i + 1 == len) ? null : _values[i + 1]);

    return map;
  }
  
  /* queries */
  
  public static final Object[] allKeysForValue(Map _map, Object _value) {
    if (_map == null)
      return null;
    
    List<Object> keys = new ArrayList<Object>(4);
    for (Object key: _map.keySet()) {
      Object v = _map.get(key);
      
      if (v == _value)
        keys.add(key);
      else if (_value != null && _value.equals(v))
        keys.add(key);
    }
    
    return keys.toArray(new Object[keys.size()]);
  }
  
  public static final Object anyKeyForValue(Map _map, Object _value) {
    if (_map == null)
      return null;
    
    for (Object key: _map.keySet()) {
      Object v = _map.get(key);
      
      if (v == _value)
        return key;
      if (_value != null && _value.equals(v))
        return key;
    }

    return null;
  }
  
  public static final String extractString(Object _object) {
    /*
     * This is useful for database queries which return a single string. Be
     * careful with cyclic datastructures!
     * 
     * Eg if the EOAdaptor returns a
     * 
     *   List<Map<String, Object>> results = adaptor.performSQL()
     *   
     * but you know that there is only a string contained, eg:
     * 
     *   [ { comment = "Hello World!"; } ]
     *   
     * you can get the value by using the function:
     * 
     *   String comment = UMap.extractString(adaptor.performSQL());
     *   
     * Neat, eh? ;-)
     */
    // TBD: should be moved elsewhere?
    
    if (_object == null)
      return null;
    
    if (_object instanceof String)
      return (String)_object;
    
    if (_object instanceof Number)
      return _object.toString();

    if (_object instanceof Boolean)
      return ((Boolean)_object).booleanValue() ? "true" : "false";
    
    if (_object instanceof Map)
      return extractString(((Map)_object).values());

    if (_object instanceof List) {
      List l = (List)_object;
      return l.size() > 0 ? extractString(l.get(0)) : null;
    }
    
    if (_object instanceof Collection) {
      Iterator it = ((Collection)_object).iterator();
      return it.hasNext() ? extractString(it.next()) : null;
    }
    if (_object instanceof Iterator) {
      Iterator it = (Iterator)_object;
      return it.hasNext() ? extractString(it.next()) : null;
    }
    
    if (_object instanceof String[]) {
      String[] a = (String[])_object;
      return a.length > 0 ? a[0] : null;
    }
    
    return _object.toString();
  }
  public static final Object extractValue(Object _object) {
    /* Same like extractString, only that the value doesn't have to be a
     * string.
     */
    // TBD: should be moved elsewhere?
    
    if (_object == null)
      return null;
    
    if (_object instanceof Map)
      return extractValue(((Map)_object).values());

    if (_object instanceof List) {
      List l = (List)_object;
      return l.size() > 0 ? extractValue(l.get(0)) : null;
    }
    
    if (_object instanceof Collection) {
      Iterator it = ((Collection)_object).iterator();
      return it.hasNext() ? extractValue(it.next()) : null;
    }
    if (_object instanceof Iterator) {
      Iterator it = (Iterator)_object;
      return it.hasNext() ? extractValue(it.next()) : null;
    }
    
    if (_object instanceof String[]) {
      String[] a = (String[])_object;
      return a.length > 0 ? a[0] : null;
    }
    if (_object instanceof Object[]) {
      Object[] a = (Object[])_object;
      return a.length > 0 ? a[0] : null;
    }
    
    return _object;
  }
  
  
  /* Writing plist files */
  
  public static Exception writeToFile
    (Map _map, String _path, boolean _atomically)
  {
    String s = NSPropertyListSerialization.stringFromPropertyList(_map);
    return UString.writeToFile(s, _path, _atomically);
  }
  
  public static Exception writeToFile
    (Map _map, String _encoding, File _file, boolean _atomically)
  {
    String s = NSPropertyListSerialization.stringFromPropertyList(_map);
    return UString.writeToFile(s, _encoding, _file, _atomically);
  }
  
  /* Reading plist files */

  public static Map dictionaryWithContentsOfURL(URL _url) {
    Object plist = NSPropertyListSerialization.propertyListWithPathURL(_url);
    return (plist instanceof Map) ? (Map)plist : null;
  }
  
  public static Map dictionaryWithContentsOfFile(File _file) {
    if (_file == null)
      return null;
    
    try {
      return dictionaryWithContentsOfURL(_file.toURL());
    }
    catch (MalformedURLException e) {
      return null;
    }
  }
  
  public static Map dictionaryWithContentsOfFile(String _path) {
    if (_path == null)
      return null;
    
    return dictionaryWithContentsOfFile(new File(_path));
  }
}
