package org.ghidra.bridge.serialization;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import ghidra.program.model.address.Address;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.SequenceNumber;
import ghidra.program.model.pcode.Varnode;

public final class PcodeMaps {
    private PcodeMaps() {
    }

    public static List<Map<String, Object>> ops(Iterable<? extends PcodeOp> ops) {
        List<Map<String, Object>> values = new ArrayList<>();
        for (PcodeOp op : ops) {
            values.add(op(op));
        }
        return values;
    }

    public static List<Map<String, Object>> ops(Iterator<? extends PcodeOp> ops) {
        List<Map<String, Object>> values = new ArrayList<>();
        while (ops.hasNext()) {
            values.add(op(ops.next()));
        }
        return values;
    }

    public static List<Map<String, Object>> ops(PcodeOp[] ops) {
        List<Map<String, Object>> values = new ArrayList<>();
        for (PcodeOp op : ops) {
            values.add(op(op));
        }
        return values;
    }

    public static Map<String, Object> op(PcodeOp op) {
        SequenceNumber seqnum = op.getSeqnum();
        Map<String, Object> value = new LinkedHashMap<>();
        value.put("sequence_number", seqnum.toString());
        value.put("sequence_target", address(seqnum.getTarget()));
        value.put("sequence_time", seqnum.getTime());
        value.put("sequence_order", seqnum.getOrder());
        value.put("mnemonic", op.getMnemonic());
        value.put("opcode", op.getOpcode());
        value.put("output", varnode(op.getOutput()));
        value.put("inputs", varnodes(op.getInputs()));
        return value;
    }

    public static Map<String, Object> varnode(Varnode varnode) {
        if (varnode == null) {
            return null;
        }
        Address address = varnode.getAddress();
        Map<String, Object> value = new LinkedHashMap<>();
        value.put("space", varnodeSpace(varnode));
        value.put("offset", Long.toUnsignedString(varnode.getOffset()));
        value.put("size", varnode.getSize());
        value.put("text", varnode.toString());
        value.put("address", address(address));
        value.put("constant", varnode.isConstant());
        value.put("register", varnode.isRegister());
        value.put("unique", varnode.isUnique());
        value.put("address_tied", varnode.isAddrTied());
        value.put("input", varnode.isInput());
        value.put("persistent", varnode.isPersistent());
        return value;
    }

    public static String varnodeSpace(Varnode varnode) {
        if (varnode.isConstant()) {
            return "constant";
        }
        if (varnode.isRegister()) {
            return "register";
        }
        if (varnode.isUnique()) {
            return "unique";
        }
        if (varnode.isAddress()) {
            return "address";
        }
        Address address = varnode.getAddress();
        if (address != null && address.getAddressSpace() != null) {
            return address.getAddressSpace().getName();
        }
        return "unknown";
    }

    private static List<Map<String, Object>> varnodes(Varnode[] varnodes) {
        List<Map<String, Object>> values = new ArrayList<>();
        for (Varnode varnode : varnodes) {
            values.add(varnode(varnode));
        }
        return values;
    }

    private static Map<String, Object> address(Address address) {
        if (address == null) {
            return null;
        }
        Map<String, Object> value = new LinkedHashMap<>();
        value.put("space", address.getAddressSpace().getName());
        value.put("offset", Long.toUnsignedString(address.getOffset()));
        value.put("text", address.toString());
        return value;
    }
}
