package nxm.sys.inc;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.addAll;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySortedSet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static nxm.sys.lib.Convert.o2b;
import static nxm.sys.lib.Convert.o2d;
import static nxm.sys.lib.Convert.o2f;
import static nxm.sys.lib.Convert.o2i;
import static nxm.sys.lib.Convert.o2l;
import static nxm.sys.lib.Convert.o2n;
import static nxm.sys.lib.Convert.o2t;
import static nxm.sys.lib.Convert.o2x;
import static nxm.sys.lib.StringUtil.isFalse;
import static nxm.sys.lib.StringUtil.isTrue;
import static nxm.sys.lib.Time.toTime;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import nxm.sys.lib.Data;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;
import nxm.sys.lib.Time;

/** This is an extension of the {@link Keyable} type that indicates that in-line type casting is
    available.

    @author  J. Czechowski IV
    @since   NeXtMidas 2.1.0
 */
public interface KeyableByType extends Keyable {
  //////////////////////////////////////////////////////////////////////////////////////////////////
  // NUMERICAL ACCESSORS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Gets value with the given name as an 8-bit Byte.
   *  This is a convenience method equal to {@code getB(name,(byte)0)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code 0} such does not exist.
   *  @see #getB(String,byte)
   */
  default byte getB (String name) {
    return getB(name,(byte)0);
  }

  /** Gets value with the given name as an 8-bit Byte.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default byte getB (String name, byte defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2b(val);
  }

  /** Gets value with the given name as an 8-bit Byte.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Byte getB (String name, Byte defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2b(val);
  }

  /** Gets value with the given name as a 16-bit Midas Integer (Java short).
   *  This is a convenience method equal to {@code getI(name,(short)0)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code 0} such does not exist.
   *  @see #getI(String,short)
   */
  default short getI (String name) {
    return getI(name,(short)0);
  }

  /** Gets value with the given name as a 16-bit Midas Integer (Java short).
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default short getI (String name, short defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2i(val);
  }

  /** Gets value with the given name as a 16-bit Midas Integer (Java short).
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Short getI (String name, Short defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2i(val);
  }

  /** Gets value with the given name as a 32-bit Midas Long (Java int).
   *  This is a convenience method equal to {@code getL(name,(int)0)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code 0} such does not exist.
   *  @see #getL(String,int)
   */
  default int getL (String name) {
    return getL(name,0);
  }

  /** Gets value with the given name as a 32-bit Midas Long (Java int).
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default int getL (String name, int defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2l(val);
  }

  /** Gets value with the given name as a 32-bit Midas Long (Java int).
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Integer getL (String name, Integer defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2l(val);
  }

  /** Gets value with the given name as 64-bit Midas eXtra long (Java long).
   *  This is a convenience method equal to {@code getX(name,0L)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code 0} such does not exist.
   *  @see #getX(String,long)
   */
  default long getX (String name) {
    return getX(name,0L);
  }

  /** Gets value with the given name as 64-bit Midas eXtra long (Java long).
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default long getX (String name, long defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2x(val);
  }

  /** Gets value with the given name as 64-bit Midas eXtra long (Java long).
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Long getX (String name, Long defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2x(val);
  }

  /** Gets value with the given name as a 32-bit Midas Float.
   *  This is a convenience method equal to {@code getF(name,0.0f)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code 0} such does not exist.
   *  @see #getF(String,float)
   */
  default float getF (String name) {
    return getF(name,0f);
  }

  /** Gets value with the given name as a 32-bit Midas Float.
   *  This is a convenience method equal to {@code (float)getF(name,defValue)}.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default float getF (String name, float defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2f(val);
  }

  /** Gets value with the given name as a 32-bit Midas Float.
   *  This is a convenience method equal to {@code (float)getF(name,defValue)}.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Float getF (String name, Float defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2f(val);
  }

  /** Gets value with the given name as a 64-bit Midas Double.
   *  This is a convenience method equal to {@code getD(name,0.0d)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @see #getD(String,double)
   */
  default double getD (String name) {
    return getD(name,0);
  }

  /** Gets value with the given name as a 64-bit Midas Double.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default double getD (String name, double defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2d(val);
  }

  /** Gets value with the given name as a 64-bit Midas Double.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Double getD (String name, Double defValue) {
    Object val = getO(name);
    if (val == null) return defValue;
    return o2d(val);
  }

  /** Gets value with the given name as a Number.
   *  This is a convenience method equal to {@code getN(name,0)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   *  @see #getN(String,Number)
   */
  @InternalUseOnly
  default Number getN (String name) {
    return getN(name,0);
  }

