package org.bdware.sc.boundry.utils;

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.client.AuditIrpClient;
import org.bdware.doip.codec.digitalObject.DigitalObject;
import org.bdware.doip.codec.digitalObject.Element;
import org.bdware.doip.codec.doipMessage.DoipMessage;
import org.bdware.doip.codec.doipMessage.DoipMessageFactory;
import org.bdware.doip.codec.operations.BasicOperations;
import org.bdware.doip.endpoint.client.ClientConfig;
import org.bdware.doip.endpoint.client.DoipClientImpl;
import org.bdware.doip.endpoint.client.DoipMessageCallback;
import org.bdware.irp.stateinfo.StateInfoBase;
import org.bdware.sc.compiler.PermissionStub;
import org.bdware.sc.engine.JSONTool;
import org.bdware.sc.node.Permission;
import org.bdware.sc.util.JsonUtil;
import wrp.jdk.nashorn.api.scripting.ScriptObjectMirror;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@PermissionStub(permission = Permission.DOIP)
public class DOIPUtil {
    private static final Logger LOGGER = LogManager.getLogger(DOIPUtil.class);
    private final String url;
    private final DoipClientImpl doipClient;

    private DOIPUtil(String url) {
        this.url = url;
        this.doipClient = new DoipClientImpl();
        doipClient.connect(ClientConfig.fromUrl(url));
    }

    public static DOIPUtil createClient(String url) {
        return new DOIPUtil(url);
    }

    public String test(String doi) {
        return "create DOClient And hello " + doi + " World";
    }

    public boolean reconnect() {
        try {
            if (doipClient != null)
                doipClient.reconnect();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private DoipMessage syncGetMessage(DoipMessage message) {
        List<DoipMessage> ret = new ArrayList<>();
        DoipMessage msg = null;
        try {
            doipClient.sendMessage(message, new DoipMessageCallback() {
                @Override
                public void onResult(DoipMessage doipMessage) {
                    ret.add(doipMessage);
                    synchronized (ret) {
                        ret.notify();
                    }
                }
            });
            synchronized (ret) {
                ret.wait(5000);
            }
            if (ret.size() > 0)
                msg = ret.get(0);
            if (msg != null) {
                return msg;
            } else return DoipMessageFactory.createTimeoutResponse(message.requestID);
        } catch (Exception ie) {
            ie.printStackTrace();
            return DoipMessageFactory.createConnectFailedResponse(message.requestID);
        }
    }

    private static String convertDoipMsgToString(DoipMessage ret) {
        try {
            LOGGER.info(new String(ret.body.encodedData));
            DigitalObject respDO = ret.body.getDataAsDigitalObject();
            return respDO.toString();
        } catch (Exception ie) {
            ie.printStackTrace();
            return ret.body.getDataAsJsonString();

        }
    }

    public String hello(String repoID) {
        DoipMessage msg = (new DoipMessageFactory.DoipMessageBuilder()).createRequest(repoID, BasicOperations.Hello.getName()).create();
        return convertDoipMsgToString(syncGetMessage(msg));
    }

    public String retrieve(String doi, String args) {
        DoipMessage msg = (new DoipMessageFactory.DoipMessageBuilder()).createRequest(doi, BasicOperations.Retrieve.getName())
                .create();
        msg.header.parameters.addAttribute("element", "");
        return convertDoipMsgToString(syncGetMessage(msg));
    }

    public String call(String doi, String action, String args) {
        JsonObject jo = new JsonObject();
        jo.addProperty("action", action);
        jo.addProperty("args", args);
        DoipMessage msg = (new DoipMessageFactory.DoipMessageBuilder())
                .createRequest(doi, BasicOperations.Retrieve.getName())
                .setBody(jo.toString().getBytes(StandardCharsets.UTF_8)).create();
        return convertDoipMsgToString(syncGetMessage(msg));
    }

    public String create(String repoID, String doString) {
        DigitalObject digitalObject = JsonUtil.fromJson(doString, DigitalObject.class);
        for (Element e : digitalObject.elements) {
            if (null != e.dataString) {
                e.setData(e.dataString.getBytes());
            }
            e.dataString = null;
        }
        DoipMessage msg =
                new DoipMessageFactory.DoipMessageBuilder()
                        .createRequest(repoID, BasicOperations.Create.getName())
                        .setBody(digitalObject)
                        .create();
        return convertDoipMsgToString(syncGetMessage(msg));
    }

    public String delete(String doID) {
        DoipMessage msg =
                new DoipMessageFactory.DoipMessageBuilder()
                        .createRequest(doID, BasicOperations.Delete.getName())
                        .create();
        return convertDoipMsgToString(syncGetMessage(msg));
    }

    public String listOperation(String doID) {
        DoipMessage msg =
                new DoipMessageFactory.DoipMessageBuilder()
                        .createRequest(doID, BasicOperations.ListOps.getName())
                        .create();
        return convertDoipMsgToString(syncGetMessage(msg));
    }

    public static class IRPClientWrapper {
        public AuditIrpClient impl;

        public IRPClientWrapper(EndpointConfig config) {

            impl = new AuditIrpClient(config);

        }

        public Object reconnect() {
            JsonObject jo2 = new JsonObject();
            try {
                impl.reconnect();
                jo2.addProperty("code", 1);
                jo2.addProperty("msg", "success");
            } catch (Exception e) {
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                e.printStackTrace(new PrintStream(bo));
                jo2.addProperty("code", 0);
                jo2.addProperty("msg", bo.toString());

            }
            return JSONTool.convertJsonElementToMirror(jo2);
        }

        public Object resolve(String doId) {
            StateInfoBase jo = null;
            try {
                jo = impl.resolve(doId);
            } catch (Exception e) {
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                e.printStackTrace(new PrintStream(bo));
                JsonObject jo2 = new JsonObject();
                jo2.addProperty("code", 0);
                jo2.addProperty("msg", bo.toString());
                return JSONTool.convertJsonElementToMirror(jo2);
            }
            return JSONTool.convertJsonElementToMirror(jo.getHandleValues());
        }

        public Object register(ScriptObjectMirror obj) {
            JsonObject jo = JSONTool.convertMirrorToJson(obj).getAsJsonObject();
            StateInfoBase base = new StateInfoBase();
            jo.addProperty("repoId", impl.getEndpointInfo().getDoId());
            base.setHandleValues(jo);
            String ret = null;
            JsonObject jo2 = new JsonObject();
            try {
                ret = impl.register(base);
                if (ret != null) {
                    jo2.addProperty("code", 1);
                    jo2.addProperty("doId", ret.toString());
                } else {
                    jo2.addProperty("code", 0);
                    jo2.addProperty("msg", "connection failed!");
                }
            } catch (Exception e) {
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                e.printStackTrace(new PrintStream(bo));
                jo2.addProperty("code", 0);
                jo2.addProperty("msg", bo.toString());
                return JSONTool.convertJsonElementToMirror(jo2);
            }
            return JSONTool.convertJsonElementToMirror(jo2);

        }
    }

    public static IRPClientWrapper createIrpClient(String uri) {
        return createIrpClient(uri, null, null, null);
    }

    public static IRPClientWrapper createIrpClient(String uri, String pubkey, String privateKey, String repoName) {
        EndpointConfig config = new EndpointConfig();
        config.routerURI = uri;
        config.repoName = repoName;
        config.privateKey = privateKey;
        config.publicKey = pubkey;
        return new IRPClientWrapper(config);
    }

}
