package org.bdware.sc.server;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.doip.audit.EndpointConfig;
import org.bdware.doip.audit.EndpointInfo;
import org.bdware.doip.audit.client.AuditIrpClient;
import org.bdware.doip.endpoint.server.DoipListenerConfig;
import org.bdware.doip.endpoint.server.DoipServerImpl;
import org.bdware.doip.endpoint.server.DoipServiceInfo;
import org.bdware.irp.exception.IrpClientException;
import org.bdware.irp.stateinfo.StateInfoBase;
import org.bdware.sc.ContractProcess;
import org.bdware.sc.bean.Contract;
import org.bdware.sc.bean.RouteInfo;
import org.bdware.sc.handler.DOOPRequestHandler;
import org.bdware.sc.node.ContractNode;
import org.bdware.sc.node.FunctionNode;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.bdware.doip.audit.EndpointConfig.defaultDOIPServerPort;
import static org.bdware.doip.audit.EndpointConfig.defaultRepoType;

public class DoipClusterServer extends DoipServerImpl {
    static EndpointConfig config;
    static AuditIrpClient repoIrpClient;
    static DoipClusterServer instance;
    // LOGGER
    private static final Logger LOGGER = LogManager.getLogger(ContractProcess.class);

    public DoipClusterServer(EndpointConfig config) {
        super(resolveInfo(config));
        DoipClusterServer.config = config;
    }

    public static void createDOOPServerInstance(EndpointConfig config) {
        instance = new DoipClusterServer(config);
    }

    public static DoipClusterServer getDOOPServerInstance() {
        return instance;
    }

    public static void startDoipServer(String arg) throws InterruptedException {
        final String doipAddr = arg;

        Thread doipServerThread = new Thread(){
            @Override
            public void run() {
                try {
                    DoipLocalSingleton.run(doipAddr);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        doipServerThread.start();
    }

    private static DoipServiceInfo resolveInfo(EndpointConfig config) {
        repoIrpClient = new AuditIrpClient(config);
        EndpointInfo info = repoIrpClient.getEndpointInfo();
        if (info == null) {
            String content = "{\"date\":\"2022-1-13\",\"name\":\"testrepoforaibd\",\"doId\":\"bdware.test.local/Repo\",\"address\":\"tcp://127.0.0.1:" + defaultDOIPServerPort + "\",\"status\":\"已审核\",\"protocol\":\"DOIP\",\"pubKey\":\"empty\",\"version\":\"2.1\"}";
            info = EndpointInfo.fromJson(content);
        }
        List<DoipListenerConfig> infos = new ArrayList<>();
        try {
            infos.add(new DoipListenerConfig(info.getURI(), info.getVersion()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        DoipServiceInfo ret = new DoipServiceInfo(info.getDoId(), info.getPubKey(), defaultRepoType, infos);
        return ret;
    }

    public void updateRepoInfo(Contract contract, ContractNode cn) throws IrpClientException {
        String repoIdentifier = "bdtest/" + config.repoName;
        StateInfoBase repoInfo = new StateInfoBase();

        repoInfo.identifier = repoIdentifier;
        repoInfo.handleValues = new JsonObject();
        
        JsonObject repoHandleValues = new JsonObject();

        JsonObject createParams = contract.getCreateParam().getAsJsonObject();
        // 放置集群信息
        JsonElement clusterInfo = createParams.get("clusterInfo");

        // doipOperationName和对应的routeFunctionName的对应关系，存储方式为doipOperationName: routeFunctionName
        JsonObject methodRouteInfoMap = new JsonObject();
        // 所有Router中用得到的函数（例如Route函数和Route函数依赖的相关函数）
        JsonObject functions = new JsonObject();

        // 维护RouteInfo，将RouteInfo和doipOperationName的映射关系，以及所有Router中用得到的函数都维护好
        maintainRouteInfo(cn, methodRouteInfoMap, functions);

        if(clusterInfo != null) repoHandleValues.add("clusterInfo", clusterInfo);
        if(!functions.equals(new JsonObject())) repoHandleValues.add("functions", functions);
        if(!methodRouteInfoMap.equals(new JsonObject())) repoHandleValues.add("routeInfo", methodRouteInfoMap);
        repoInfo.handleValues.addProperty("cluster", repoHandleValues.toString());

        String updateRepoInfoRes = repoIrpClient.reRegister(repoInfo);
        if (updateRepoInfoRes.equals("success")) {
            LOGGER.info("Update cluster info to router successfully");
        } else if (updateRepoInfoRes.equals("failed")) {
            LOGGER.error("Failed to update cluster info to router");
        } else {
            LOGGER.warn("Oops...The result of updating clusterInfo to the router is " + updateRepoInfoRes);
        }
    }

    public void maintainRouteInfo(ContractNode cn, JsonObject methodRouteInfoMap, JsonObject functions) {
        // all functions存了ContractNode中，所有的FunctionNode
        List<FunctionNode> allFunctions = cn.getFunctions();
        // gson是Gson工具类昂！！！
        Gson gson = new Gson();

        // 遍历所有的 doipOperationName 和其对应的 doipFunctionNode
        for (Map.Entry<String, FunctionNode> doipFunctionNodes : DOOPRequestHandler.instance.doipFunctionNodeMap.entrySet()) {
            String doipOperationName = doipFunctionNodes.getKey();
            FunctionNode doipFunctionNode = doipFunctionNodes.getValue();
            RouteInfo doipFunctionRouteInfo = doipFunctionNode.getRouteInfo();

            // 对于routerInfo进行处理，对于routeInfo进行维护
            if(doipFunctionRouteInfo != null) {
                methodRouteInfoMap.addProperty(doipOperationName, gson.toJson(doipFunctionRouteInfo));
                if(doipFunctionRouteInfo.funcName != null) {
                    String routeFunctionName = doipFunctionRouteInfo.funcName;
                    for(FunctionNode functionNode : allFunctions) {
                        // find Route Function
                        if(functionNode.functionName.equals(routeFunctionName)) {
                            functions.addProperty(functionNode.functionName, functionNode.plainText());

                            // add all dependentFunction to the "functions" struct
                            for (String dependentFunctionName : functionNode.getDependentFunctions()) {
                                for (FunctionNode f : cn.getFunctions()) {
                                    if(f.functionName.equals(dependentFunctionName) && !functions.has(dependentFunctionName)) {
                                        functions.addProperty(dependentFunctionName, f.plainText());
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
