/*
 * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
 * 
 * Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
 * for details.
 */
package json.ext;

import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyHash;
import org.jruby.RubyNumeric;
import org.jruby.RubyString;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

final class OptionsReader {
    private final ThreadContext context;
    private final Ruby runtime;
    private final RubyHash opts;
    private RuntimeInfo info;

    OptionsReader(ThreadContext context, IRubyObject vOpts) {
        this.context = context;
        this.runtime = context.getRuntime();

        if (vOpts == null || vOpts.isNil()) {
            opts = null;
        } else if (vOpts.respondsTo("to_hash")) {
            opts = vOpts.convertToHash();
        } else {
            opts = vOpts.callMethod(context, "to_h").convertToHash();
        }
    }

    private RuntimeInfo getRuntimeInfo() {
        if (info != null) return info;
        info = RuntimeInfo.forRuntime(runtime);
        return info;
    }

    /**
     * Efficiently looks up items with a {@link RubySymbol Symbol} key
     * @param key The Symbol name to look up for
     * @return The item in the {@link RubyHash Hash}, or <code>null</code>
     *         if not found
     */
    IRubyObject get(String key) {
        return opts == null ? null : opts.fastARef(runtime.newSymbol(key));
    }

    boolean getBool(String key, boolean defaultValue) {
        IRubyObject value = get(key);
        return value == null ? defaultValue : value.isTrue();
    }

    int getInt(String key, int defaultValue) {
        IRubyObject value = get(key);
        if (value == null) return defaultValue;
        if (!value.isTrue()) return 0;
        return RubyNumeric.fix2int(value);
    }

    /**
     * Reads the setting from the options hash. If no entry is set for this
     * key or if it evaluates to <code>false</code>, returns null; attempts to
     * coerce the value to {@link RubyString String} otherwise.
     * @param key The Symbol name to look up for
     * @return <code>null</code> if the key is not in the Hash or if
     *         its value evaluates to <code>false</code>
     * @throws RaiseException <code>TypeError</code> if the value does not
     *                        evaluate to <code>false</code> and can't be
     *                        converted to string
     */
    ByteList getString(String key) {
        RubyString str = getString(key, null);
        return str == null ? null : str.getByteList().dup();
    }

    RubyString getString(String key, RubyString defaultValue) {
        IRubyObject value = get(key);
        if (value == null || !value.isTrue()) return defaultValue;

        RubyString str = value.convertToString();
        RuntimeInfo info = getRuntimeInfo();
        if (info.encodingsSupported() && str.encoding(context) != info.utf8) {
            str = (RubyString)str.encode(context, info.utf8);
        }
        return str;
    }

    /**
     * Reads the setting from the options hash. If it is <code>nil</code> or
     * undefined, returns the default value given.
     * If not, ensures it is a RubyClass instance and shares the same
     * allocator as the default value (i.e. for the basic types which have
     * their specific allocators, this ensures the passed value is
     * a subclass of them).
     */
    RubyClass getClass(String key, RubyClass defaultValue) {
        IRubyObject value = get(key);

        if (value == null || value.isNil()) return defaultValue;

        if (value instanceof RubyClass &&
                ((RubyClass)value).getAllocator() == defaultValue.getAllocator()) {
            return (RubyClass)value;
        }
        throw runtime.newTypeError(key + " option must be a subclass of "
                                   + defaultValue);
    }

    public RubyHash getHash(String key) {
        IRubyObject value = get(key);
        if (value == null || value.isNil()) return new RubyHash(runtime);
        return (RubyHash) value;
    }
}
