package org.bdware.sc.node;

import org.antlr.v4.runtime.CommonTokenStream;
import org.bdware.sc.event.REvent.REventSemantics;

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 Map<String, FunctionNode> functionMap;
    private final Set<String> dependentContracts;
    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<>();
        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 addClass(ClassNode clzNode) {
        getClzs().add(clzNode);
    }

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

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

    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);
        }
        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 void setYjsType(YjsType yjsType1) {
        this.yjsType = yjsType1;
    }

    public boolean getInstrumentBranch() {
        return instrumentBranch;
    }

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