package org.bdware.sc.controlproxy;

import com.google.gson.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.client.SmartContractHttpClient;
import org.bdware.sc.ContractResult;
import org.bdware.sc.boundry.JavaScriptEntry;
import org.bdware.sc.engine.JSONTool;
import org.bdware.sc.util.JsonUtil;
import wrp.jdk.nashorn.api.scripting.ScriptObjectMirror;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TopologyCollector {

    static Logger LOGGER = LogManager.getLogger(TopologyCollector.class);


    public static List<JsonObject> getCurrentTopology() {
        try {
            TopologyGraph graph = new TopologyGraph();
            //通过SearchEngine，可拿到本级的Repo（或下组的SE)与SearchEngine的关系。
            //通过Router，可拿到本级的Repo列表，也可拿到下组Router列表。
            JsonPrimitive arg = new JsonPrimitive("a");
            ContractResult currentRouters = executeContract(RepoProxy.router, "getRouterInfo", arg);
            //--------构建Router网
            JsonElement midResult = currentRouters.result.getAsJsonObject().get("data");
            if (midResult.isJsonPrimitive())
                midResult = JsonParser.parseString(midResult.getAsString()).getAsJsonObject();
            JsonObject root = midResult.getAsJsonObject();
            JsonObject rootNode = new JsonObject();
            rootNode.add("name", root.get("name"));
            rootNode.add("doId", root.get("doId"));
            rootNode.addProperty("type", "Router");
            rootNode.add("previousNode", new JsonArray());
            graph.addNode(rootNode);
            ContractResult nextRouters = executeContract(RepoProxy.router, "listLRS", arg);
            JsonArray routersArray = nextRouters.result.getAsJsonObject().get("data").getAsJsonArray();
            for (JsonElement je : routersArray) {
                JsonObject node = new JsonObject();
                JsonObject from = je.getAsJsonObject();
                if (!from.get("doId").getAsString().startsWith(rootNode.get("doId").getAsString())) {
                    continue;
                }
                node.add("name", from.get("name"));
                node.add("doId", from.get("doId"));
                node.addProperty("type", "Router");
                graph.addNode(node);
                graph.addEdge(node.get("doId").getAsString(), root.get("doId").getAsString());
            }
            //--------Router网构建完毕
            //--------构建SearchEngine网-----

            JsonObject searchEngine = new JsonObject();
            searchEngine.addProperty("doId", root.get("doId").getAsString() + "/Registry");
            searchEngine.addProperty("type", "Registry");
            searchEngine.addProperty("name", root.get("name").getAsString() + "搜索引擎");
            graph.addNode(searchEngine);
            //下级SearchEngine同步至上级
            for (JsonElement je : routersArray) {
                JsonObject node = new JsonObject();
                JsonObject from = je.getAsJsonObject();
                if (!from.get("doId").getAsString().startsWith(rootNode.get("doId").getAsString())) {
                    continue;
                }
                node.addProperty("doId", from.get("doId").getAsString() + "/Registry");
                node.addProperty("type", "Registry");
                node.addProperty("name", from.get("name").getAsString() + "搜索引擎");
                graph.addNode(node);
                graph.addEdge(node.get("doId").getAsString(), searchEngine.get("doId").getAsString());
            }

            JsonObject gateway = new JsonObject();
            gateway.addProperty("doId", root.get("doId").getAsString() + "/GateWay");
            gateway.addProperty("type", "GateWay");
            gateway.addProperty("name", root.get("name").getAsString() + "网关");
            graph.addNode(gateway);
            //TODO 通过SearchEngine把Repo的关系给弄上
            //-----------------
            ContractResult currentRepos = executeContract(RepoProxy.router, "listRepo", arg);
            JsonArray repos = currentRepos.result.getAsJsonObject().get("data").getAsJsonArray();
            for (JsonElement je : repos) {
                JsonObject from = je.getAsJsonObject();
                String name = from.get("name").getAsString();
                if (name.equals("AuditProxy") || name.equals("ControlProxy"))
                    continue;
                if (name.equals("Registry") || name.equals("SearchEngine") || name.equals("GateWay"))
                    continue;

                JsonObject node = new JsonObject();
                node.add("name", from.get("name"));
                node.add("doId", from.get("doId"));
                node.addProperty("type", "Repo");
                graph.addNode(node);
                graph.addEdge(node.get("doId").getAsString(), searchEngine.get("doId").getAsString());
                graph.addEdge(node.get("doId").getAsString(), gateway.get("doId").getAsString());
            }
            graph.pruneEdges();
            return graph.ret;
        } catch (Exception e) {
            List<JsonObject> ret = new ArrayList<>();
            JsonObject errorInfo = new JsonObject();
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            e.printStackTrace(new PrintStream(bo));
            errorInfo.addProperty("stacktrace", bo.toString());
            return ret;
        }
    }

    private static ContractResult executeContract(String contractName, String operation, JsonElement arg) {
        Object jsobj = JavaScriptEntry.executeContract(contractName, operation, arg);
        Object som = ScriptObjectMirror.wrap(jsobj, JavaScriptEntry.getEngineGlobal());
        JsonElement je = JSONTool.convertMirrorToJson(som);
        LOGGER.info(je.toString());
        ContractResult contractResult = new Gson().fromJson(je, ContractResult.class);
        return contractResult;
    }

    //{
    //       "name":"小店区仓库","doId":"bdware.ss.ab/Repo1","type":"Repo","previousNode":["bdware.ss/Gateway1","bdware.ss/Gateway2","bdware.ss.ab/Repo2","bdware.ss.zz/SearchEngine"]}
    //
    public static List<JsonObject> getLevelTopologyByDoId(String componentDoid, int level) {
        TopologyGraph graph = new TopologyGraph();
        graph.addGraph(getLevelTopology(2));
        graph.grepByDoId(componentDoid);
        return graph.ret;
    }

    static long lastUpdate = 0;
    static TopologyGraph cachedGraph;

    public static boolean isCacheValid() {
        if (cachedGraph != null) return true;
        if (System.currentTimeMillis() - lastUpdate < 30000) {
            return true;
        }
        return false;
    }

    static StringBuilder debugInfo = new StringBuilder();

    public static String getDebugInfo() {
        return debugInfo.toString();
    }

    public static String resetDebugInfo() {
        debugInfo = new StringBuilder();
        return "done";
    }

    public void reflushCache() {

    }

    public static List<JsonObject> getLevelTopology(int arg) {
        if (isCacheValid()) {
            LOGGER.info("getLevelTopology use Cache!");
            reflushCacheAsync(arg);
            return cachedGraph.ret;
        }
        reflushCacheSync(arg);
        return cachedGraph.ret;
    }

    static ExecutorService pool = Executors.newFixedThreadPool(2);

    private static void reflushCacheAsync(int arg) {
        pool.execute(new Runnable() {
            @Override
            public void run() {
                reflushCacheSync(2);
            }
        });
    }

    private synchronized static void reflushCacheSync(int arg) {
        LOGGER.info("getLevelTopology refresh");
        List<JsonObject> points = getCurrentTopology();
        TopologyGraph graph = new TopologyGraph();
        graph.addGraph(points);
        if (arg > 0) {
            ContractResult nextRouters = executeContract(RepoProxy.router, "listLRS", new JsonPrimitive("a"));
            JsonArray routersArray = nextRouters.result.getAsJsonObject().get("data").getAsJsonArray();
            for (JsonElement je : routersArray) {
                if (!je.getAsJsonObject().get("doId").getAsString().startsWith(points.get(0).get("doId").getAsString())) {
                    continue;
                }
                try {
                    String nextControlProxy = je.getAsJsonObject().get("doId").getAsString() + "/ControlProxy";
                    ContractResult cr = executeContract(RepoProxy.router, "resolveTest", new JsonPrimitive(nextControlProxy));
                    URI uri = null;
                    uri = new URI(JsonUtil.parseStringAsJsonObject(cr.result.getAsString()).get("address").getAsString());
                    SmartContractHttpClient client = new SmartContractHttpClient(JavaScriptEntry.getKeyPair(), uri.getHost(), uri.getPort());
                    String url = client.getExecuteUrl("ControlProxy", "getLevelTopology", (arg - 1) + "");
                    LOGGER.info(url);
                    JsonObject result = client.executeContract("ControlProxy", "getLevelTopology", (arg - 1) + "");
                    graph.addGraph(result.get("result").getAsJsonArray());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        lastUpdate = System.currentTimeMillis();
        cachedGraph = graph;
    }


}
