package leigh.eo;

import leigh.mecha.json.JSONArray;
import leigh.mecha.json.JSONException;
import leigh.mecha.json.JSONObject;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;

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

public class EOJsonSerializer {
    public static final String TYPE_KEY = "_type";
    public static final String META_KEY = "_meta";
    public static final String UNDERSCORE = "_";
    public static final String META_VALUE = "value";
    public static final String META_ISWEAK = "isWeak";

    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.
        EO eo;
        try {
            eo = new EO(object.getString(TYPE_KEY));
        } catch (JSONException e) {
            eo = new EO();
        }

        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. [val: {}]", o);
                    }
                }
            }
        }

        JSONObject metaRoot = object.optJSONObject(META_KEY);
        if (metaRoot != null) {
            for (String apiKey : metaRoot.keySet()) {
                JSONObject metaArray = metaRoot.getJSONObject(apiKey);
                logger.info("meta: {}", metaArray);
                for (String metaType : metaArray.keySet()) {
                    JSONObject metaParam = metaArray.getJSONObject(metaType);
                    String metaValue = metaParam.getString(META_VALUE);
                    boolean metaIsWeak = metaParam.getBoolean(META_ISWEAK);
                    eo.setMetaValue(apiKey, metaType, metaValue, metaIsWeak);
                }
            }
        }

        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().size() > 0) {
            for (final Map.Entry<String, Serializable> 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.getApiType());

        if (eo.getMeta()!= null) {
            JSONObject metaObj = new JSONObject();
            for (Map.Entry<String, HashMap<String, Serializable>> metaAttr : eo.getMeta().entrySet()) {
                JSONObject metaAttrJson = new JSONObject();
                metaObj.put(metaAttr.getKey(), metaAttrJson);
                    for (Map.Entry<String, Serializable> metaValue : metaAttr.getValue().entrySet()) {
                        JSONObject metaValueJson = new JSONObject();
                        String type = metaValue.getKey();

                        // This relies on the premise that the label will ALWAYS be prefixed in the EO.
                        // So this is tightly coupled with EO.java

                        if(type.startsWith("_")) {
                            metaValueJson.put(META_ISWEAK, true);
                        } else if (type.startsWith("#")) {
                            metaValueJson.put(META_ISWEAK, false);
                        }
                        metaValueJson.put(META_VALUE, metaValue.getValue());
                        metaAttrJson.put(type.substring(1), metaValueJson);
                    }
            }
            if (metaObj.keySet().size() > 0) {
                obj.put(META_KEY, metaObj);
            }
        }

        return obj;
    }
}
