package org.bdware.sc.node;

import com.google.gson.JsonObject;
import org.antlr.v4.runtime.CommonTokenStream;
import org.bdware.sc.bean.DoipOperationInfo;
import org.bdware.sc.bean.JoinInfo;
import org.bdware.sc.bean.RouteInfo;
import org.bdware.sc.event.REvent.REventSemantics;
import org.bdware.sc.util.JsonUtil;

import java.util.*;

import static org.bdware.sc.event.REvent.REventSemantics.AT_LEAST_ONCE;

public class ContractNode {
    private final List<ImportNode> imports;
    private final List<ClassNode> clzs;
    private final List<FunctionNode> functions;
    private final List<SharableNode> sharables;
    private final Map<String, FunctionNode> functionMap;
    private final Set<String> dependentContracts;
    private final Map<String, InterfaceNode> interfaceMap;
    public Map<String, REventSemantics> events;
    public Map<String, REventSemantics> logs;
    public List<AnnotationNode> annotations;
    public boolean sigRequired;
    public String memorySet;
    boolean isBundle;
    String contractName;
    List<Permission> permission;
    List<LogType> logTypes;
    YjsType yjsType;
    boolean instrumentBranch;

    public ContractNode(String name) {
        contractName = name;
        imports = new ArrayList<>();
        clzs = new ArrayList<>();
        functions = new ArrayList<>();
        functionMap = new HashMap<>();
        interfaceMap = new HashMap<>();
        sharables = new ArrayList<>();
        isBundle = false;
        events = new HashMap<>();
        logs = new HashMap<>();
        annotations = new ArrayList<>();
        permission = new ArrayList<>();
        instrumentBranch = false;
        dependentContracts = new HashSet<>();
    }

    public void addFunction(FunctionNode function) {
        functionMap.put(function.functionName, function);
        getFunctions().add(function);
    }

    public void addInterface(InterfaceNode interfaceNode) {
        interfaceMap.put(interfaceNode.functionName, interfaceNode);
    }

    public void addSharable(SharableNode sharable) {
        getSharables().add(sharable);
    }

    public void addClass(ClassNode clzNode) {
        getClzs().add(clzNode);
    }

    public List<FunctionNode> getFunctions() {
        return functions;
    }

    public List<ClassNode> getClzs() {
        return clzs;
    }

    public List<SharableNode> getSharables() {
        return sharables;
    }

    public void initPlainText(CommonTokenStream cts) {
        for (ClassNode cn : clzs) {
            cn.initText(cts);
        }
        for (FunctionNode fun : functions) {
            fun.initTextWithCleaning(cts, fun.isExport, fun.isView());
            if (fun.isExport || fun.functionName.equals("onCreate")) {
                fun.initTextWithRequester();
            }
        }
    }

    public int queryLine(String methodName) {
        FunctionNode cn = functionMap.get(methodName);
        if (null != cn) {
            return cn.getLine();
        }
        return 0;
    }

    public String getContractName() {
        return contractName;
    }

    public String queryFile(String methodName) {
        FunctionNode cn = functionMap.get(methodName);
        if (null != cn) {
            return cn.getFileName();
        } else {
            return "--";
        }
    }

    public void addImportStmt(ImportNode importNode) {
        imports.add(importNode);
    }

    public List<ImportNode> getImports() {
        return imports;
    }

    public boolean isBundle() {
        return isBundle;
    }

    public void setIsBundle(boolean b) {
        isBundle = b;
    }

    public void merge(ContractNode contract) {
        sigRequired |= contract.sigRequired;
        instrumentBranch |= contract.instrumentBranch;
        for (FunctionNode fn : contract.functions) {
            functions.add(fn);
            functionMap.put(fn.functionName, fn);
        }
        for (InterfaceNode interfaceNode : contract.interfaceMap.values()) {
            interfaceMap.put(interfaceNode.functionName, interfaceNode);
        }
        sharables.addAll(contract.getSharables());
        clzs.addAll(contract.clzs);
        this.events.putAll(contract.events);
        this.logs.putAll(contract.logs);
        if (null != contract.permission) {
            permission.addAll(contract.permission);
        }
        if (null != contract.annotations) {
            annotations.addAll(contract.annotations);
        }
        dependentContracts.addAll(contract.dependentContracts);
    }

    public boolean isExport(String action) {
        FunctionNode node = functionMap.get(action);
        return (null != node && node.isExport);
    }

    public FunctionNode getFunction(String action) {
        return functionMap.get(action);
    }

