package org.bdware.sc.trace;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bdware.analysis.CFGraph;
import org.bdware.sc.ContractProcess;
import wrp.jdk.nashorn.internal.runtime.TraceMethod;

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

public class ProgramPointCounter extends ContractProcess.Logger implements TraceMethod {
    public long gasLimit;
    public long extraGas;
    // PrintStream out;
    ByteArrayOutputStream bo;
    HashMap<Integer, Map<String, Integer>> ppc = new HashMap<>();
    String globalAction;
    HashMap<String, CFGraph> cfgMap;
    HashMap<String, Long> countMap;
    int functionIndex = 0;
    public long gasValue = 0;

    public ProgramPointCounter(
            ByteArrayOutputStream bo,
            ContractProcess cp,
            long gasLimit,
            int functionIndex,
            long gasValue,
            long extraGas,
            String action,
            HashMap<String, Long> countMap) {
        super(new PrintStream(bo), cp);
        //   out = System.out;
        this.bo = bo;
        globalAction = action;
        this.gasLimit = gasLimit - extraGas;
        this.functionIndex = functionIndex;
        this.countMap = countMap;
        this.gasValue = gasValue - extraGas;
        this.extraGas = extraGas;
    }

    boolean simple = true;

    @Override
    public String getOutputStr() {
        return bo.toString();
    }
    /*
        private void printObject(final Object arg) {
            if (simple) return;
            if (arg instanceof ScriptObject) {
                final ScriptObject object = (ScriptObject) arg;

                boolean isFirst = true;
                final Set<Object> keySet = object.keySet();
                // System.out.println("[keySet:]");
                if (keySet.isEmpty()) {
                    out.print(ScriptRuntime.safeToString(arg));
                } else {
                    out.print("{ ");

                    for (final Object key : keySet) {
                        if (!isFirst) {
                            out.print(", ");
                        }

                        out.print(key);
                        out.print(":");

                        final Object value = object.get(key);

                        if (value instanceof ScriptObject) {
                            out.print("...");
                        } else {
                            printObject(value);
                        }

                        isFirst = false;
                    }

                    out.print(" }");
                }
            } else {
                out.print(ScriptRuntime.safeToString(arg));
            }
        }
    */
    boolean initEnter = false;
    Stack<Integer> pcStack = new Stack<>();

    @Override
    public void tracePrint(
            final String tag, int pc, String methodName, final Object[] args, final Object result) {
        //  System.out.println("!@#$%^&*" + tag + pc + methodName + args + result);
        if (!initEnter) {
            // System.out.println("[functionIndex]:" + countMap.get(String.valueOf(functionIndex)));
            compareValue(String.valueOf(functionIndex));
            initEnter = true;
        }

        if (tag.equals("EXIT  ")) {
            // System.out.println(tag);
            if (pc == pcStack.peek()) {
                // System.out.println("出栈之前" + pcStack);
                pcStack.pop();
                if (countMap.containsKey(String.valueOf(pc))) {
                    // System.out.println("出栈之后" + pcStack);
                    compareValue(String.valueOf(pc));
                    // System.out.println("弹出之后的消耗" + gasValue);
                }
            }
        } else {
            // System.out.println(tag);
            pcStack.push(pc);
            // System.out.println("入栈" + pcStack);
        }
        //        out.print("[ProgramPointCounter] " + tag);
        //        out.print(methodName + "_" + pc + "(");

        //        if (args.length > 0) {
        //            printObject(args[0]);
        //            for (int i = 1; i < args.length; i++) {
        //                final Object arg = args[i];
        //                out.print(", ");
        //                if (!(arg instanceof ScriptObject && ((ScriptObject) arg).isScope())) {
        //                    printObject(arg);
        //                } else {
        //                    out.print("SCOPE");
        //                }
        //            }
        //        }
        //        out.print(")");
        //        if (tag.equals("EXIT  ")) {
        //            out.print(" --> ");
        //            printObject(result);
        //            System.out.println("[result:]" + result);
        //        }
        //        out.println();
        //  System.out.println("[Fee剩余]" + gasValue);
    }

    public long cost = 0;

    private void compareValue(String index) {
        if (gasValue > gasLimit) {
            System.out.println("out of gas");
            throw new IllegalStateException("gas out of limit");
        }
        cost = cost + countMap.get(index);
        // System.out.println("gasValue --:" + gasValue + " -" + countMap.get(index));

        gasValue = gasValue - countMap.get(index);
        gasLimit = gasLimit - countMap.get(index);
        if (gasValue <= 0) {
            System.out.println("out of gas");
            throw new IllegalStateException("run out of InsnFee");
        } else if (gasLimit <= 0) {
            System.out.println("out of gas");
            throw new IllegalStateException("run over the limit");
        }
    }

    @Override
    public void println(String s) {
        super.println(s);
        // System.out.print("[s是：]"+s);
        // count++;
        // String oldTrace=traceCompare(s);
        // +=当前if与之前的if的指令权重或是指令数量。
        // if (count > 10) {
        // throw new IllegalStateException("run out of gas");
        // }
        try {
            JsonObject jo = new JsonParser().parse(s).getAsJsonObject();
            String traceMarkValue = jo.get("traceMark").getAsString();
            String val = jo.get("val").getAsString();
            // System.out.println(countMap);
            for (Map.Entry<String, Long> test : countMap.entrySet()) {
                if (test.getKey().contains(traceMarkValue)) {
                    if (Integer.valueOf(val) <= 0 && test.getKey().contains("false")) {
                        // System.out.println("false" + test.getKey());
                        compareValue(test.getKey());
                    } else if (Integer.valueOf(val) > 0 && test.getKey().contains("true")) {
                        // System.out.println("true" + test.getKey());
                        compareValue(test.getKey());
                        // System.out.println("[gasLimit]"+gasValue+"[gasLimit]"+gasLimit);
                    }
                }
            }
            //            System.out.println("[Gas剩余]" + gasValue);
            //            System.out.println("[执行消耗Gas]" + cost);
            //            System.out.println("[额外Gas]" + extraGas);
            //            System.out.println(
            //                    "[ProgramPointCounter] {\"traceMark\":"
            //                            + traceMarkValue
            //                            + "\"val\":"
            //                            + val
            //                            + "}");
        } catch (IllegalStateException e) {
            throw e;
        }
    }
}
