package org.bdware.sc.crdt;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.crdt.basic.Constants;
import org.bdware.crdt.basic.JoinableCRDT;
import org.bdware.doip.audit.AuditDoaClient;
import org.bdware.doip.audit.EndpointConfig;
import org.bdware.doip.audit.client.AuditDoipClient;
import org.bdware.doip.audit.client.AuditIrpClient;
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.endpoint.client.DoipMessageCallback;
import org.bdware.irp.client.IrpClient;
import org.bdware.irp.stateinfo.StateInfoBase;
import org.bdware.sc.crdt.proxy.*;
import org.bdware.sc.util.JsonUtil;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

public class SharableVarManager {
    static Logger LOGGER = LogManager.getLogger(SharableVarManager.class);
    public static SharableVarManager instance;
    public final String cpId;
    Map<String, SharableVar> allVars;
    IrpClient client;
    AuditDoaClient doaClient;

    public static final String SHARABLEOP = "86.100871/SyncVar";

    public SharableVarManager(String cpId, EndpointConfig config) {
        this.allVars = new ConcurrentHashMap<>();
        this.client = new AuditIrpClient(config);
        this.doaClient = new AuditDoaClient("", config, null);
        this.cpId = cpId;
    }

    public static void initSharableVarManager(String id, EndpointConfig config) {
        if (instance == null) {
            instance = new SharableVarManager(id, config);
        }
    }

    public DoipMessage handleSyncMessage(DoipMessage message) {
        try {
            String varId = message.header.parameters.attributes.get("varId").getAsString();
            String content = message.header.parameters.attributes.get("content").getAsString();
            String type = message.header.parameters.attributes.get("type").getAsString();
            SharableVar var = allVars.get(varId);
            if (var != null) {
                if (Objects.equals(type, "r2r") || Objects.equals(type, "w2r")) {
                    JoinableCRDT delta = JsonUtil.fromJson(content, var.readerVar.getClass());
                    var.readerVar.join(delta);
                    var.readerVarDeltaQueue.add(delta);
                } else if (Objects.equals(type, "w2w")) {
                    JoinableCRDT delta = JsonUtil.fromJson(content, var.writerVar.getClass());
                    var.writerVar.join(delta);
                    var.writerVarDeltaQueue.add(delta);
                }
            }
            DoipMessageFactory.DoipMessageBuilder builder =
                    new DoipMessageFactory.DoipMessageBuilder();
            builder.createResponse(DoipResponseCode.Success, message);
            builder.addAttributes("msg", "success");
            return builder.create();
        } catch (Exception e) {
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            e.printStackTrace();
            e.printStackTrace(new PrintStream(bo));
            DoipMessageFactory.DoipMessageBuilder builder =
                    new DoipMessageFactory.DoipMessageBuilder();
            builder.createResponse(DoipResponseCode.UnKnownError, message);
            builder.addAttributes("exception", bo.toString());
            return builder.create();
        }
    }

    private DoipMessage createSyncMessage(String target, String varId, String content,
            String type) {
        DoipMessageFactory.DoipMessageBuilder builder = new DoipMessageFactory.DoipMessageBuilder();
        builder.createRequest(target, SHARABLEOP);
        builder.addAttributes("varId", varId);
        builder.addAttributes("content", content);
        builder.addAttributes("type", type);
        return builder.create();
    }

    public void broadcastSyncMessage(String varId, List<String> sendTo, String content,
            String type) {
        for (String target : sendTo) {
            DoipMessage doipMessage = createSyncMessage(target, varId, content, type);
            if (target.equals(cpId)) {
                LOGGER.info("Handle Sync locally:"
                        + JsonUtil.toJson(JsonDoipMessage.fromDoipMessage(doipMessage)));
                handleSyncMessage(doipMessage);
            } else {
                AuditDoipClient client = getClient(target);
                client.sendMessage(doipMessage, new DoipMessageCallback() {
                    @Override
                    public void onResult(DoipMessage doipMessage) {
                        LOGGER.info("RECV Sync:"
                                + JsonUtil.toJson(JsonDoipMessage.fromDoipMessage(doipMessage)));
                    }
                });
            }
        }
    }

    private AuditDoipClient getClient(String id) {
        return doaClient.convertDoidToRepo(id);
    }

    public synchronized SharableVar createVar(String identifier, String type) {
        try {
            if (allVars.containsKey(identifier)) {
                return allVars.get(identifier);
            }
            StateInfoBase stateInfoBase = client.resolve(identifier);
            if (stateInfoBase.handleValues.has("bdwType") && stateInfoBase.handleValues
                    .get("bdwType").getAsString().equals("SharableVar")) {
                SharableVarState.SharableVarConfiguration sharableVarConf =
                        JsonUtil.fromJson(stateInfoBase.handleValues,
                                SharableVarState.SharableVarConfiguration.class);
                SharableVar sharableVar = createSharableVar(sharableVarConf, identifier, type);
                if (sharableVar != null) {
                    allVars.put(identifier, sharableVar);
                }
                return sharableVar;
            } else
                return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private SharableVar createSharableVar(SharableVarState.SharableVarConfiguration conf,
            String identifier, String type) {
        switch (type) {
            case Constants.TypeName.G_COUNTER:
                return new GCounterProxy(identifier, cpId, conf);
            case Constants.TypeName.PN_COUNTER:
                return new PNCounterProxy(identifier, cpId, conf);
            case Constants.TypeName.DW_FLAG:
                return new DWFlagProxy(identifier, cpId, conf);
            case Constants.TypeName.LWW_REGISTER:
                return new LWWRegisterProxy(identifier, cpId, conf);
            case Constants.TypeName.MV_REGISTER:
                return new MVRegisterProxy(identifier, cpId, conf);
            case Constants.TypeName.G_SET:
                return new GSetProxy(identifier, cpId, conf);
            case Constants.TypeName.TP_SET:
                return new TPSetProxy(identifier, cpId, conf);
            case Constants.TypeName.AW_OR_SET:
                return new AWORSetProxy(identifier, cpId, conf);
            case Constants.TypeName.RW_OR_SET:
                return new RWORSetProxy(identifier, cpId, conf);
            case Constants.TypeName.RW_LWW_SET:
                return new RWLWWSetProxy(identifier, cpId, conf);
        }
        return null;
    }

}
