/*
 * Copyright (c) 2004-2017, by Alex Leigh.
 * All rights reserved.
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE
 * The copyright notice above does not evidence any
 * actual or intended publication of such source code.
 */

package lc.mecha.lang;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * This is a reference implementation of the <code>DynamicObject</code>
 * interface. It should be suitable in almost every usage case.
 * <p>
 * For accessing dynamic objects, this implementation favors getter/setter
 * methods, fields, and then the unbound key interface, in that order. In other
 * words, to set a given value it will first search for a matching setter, then
 * see if it can access the field directly, and finally it will call the unbound
 * setter.
 *
 * @author Alex Leigh
 */
public class SimpleDynamicObject implements DynamicObject {
    private final static Logger logger = LoggerFactory.getLogger(SimpleDynamicObject.class);
    private final HashMap<String, Object> unboundValues = new HashMap<>();

    /**
     * Get the given value in this object. The method will first search for a
     * matching getter, then attempt to set the property directly (if a field),
     * and finally will call <code>unbound()</code>.
     */
    public Object get(final String property) {
        final Object o = Introspector.getValueInObject(this, property);
        logger.info("Object found for property: " + o);
        return o;
    }

    /**
     * Return all the property names that are accessible using the
     * <code>get()</code> method. This includes properties that are bound
     * (have fields or getters) and unbound (are fully dynamic).
     */
    // TODO: write a JUnit test-case for this, since there was a bug
    public Set<String> propertyNames() {
        final HashSet<String> bar = new HashSet<>();
        bar.addAll(unboundValues.keySet());
        bar.addAll(Introspector.getProperties(this).keySet());
        logger.info("Property names are: " + bar);
        return bar;
    }

    public Set<String> propertyStringNamesWithValues() {
        final Set<String> foo = new HashSet<>();
        for (final String s : propertyNames()) {
            logger.info("Evaluating property: " + s);
            if (get(s) != null)
                foo.add(s);
        }
        return foo;
    }

    /**
     * Get a value that is unbound in this object (a fully dynamic value). This
     * implementation stores unbound values in a <code>HashMap</code>. You
     * should generally be calling <code>get()</code> instead.
     */
    public Object unbound(final String property) {
        return unboundValues.get(property);
    }

    /**
     * Set all the provided values in this object. The method operates by
     * calling the <code>set()</code> method for each provided name/value
     * tuple.
     */
    public void setAll(final Map<String, Object> map) {
        for (final String key : map.keySet()) {
            set(key, map.get(key));
        }
    }

    /**
     * Set the given value in this object. The method will first search for a
     * matching setter, then attempt to set the property directly (if a field),
     * and finally will call <code>setUnbound()</code>.
     */
    public void set(final String property, final Object value) {
        Introspector.setValueInObject(this, property, value);
    }

    /**
     * Set a value that is unbound on this object (a fully dynamic value). This
     * implementation stores unbound values in a <code>HashMap</code>. You
     * should generally be calling <code>set()</code> instead.
     */
    public void setUnbound(final String property, final Object value) {
        unboundValues.put(property, value);
    }

    @Override
    public String toString() {
        return "SimpleDynamicObject{" +
                "unboundValues=" + unboundValues +
                '}';
    }
}
