package org.bdware.sc.handler;

import com.google.gson.JsonObject;
import io.netty.channel.ChannelHandlerContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.doip.codec.JsonDoipMessage;
import org.bdware.doip.codec.doipMessage.DoipMessage;
import org.bdware.doip.codec.doipMessage.DoipMessageFactory;
import org.bdware.doip.codec.doipMessage.DoipResponseCode;
import org.bdware.doip.codec.doipMessage.HeaderParameter;
import org.bdware.doip.codec.operations.BasicOperations;
import org.bdware.doip.endpoint.server.DoipRequestHandler;
import org.bdware.doip.endpoint.server.NettyServerHandler;
import org.bdware.sc.ContractProcess;
import org.bdware.sc.bean.ContractRequest;
import org.bdware.sc.boundry.JavaScriptEntry;
import org.bdware.sc.crdt.SharableVarManager;
import org.bdware.sc.entity.DoipMessagePacker;
import org.bdware.sc.node.FunctionNode;
import org.bdware.sc.util.JsonUtil;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

public class DOOPRequestHandler implements DoipRequestHandler {
    static Logger logger = LogManager.getLogger(NettyServerHandler.class);
    public Map<String, FunctionNode> doipFunctionNodeMap;

    public DOOPRequestHandler() {
        doipFunctionNodeMap = new HashMap<>();
    }

    public void addDoipOperation(FunctionNode function) throws Exception {
        String operationName = function.getDoipOperationInfo().operationName;
        if (doipFunctionNodeMap.containsKey(operationName)) {
            throw new Exception("Contract is wrong: One DO operation maps multiple functions");
        }
        doipFunctionNodeMap.put(operationName, function);
    }

    @Override
    public DoipMessage onRequest(ChannelHandlerContext ctx, DoipMessage msg) {
        String str = msg.header.parameters.operation;
        if (msg.header != null
                && msg.header.parameters.operation.equals(SharableVarManager.SHARABLEOP)) {
            return SharableVarManager.instance.handleSyncMessage(msg);
        }
        logger.debug("[Call operation] name: " + str);
        if (str != null) {
            FunctionNode fn;
            fn = doipFunctionNodeMap.get(str);
            if (fn == null)
                fn = doipFunctionNodeMap.get(BasicOperations.Unknown.getName());
            if (fn != null) {
                return buildRequestAndInvokeEngine(fn, msg);
            }
        }
        return null;
    }

    public DoipMessage buildRequestAndInvokeEngine(FunctionNode fn, DoipMessage msg) {
        ContractRequest contractRequest = constructContractRequest(fn, msg);
        DoipMessagePacker arg = new DoipMessagePacker("doip", msg);
        try {
            // 改变调用的函数 + 构造DoipMessagePacker
            Object ret =
                    ContractProcess.instance.engine.executeWithoutLock(fn, contractRequest, arg);
            DoipMessage finalDoipMsg = (DoipMessage) ret;
            finalDoipMsg.requestID = msg.requestID;
            if (finalDoipMsg.header.parameters == null) {
                finalDoipMsg.header.parameters = new HeaderParameter("", "");
            }
            if (finalDoipMsg.header.parameters.attributes == null) {
                finalDoipMsg.header.parameters.attributes = new JsonObject();
            }
            finalDoipMsg.header.parameters.attributes.addProperty("nodeID",
                    String.valueOf(JavaScriptEntry.shardingID));
            return finalDoipMsg;
        } catch (Exception e) {
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            e.printStackTrace(new PrintStream(bo));
            DoipMessageFactory.DoipMessageBuilder builder =
                    new DoipMessageFactory.DoipMessageBuilder();
            builder.createResponse(DoipResponseCode.Declined, msg);
            builder.setBody(bo.toByteArray());
            builder.addAttributes("nodeID", String.valueOf(JavaScriptEntry.shardingID));
            logger.error(
                    "buildRequestAndInvokeEngine has something wrong, executeWithoutLock err or validateJsonElementRulesByArgSchemaVisitor err");
            return builder.create();
        }
    }

    public ContractRequest constructContractRequest(FunctionNode fn, DoipMessage request) {
        ContractRequest cr = new ContractRequest();
        cr.setContractID("");
        if (request.credential == null) {
            cr.setRequester(null);
        } else {
            cr.setRequester(request.credential.getSigner());
        }
        cr.setAction(fn.functionName);
        cr.setArg(JsonUtil.parseObjectAsJsonObject(JsonDoipMessage.fromDoipMessage(request)));
        return cr;
    }
}
