package org.bdware.sc.memory;


import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.bdware.sc.boundry.JavaScriptEntry;
import org.bdware.sc.util.JsonUtil;
import wrp.jdk.nashorn.api.scripting.ScriptObjectMirror;
import wrp.jdk.nashorn.internal.objects.Global;
import wrp.jdk.nashorn.internal.runtime.Context;
import wrp.jdk.nashorn.internal.runtime.PropertyMap;
import wrp.jdk.nashorn.internal.scripts.JO;

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


public class MemoryDump implements Serializable {
    transient long id;
    transient Map<Object, Long> allocated;  //js对象，id

    Map<Long, MemoryObject> objects;  //id,memory对象
    JSEDump jseDump;

    transient Map<Long, Object> recreate;

    public MemoryDump() {
        objects = new HashMap<>();
        allocated = new HashMap<>();
        id = 0;
        getRoot();
    }

    public static MemoryDump loadFromStr(String memDump) {
        JsonObject map = JsonUtil.parseStringAsJsonObject(memDump);
        MemoryDump ret = new MemoryDump();
        for (Entry<String, JsonElement> entry : map.entrySet()) {
            long id = Long.parseLong(entry.getKey());
            JsonObject obj = entry.getValue().getAsJsonObject();
            MOType type = MOType.valueOf(obj.get("type").getAsString());
            MemoryObject mo = null;

            switch (type) {
                case JSObject:
                case JSArray:
                    mo = JsonUtil.fromJson(obj, MemoryJSObject.class);
                    break;
                case JSFunction:
                    mo = JsonUtil.fromJson(obj, MemoryFunctionObject.class);
                    break;
                case Int:
                    mo = JsonUtil.fromJson(obj, MemoryObject.class);
                    mo.data = Integer.parseInt(obj.get("data").getAsString());
                    break;
                case Boolean:
                    mo = JsonUtil.fromJson(obj, MemoryObject.class);
                    mo.data = Boolean.parseBoolean(obj.get("data").getAsString());
                    break;
                case String:
                case Double:
                    mo = JsonUtil.fromJson(obj, MemoryObject.class);
                    break;
                default:
                    System.out.println("[MemoryDump] todo, missing type:" + type);
                    break;
            }
            ret.objects.put(id, mo);
        }

        return ret;
    }

    public Map<Long, MemoryObject> getObjects() {
        return objects;
    }

    public void setObjects(Map<Long, MemoryObject> m) {
        this.objects = m;
    }

    public MemoryJSObject getRoot() {
        if (objects.containsKey(0L))
            return (MemoryJSObject) objects.get(0L);
        else {
            MemoryJSObject jo = new MemoryJSObject(0);
            objects.put((long) 0, jo);
            return jo;
        }
    }

    public long allocate(Object obj) {
        if (obj == null)
            return -1;

        long currID;

        id++;
        currID = id;
        if (obj.getClass() == jdk.internal.dynalink.beans.StaticClass.class) {
			/*
			String obj2 = "jdk.internal.dynalink.beans.StaticClass.class";
			if (allocated.containsKey(obj2))
				return allocated.get(obj2);
			allocated.put(obj2, currID);
			 */
        } else {
            if (allocated.containsKey(obj))
                return allocated.get(obj);
            allocated.put(obj, currID);
        }


        // 如果是对象
        if (obj.getClass() == ScriptObjectMirror.class) {
            ScriptObjectMirror som = (ScriptObjectMirror) obj;
            if (som.isFunction()) {
                MemoryFunctionObject fo = new MemoryFunctionObject(currID);
                objects.put(currID, fo);
                for (String str : som.getOwnKeys(true)) {
                    fo.addField(str, allocate(som.getMember(str)));
                }
            } else if (som.isArray()) {
                MemoryArrayObject ao = new MemoryArrayObject(currID);
                objects.put(currID, ao);
                for (String str : som.getOwnKeys(true)) {
                    ao.addField(str, allocate(som.getMember(str)));
                }
            } else {
                MemoryJSObject jo = new MemoryJSObject(currID);
                objects.put(currID, jo);
                for (String str : som.getOwnKeys(true)) {
                    jo.addField(str, allocate(som.getMember(str)));
                }
            }
        } else if (obj.getClass() == wrp.jdk.nashorn.internal.runtime.Undefined.class) {

        } else if (obj.getClass() == jdk.internal.dynalink.beans.StaticClass.class) {
            //regard as String

//			MemoryObject mo = new MemoryObject(currID);
//			mo.type = MOType.String;
//			mo.data = "jdk.internal.dynalink.beans.StaticClass";
//			objects.put(currID, mo);
        } else {
            MOType type = MOType.getType(obj);
            if (type.isPrimitive()) {
                MemoryObject mo = new MemoryObject(currID);
                mo.type = type;
                mo.data = obj;
                objects.put(currID, mo);
            } else
                System.out.println("[MemoryDump] Allocat MetType:" + obj.getClass() + " now id=" + currID);

        }
        return currID;
    }

    public Map<Long, Object> recreateObject() {
        recreate = new HashMap<>();
        fillRecreate();
        fillJO();
        return recreate;
    }

    private void fillJO() {
        for (Long key : objects.keySet()) {
            MemoryObject mo = objects.get(key);
            if (mo == null)
                continue;

            switch (mo.type) {
                case JSObject:
                case JSArray:
                    MemoryJSObject mjo = (MemoryJSObject) mo;
                    ScriptObjectMirror jo = (ScriptObjectMirror) recreate.get(key);
                    for (String field : mjo.fields.keySet()) {
                        Object temp = recreate.get(mjo.fields.get(field));

                        if (mjo.fields.get(field) >= 0) {
                            if (field.length() > 0)
                                jo.setMember(field, temp);
                            else
                                jo.setMember(" ", temp);
                        }
                    }
                    break;
                default:
                    break;
            }
        }

    }

    private void fillRecreate() {
        Context.setGlobal(JavaScriptEntry.getEngineGlobal());
        for (Long key : objects.keySet()) {
            Object obj = null;
            MemoryObject mo = objects.get(key);
            if (mo == null) {
                continue;
            }
            switch (mo.type) {
                case JSArray:
                    obj = ScriptObjectMirror.wrap(Global.allocate(new int[0]), JavaScriptEntry.getEngineGlobal());
                    break;
                case JSObject:
                    obj = ScriptObjectMirror.wrap(new JO(PropertyMap.newMap()), JavaScriptEntry.getEngineGlobal());
                    break;
                case JSFunction:
                    break;
                case String:
                case Int:
                    obj = mo.data;
                    break;
                case Double:
                    obj = Double.parseDouble(mo.data.toString());
                    break;
                default:
                    System.out.println("[MemoryDump] todo, missing type:" + mo.type);
                    break;
            }

            recreate.put(key, obj);
        }
    }
}