    public Set<String> getDependentContracts() {
        return dependentContracts;
    }

    public void addDependentContracts(String contractName) {
        dependentContracts.add(contractName);
    }

    public void addEvent(String eventName, String semantics, boolean isGlobal) {
        Map<String, REventSemantics> pointer = (isGlobal ? this.events : this.logs);
        try {
            pointer.put(eventName, REventSemantics.valueOf(semantics));
        } catch (IllegalArgumentException | NullPointerException e) {
            pointer.put(eventName, AT_LEAST_ONCE);
        }
    }

    public void addAnnotation(AnnotationNode annNode) {
        annotations.add(annNode);
    }

    public void setLogType(List<String> args) {
        logTypes = new ArrayList<>();
        for (String str : args) {
            logTypes.add(LogType.parse(str));
        }
    }

    public List<LogType> getLogTypes() {
        return logTypes;
    }

    public List<Permission> getPermission() {
        return permission;
    }

    public void setPermission(List<String> args) {
        // permission = new ArrayList<>();
        for (String str : args) {
            permission.add(Permission.parse(str));
        }
    }

    public YjsType getYjsType() {
        return yjsType;
    }

    public boolean hasDoipModule() {
        if (functions != null)
            for (FunctionNode node : functions) {
                if (node.getDoipOperationInfo() != null)
                    return true;
            }
        return false;
    }

    public void setYjsType(YjsType yjsType1) {
        this.yjsType = yjsType1;
    }

    public boolean getInstrumentBranch() {
        return instrumentBranch;
    }

    public void setInstrumentBranch(boolean b) {
        instrumentBranch = b;
    }

    public void resetContractName(String name) {
        contractName = name;
    }

    public void maintainRouteJoinInfo(JsonObject methodRouteInfoMap, JsonObject methodJoinInfoMap,
            JsonObject dependentFunctions) {
        // all functions存了ContractNode中，所有的FunctionNode
        List<FunctionNode> allFunctions = getFunctions();
        for (FunctionNode functionNode : allFunctions) {
            AnnotationNode doopAnnotation = functionNode.getAnnotation("DOOP");
            DoipOperationInfo doipOperationInfo = functionNode.getDoipOperationInfo();
            if (doopAnnotation != null && doipOperationInfo != null) {
                RouteInfo routeInfo = functionNode.getRouteInfo();
                JoinInfo joinInfo = functionNode.getJoinInfo();
                if (routeInfo != null) {
                    packSourceFunctionAndDependentFunctions(getFunction(routeInfo.funcName),
                            dependentFunctions);
                    methodRouteInfoMap.add(doipOperationInfo.operationName,
                            JsonUtil.parseObjectAsJsonObject(routeInfo));
                }
                if (joinInfo != null) {
                    packSourceFunctionAndDependentFunctions(getFunction(joinInfo.joinCountFuncName),
                            dependentFunctions);
                    packSourceFunctionAndDependentFunctions(getFunction(joinInfo.joinFuncName),
                            dependentFunctions);
                    methodJoinInfoMap.add(doipOperationInfo.operationName,
                            JsonUtil.parseObjectAsJsonObject(joinInfo));
                }
            }
        }
    }

    public void packSourceFunctionAndDependentFunctions(FunctionNode sourceFunctionNode,
            JsonObject functions) {
        if (sourceFunctionNode == null)
            return;
        functions.addProperty(sourceFunctionNode.functionName, sourceFunctionNode.plainText());
        // find all dependent functions to the "functions" struct
        for (String dependentFunctionName : sourceFunctionNode.getDependentFunctions()) {
            FunctionNode dependentFunctionNode = getFunction(dependentFunctionName);
            functions.addProperty(dependentFunctionName, dependentFunctionNode.plainText());
        }
    }


    public void mergeInterfaceAnnotationIntoFunction() {
        for (InterfaceNode node : interfaceMap.values()) {
            FunctionNode functionNode = functionMap.get(node.functionName);
            if (functionNode != null) {
                Set<String> funAnno = new HashSet<>();
                for (AnnotationNode annotationNode : functionNode.annotations) {
                    funAnno.add(annotationNode.type);
                }
                for (AnnotationNode annotationNode : node.annotations) {
                    if (funAnno.contains(annotationNode.type))
                        throw new RuntimeException("duplicated annotation:" + node.functionName
                                + " -> " + annotationNode.getType());
                }
                functionNode.annotations.addAll(node.annotations);
            } else
                throw new RuntimeException("unimplemented functions:" + node.functionName);
        }
    }
}
