package lc.esp.eo.json;

import lc.esp.eo.sdk.EO;
import lc.esp.eo.sdk.EOId;
import lc.esp.eo.sdk.EOLoop;
import lc.esp.eo.sdk.EOStringId;
import lc.mecha.json.JSONArray;
import lc.mecha.json.JSONException;
import lc.mecha.json.JSONObject;
import lc.mecha.log.MechaLogger;
import lc.mecha.log.MechaLoggerFactory;

import java.io.Serializable;
import java.util.Map;

public class EOJsonSerializer {
    public static final String TYPE_KEY = "_type";
    public static final String ID_KEY = "_id";
    public static final String UNDERSCORE = "_";

    private static final MechaLogger logger = MechaLoggerFactory.getLogger(EOJsonSerializer.class);

    /**
     * Deserialize a JSON object into an {@link EO}.
     */
    public static EO toEo(JSONObject object) {
        // Believe it or not - his implementation is so terrible it will throw if
        // the key is not found in the object.

        EOId objId;

        if (object.has(ID_KEY)) {
            objId = new EOStringId(object.getString(ID_KEY));
        } else {
            objId = null;
        }

        EO eo;
        try {
            eo = new EO(object.getString(TYPE_KEY), objId);
        } catch (JSONException e) {
            eo = new EO(objId);
        }

        for (final String key : object.keySet()) {
            // JSONObject will provide strings as such, and booleans as Boolean
            if (!key.startsWith(UNDERSCORE) && !object.isNull(key)) {
                final Object val = object.opt(key);
                if (val instanceof JSONArray) {
                    EOLoop loop = toEoLoop((JSONArray) val);
                    eo.setValue(key, loop);
                    // goodness
                } else {
                    // We do this in case it actually comes out as a JSONObject due to a bad serialization
                    Object o = object.opt(key);
                    if (o instanceof Serializable) {
                        eo.setValue(key, (Serializable) object.opt(key));
                    } else {
                        logger.warn("Could not deserialize JSON value. Dropping that value. [key: {}] [val: {} {}]",
                                key, o.getClass(), o);
                        throw new IllegalArgumentException("Could not deserialize JSON");
                    }
                }
            }
        }

        return eo;
    }

    public static EOLoop toEoLoop(JSONArray val) {
        EOLoop loop = new EOLoop();
        for (int i = 0; i < val.length(); i++) {
            try {
                loop.add(EOJsonSerializer.toEo(val.getJSONObject(i)));
            } catch (JSONException e) {
                logger.warn("JSON was illegal. {}", e);
            }
        }
        return loop;
    }

    /**
     * Returns this EO in the form of a JSON graph. The graph is compatible with the <code>JSONObject</code>
     * constructor.
     */
    public static JSONObject toJson(EO eo) throws JSONException {
        final JSONObject obj = new JSONObject();
        // Store the data keys in JSON.
        if (!eo.getKeys().isEmpty()) {
            for (final Map.Entry<String, Object> entry : eo.getData().entrySet()) {
                if (entry.getKey().startsWith(UNDERSCORE)) continue;
                if (entry.getValue() instanceof EOLoop) {
                    final JSONArray array = new JSONArray();
                    obj.put(entry.getKey(), array);
                    for (final EO value : (EOLoop) entry.getValue()) {
                        array.put(EOJsonSerializer.toJson(value));
                    }
                } else {
                    obj.put(entry.getKey(), entry.getValue());
                }
            }
        }

        obj.put(TYPE_KEY, eo.getType());
        obj.put(ID_KEY, eo.getId().toString());

        return obj;
    }
}