  /** Gets value with the given name as a Number.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Number getN (String name, Number defValue) {
    Object val = getO(name);
    return (val == null)? defValue : o2n(val);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // STRING ACCESSORS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Gets value with the given name as a string.
   *  This is a convenience method equal to {@code getS(name,null)} (preferred) *or*
   *  {@code getS(name,"")} (alternate). NeXtMidas 3.7 and earlier only documented the version
   *  with the empty string as the default although the most-used implementation (in {@link Table})
   *  used the null default. The default given in the {@link KeyableByType} class uses the null
   *  default.
   *  @param name The parameter name.
   *  @return The associated value or {@code null} such does not exist.
   *  @see #getS(String,String)
   */
  default String getS (String name) {
    return getS(name,null);
  }

  /** Gets value with the given name as a string.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   */
  default String getS (String name, String defValue) {
    Object val = getO(name);
    return (val == null)? defValue : val.toString();
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // STATE ACCESSORS
  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** Gets value with the given name as a boolean. This is identical to {@code getState(name,defValue)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code false} such does not exist.
   *  @see #getState(String,boolean)
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default boolean getZ (String name) {
    return getState(name,false);
  }

  /** Gets value with the given name as a boolean. This is identical to {@code getState(name,defValue)}.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default boolean getZ (String name, boolean defValue) {
    return getState(name,defValue);
  }

  /** Gets value with the given name as a boolean. This is identical to {@code getState(name,defValue)}.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Boolean getZ (String name, Boolean defValue) {
    return getState(name,defValue);
  }

  /** Gets value with the given name as a boolean.
   *  This is a convenience method equal to {@code getState(name,false)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code false} such does not exist.
   *  @see #getState(String,boolean)
   */
  default boolean getState (String name) {
    return getState(name,false);
  }

  /** Gets value with the given name as a boolean. For the purposes of this method the
   *  following define the states:
   *  <ul>
   *    <li>True States  = Anything that is true according to {@code StringUtil.isTrue(..)}
   *                       or a numerical value, {@code n} where {@code n&gt;0}.</li>
   *    <li>False States = Anything that is false according to {@code StringUtil.isFalse(..)}
   *                       or a numerical value, {@code n} where {@code n&lt;=0}.</li>
   *  </ul>
   *  Note that the argument is parsed twice for one of these conditions. Once without results
   *  substitution and then once with results substitution. This eliminates problems where a
   *  macro has one of these tokens as a results parameter.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @see nxm.sys.lib.StringUtil#isTrue(String)
   *  @see nxm.sys.lib.StringUtil#isFalse(String)
   */
  default boolean getState (String name, boolean defValue) {
    String str = getS(name,null);
    if (str == null)  return defValue;
    if (isTrue(str))  return true;
    if (isFalse(str)) return false;
    return (getD(name) > 0);
  }

  /** Gets value with the given name as a boolean. For the purposes of this method the
   *  following define the states:
   *  <ul>
   *    <li>True States  = Anything that is true according to {@code StringUtil.isTrue(..)}
   *                       or a numerical value, {@code n} where {@code n&gt;0}.</li>
   *    <li>False States = Anything that is false according to {@code StringUtil.isFalse(..)}
   *                       or a numerical value, {@code n} where {@code n&lt;=0}.</li>
   *  </ul>
   *  Note that the argument is parsed twice for one of these conditions. Once without results
   *  substitution and then once with results substitution. This eliminates problems where a
   *  macro has one of these tokens as a results parameter.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @see nxm.sys.lib.StringUtil#isTrue(String)
   *  @see nxm.sys.lib.StringUtil#isFalse(String)
   *  @since NeXtMidas 3.9.0
   */
  default Boolean getState (String name, Boolean defValue) {
    Object obj = getO(name);
    if (obj == null) return defValue;
    if (obj instanceof Boolean) return (Boolean)obj;
    if (obj instanceof Number) {
      return ((Number)obj).doubleValue() > 0;
    }
    String str = obj.toString();
    if (StringUtil.isEmpty(str)) return defValue;
    if (isTrue(str))  return TRUE;
    if (isFalse(str)) return FALSE;
    double dblValue = defValue ? getD(name,1) : getD(name,0);
    return (dblValue > 0);
  }

  /** Gets the inverse of the boolean state of a specified result/parameter.
   *  This is a convenience method equal to {@code getNoState(name,false)}.
   *  @param name The parameter name.
   *  @return The associated value or {@code false} such does not exist.
   *  @see #getState(String,boolean)
   */
  default boolean getNoState (String name) {
    return getNoState(name,false);
  }

  /** Gets the inverse of the boolean state of a specified result/parameter.
   *  This is a convenience method equal to {@code !getState(name,!defValue)}.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @see #getState(String,boolean)
   */
  default boolean getNoState (String name, boolean defValue) {
    return !getState(name,!defValue);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // OBJECT ACCESSORS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Gets value with the given name as an Object. <br>
   *  <br>
   *  Calling either {@code getO(name)} or {@code getO(name,null)} must produce the same results,
   *  but it is up to the implementation as to whether {@code getO(name)} depends on
   *  {@code getO(name,defValue)} or if {@code getO(name,defValue)} depends on {@code getO(name)},
   *  or if both have independent, but compatible, implementations. <br>
   *  <br>
   *  In the default implementation in {@link KeyableByType} has {@code getO(name)} calling
   *  {@code getO(name,null)} as this matches the pre-NeXtMidas 3.7 implementation in {@link Table}.
   *  It should not be be assumed that this is the optimal implementation. <br>
   *  <br>
   *  Note: Implementations may feel free to translate the input value to an appropriate object
   *        (of their choosing).
   *  @param name The parameter name.
   *  @return The associated value.
   *  @see #getO(String,Object)
   */
  default Object getO (String name) {
    return getO(name,null);
  }


  /** Gets value with the given name as a Java object. <br>
   *  <br>
   *  Calling either {@code getO(name)} or {@code getO(name,null)} must produce the same results,
   *  but it is up to the implementation as to whether {@code getO(name)} depends on
   *  {@code getO(name,defValue)} or if {@code getO(name,defValue)} depends on {@code getO(name)},
   *  or if both have independent, but compatible, implementations. <br>
   *  <br>
   *  In the default implementation in {@link KeyableByType} has {@code getO(name)} calling
   *  {@code getO(name,null)} as this matches the pre-NeXtMidas 3.7 implementation in {@link Table}.
   *  It should not be be assumed that this is the optimal implementation. <br>
   *  <br>
   *  Note: Implementations may feel free to translate the input value to an appropriate object
   *        (of their choosing).<br>
   *  <br>
   *  Note: Implementations that support null values, may return either {@code null} (preferred)
   *        or {@code defValue} (alternate) if the value is present and set to null.
   *  @param name     The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @see #getO(String)
   */
  Object getO (String name, Object defValue); // intentionally has no default implementation

  /** Gets the command line parameter Object and returns a List<?>
   *  @param key  The parameter name.
   *  @return The new List from Convert.o2List()
   *  @since NeXtMidas 3.7.1 for NXM-2945
   */
  //////////////////////////////////////////////////////////////////////////////////////////////////
  // COLLECTION ACCESSORS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Gets value with the given name as a List.
   *  @param <T>  The value type.
   *  @param key  The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0 (in {@link Table} since NeXtMidas 3.7.1, per NXM-2945)
   */
  default <T> List<T> getList (String key) {
    return getList(key,null);
  }

  /** Gets value with the given name as a List.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default <T> List<T> getList (String key, List<T> defValue) {
    return getList(key,defValue,null);
  }

  /** Gets value with the given name as a List.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @param flags    Flags related to the conversions.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @SuppressWarnings("unchecked")
  @InternalUseOnly
  default <T> List<T> getList (String key, List<T> defValue, Set<ConvertFlags> flags) {
    Collection<T> c = getCollection(key, defValue, +1, flags);
    return ((c == null) || (c instanceof List))? (List)c : new ArrayList<>(c);
  }

  /** Gets value with the given name as a List.
   *  @param <T>  The value type.
   *  @param key  The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0 (in {@link Table} since NeXtMidas 3.7.1, per NXM-2945)
   */
  @InternalUseOnly
  default <T> Collection<T> getCollection (String key) {
    return getCollection(key,null);
  }

  /** Gets value with the given name as a List.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default <T> Collection<T> getCollection (String key, Collection<T> defValue) {
    return getCollection(key, defValue, 0, null);
  }

  /** Gets value with the given name as a Set.
   *  @param <T>  The value type.
   *  @param key  The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0 (in {@link Table} since NeXtMidas 3.7.1, per NXM-2945)
   */
  @InternalUseOnly
  default <T> Set<T> getSet (String key) {
    return getSet(key,null);
  }

  /** Gets value with the given name as a Set.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  @SuppressWarnings("unchecked")
  default <T> Set<T> getSet (String key, Set<T> defValue) {
    return getSet(key,defValue,null);
  }

  /** Gets value with the given name as a Set.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @param flags    Flags related to the conversions.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  @SuppressWarnings("unchecked")
  default <T> Set<T> getSet (String key, Set<T> defValue, Set<ConvertFlags> flags) {
    Collection<T> c = getCollection(key, defValue, -1, flags);
    return ((c == null) || (c instanceof Set))? (Set)c : new HashSet<>(c);
  }

  /** Gets value with the given name as a SortedSet.
   *  @param <T>  The value type.
   *  @param key  The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0 (in {@link Table} since NeXtMidas 3.7.1, per NXM-2945)
   */
  @InternalUseOnly
  default <T> SortedSet<T> getSortedSet (String key) {
    return getSortedSet(key,null);
  }

  /** Gets value with the given name as a SortedSet.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default <T> SortedSet<T> getSortedSet (String key, SortedSet<T> defValue) {
    return getSortedSet(key,defValue,null);
  }

  /** Gets value with the given name as a SortedSet.
   *  @param <T>      The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @param flags    Flags related to the conversions.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  @SuppressWarnings("unchecked")
  default <T> SortedSet<T> getSortedSet (String key, SortedSet<T> defValue, Set<ConvertFlags> flags) {
    // Note: This uses "!(c instanceof ...)" due to the way null values are handled.
    Collection<T> c = getCollection(key, defValue, -2, flags);
    return ((c == null) || (c instanceof SortedSet))? (SortedSet)c : new TreeSet<>(c);
  }

  /** Gets value with the given name as a List.
   *  @param <T>        The value type.
   *  @param key        The parameter name.
   *  @param defValue   The default value for the parameter.
   *  @param listOrSet  Indicates a preference for a {@link List} (+N), a {@link Set} (-N), or no
   *                    preference (0). This value is merely a suggestion and intended to optimize
   *                    calls made on behalf of the {@code getList(..)} and {@code getSet(..)}
   *                    methods.
   *  @param flags      Flags related to the conversions.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  @SuppressWarnings("unchecked")
  default <T> Collection<T> getCollection (String key, Collection<T> defValue,
                                           int listOrSet, Set<ConvertFlags> flags) {
    Object val = getO(key);

    // ==== Try Basic Conversions ==================================================================
    if (val == null)               return defValue;
    if (val instanceof Collection) return (Collection<T>)val;
    if (val instanceof Data)       return (Collection<T>)((Data)val).toVector();
    if (val instanceof Object[])   return (Collection<T>)Arrays.asList((Object[])val);

    if (flags == null) {
      throw new RuntimeException("Could not convert "+val.getClass()+" to Collection: "+val);
    }

    // ==== Try Additional Conversions =============================================================
    if (flags.contains(ConvertFlags.SplitString)) {
      String str = val.toString().trim();
      if (str.isEmpty()) return (listOrSet > 0)? emptyList() : emptySortedSet();

      String[] vals = StringUtil.split(str);

      if (listOrSet >= 0) {
        return (Collection<T>)asList(vals);
      }
      else {
        Set<String> set = new TreeSet<>();
        addAll(set,vals);
        return (Collection<T>)set;
      }
    }

    if (flags.contains(ConvertFlags.SingletonToCollection)) {
      return (Collection<T>)((listOrSet >= 0)? singletonList(val) : singleton(val));
    }

    throw new RuntimeException("Could not convert "+val.getClass()+" to Collection: "+val);
  }

  /** Gets value with the given name as a Map.
   *  @param <K>  The key type.
   *  @param <V>  The value type.
   *  @param key  The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default <K,V> Map<K,V> getMap (String key) {
    return getMap(key,null);
  }

  /** Gets value with the given name as a Map.
   *  @param <K>  The key type.
   *  @param <V>  The value type.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  @SuppressWarnings("unchecked")
  default <K,V> Map<K,V> getMap (String key, Map<K,V> defValue) {
    Object val = getO(key);
    if (val == null) return defValue;
    return (val instanceof Map)? (Map)val : (Map)o2t(val);
  }

  /** Gets value with the given name as a MapKBT.
   *  @param key  The parameter name.
   *  @return The associated value or null such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default MapKBT getMapKBT (String key) {
    return getMapKBT(key,null);
  }

  /** Gets value with the given name as a MapKBT.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default MapKBT getMapKBT (String key, Map<String,Object> defValue) {
    return MapKBT.wrapMap(getMap(key, defValue));
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // MISC. ACCESSORS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Gets value with the given name as a Time.
   *  @param key  The parameter name.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Time getTime (String key) {
    return getTime(key, null);
  }

  /** Gets value with the given name as a Time.
   *  @param key      The parameter name.
   *  @param defValue The default value for the parameter.
   *  @return The associated value or {@code defValue} such does not exist.
   *  @since NeXtMidas 3.9.0
   */
  @InternalUseOnly
  default Time getTime (String key, Time defValue) {
    Object val = getO(key);
    return (val == null)? defValue : toTime(val);
  }

  /** Conversion flags. */
  @InternalUseOnly
  public static enum ConvertFlags {
    /** Indicates that a singleton should be converted to a collection. */
    SingletonToCollection,
    /** Indicates that a string should be split using {@link StringUtil#split(String)} to form a
     *  {@link List} or {@link Set}. This takes priority over {@link #SingletonToCollection}.
     */
    SplitString;
  }
}
