package org.ghidra.api;

import java.util.LinkedHashMap;
import java.util.Map;

import ghidra.program.model.block.BasicBlockModel;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import org.ghidra.bridge.decompile.DecompilerService;
import org.ghidra.bridge.edit.ProgramEdits;
import org.ghidra.bridge.project.ProjectService;
import org.ghidra.bridge.query.ProgramCallGraph;
import org.ghidra.bridge.query.ProgramFunctionRelations;
import org.ghidra.bridge.query.ProgramListing;
import org.ghidra.bridge.query.ProgramMetadata;
import org.ghidra.bridge.query.ProgramModel;
import org.ghidra.bridge.query.ProgramSourceMaps;
import org.ghidra.bridge.runtime.GhidraBootstrap;
import org.ghidra.bridge.runtime.OutputSilencer;
import org.ghidra.bridge.runtime.TaskMonitors;
import org.ghidra.bridge.serialization.JsonWriter;
import org.ghidra.bridge.session.BridgeSession;

public final class GhidraApiBridge {
    private static final BridgeSession SESSION = new BridgeSession();

    private GhidraApiBridge() {
    }

    public static synchronized void initialize(String installDir) throws Exception {
        OutputSilencer.suppress(() -> {
            GhidraBootstrap.initialize(installDir);
            return null;
        });
    }

    public static synchronized long openOrCreateProject(
        String projectLocation,
        String projectName
    ) throws Exception {
        return OutputSilencer.suppress(() -> SESSION.insertProject(
            ProjectService.openOrCreateProject(projectLocation, projectName)
        ));
    }

    public static synchronized long openProgram(
        long projectHandle,
        String folderPath,
        String programName
    ) throws Exception {
        return OutputSilencer.suppress(() -> SESSION.insertProgram(ProjectService.openProgram(
            SESSION.project(projectHandle),
            folderPath,
            programName
        )));
    }

    public static synchronized String openOrImportProgram(
        long projectHandle,
        String binaryPath,
        String folderPath,
        String programName,
        String loader,
        String language,
        String compiler,
        String[] loaderArgs,
        int monitorTimeoutSeconds
    ) throws Exception {
        return OutputSilencer.suppress(() -> {
            ProjectService.LoadedProgram loaded = ProjectService.openOrImportProgram(
                SESSION.project(projectHandle),
                binaryPath,
                folderPath,
                programName,
                loader,
                language,
                compiler,
                loaderArgs,
                monitorTimeoutSeconds
            );
            long handle = SESSION.insertProgram(loaded.program());
            Map<String, Object> value = new LinkedHashMap<>();
            value.put("handle", handle);
            value.put("info", loaded.info());
            return JsonWriter.json(value);
        });
    }

    public static synchronized String analyzeProgram(
        long projectHandle,
        long programHandle,
        String mode,
        int monitorTimeoutSeconds
    ) throws Exception {
        SESSION.project(projectHandle);
        return OutputSilencer.suppress(() -> ProjectService.analyze(
            SESSION.program(programHandle),
            mode,
            monitorTimeoutSeconds
        ));
    }

    public static synchronized void saveProgram(
        long projectHandle,
        long programHandle
    ) throws Exception {
        OutputSilencer.suppress(() -> {
            ProjectService.save(SESSION.project(projectHandle), SESSION.program(programHandle));
            return null;
        });
    }

    public static synchronized void closeProgram(
        long projectHandle,
        long programHandle
    ) throws Exception {
        OutputSilencer.suppress(() -> {
            ProjectService.closeProgram(SESSION.project(projectHandle), SESSION.removeProgram(programHandle));
            return null;
        });
    }

    public static synchronized void closeProject(long projectHandle) throws Exception {
        OutputSilencer.suppress(() -> {
            ProjectService.closeProject(SESSION.removeProject(projectHandle));
            return null;
        });
    }

    public static synchronized String programName(long programHandle) {
        return SESSION.program(programHandle).program().getName();
    }

    public static synchronized String listFunctions(long programHandle) {
        return ProgramModel.functionDescriptorsJson(SESSION.program(programHandle).program());
    }

    public static synchronized String parseAddress(
        long programHandle,
        String addressText
    ) {
        return ProgramModel.parseAddressJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String functionAt(
        long programHandle,
        String addressText
    ) {
        return ProgramModel.functionAtJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String functionContaining(
        long programHandle,
        String addressText
    ) {
        return ProgramModel.functionContainingJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String symbols(long programHandle) {
        return ProgramModel.symbolsJson(SESSION.program(programHandle).program());
    }

    public static synchronized String symbolsAt(
        long programHandle,
        String addressText
    ) {
        return ProgramModel.symbolsAtJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String findSymbols(
        long programHandle,
        String name,
        boolean caseSensitive
    ) {
        return ProgramModel.findSymbolsJson(SESSION.program(programHandle).program(), name, caseSensitive);
    }

    public static synchronized String referencesFrom(
        long programHandle,
        String addressText
    ) {
        return ProgramModel.referencesFromJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String referencesTo(
        long programHandle,
        String addressText
    ) {
        return ProgramModel.referencesToJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String memoryBlocks(long programHandle) {
        return ProgramModel.memoryBlocksJson(SESSION.program(programHandle).program());
    }

    public static synchronized String controlFlowGraph(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramListing.controlFlowGraphJson(
            SESSION.program(programHandle).program(),
            functionAddress,
            TaskMonitors.create(-1)
        ));
    }

    public static synchronized String controlFlowGraphs(
        long programHandle,
        String[] functionAddresses
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramListing.controlFlowGraphsJson(
            SESSION.program(programHandle).program(),
            functionAddresses,
            TaskMonitors.create(-1)
        ));
    }

    public static synchronized String callGraph(long programHandle) throws Exception {
        return OutputSilencer.suppress(() -> ProgramCallGraph.json(SESSION.program(programHandle).program()));
    }

    public static synchronized String metadata(long programHandle) throws Exception {
        return OutputSilencer.suppress(() -> ProgramMetadata.json(SESSION.program(programHandle).program()));
    }

    public static synchronized long openDecompiler(long programHandle) throws Exception {
        return OutputSilencer.suppress(() -> SESSION.insertDecompiler(DecompilerService.open(
            SESSION.program(programHandle)
        )));
    }

    public static synchronized void closeDecompiler(long decompilerHandle) throws Exception {
        OutputSilencer.suppress(() -> {
            DecompilerService.close(SESSION.removeDecompiler(decompilerHandle));
            return null;
        });
    }

    public static synchronized String decompileFunction(
        long decompilerHandle,
        String functionAddress,
        int decompileTimeout,
        int monitorTimeoutSeconds
    ) throws Exception {
        return OutputSilencer.suppress(() -> DecompilerService.decompileJson(
            SESSION.decompiler(decompilerHandle),
            functionAddress,
            decompileTimeout,
            monitorTimeoutSeconds
        ));
    }

    public static synchronized String decompilerMetadata(long decompilerHandle) throws Exception {
        return OutputSilencer.suppress(() -> DecompilerService.metadataJson(
            SESSION.decompiler(decompilerHandle)
        ));
    }

    public static synchronized String instructionsForFunction(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return ProgramListing.instructionsForFunctionJson(
            SESSION.program(programHandle).program(),
            functionAddress
        );
    }

    public static synchronized String instructionsInRange(
        long programHandle,
        String startAddress,
        String endAddress
    ) throws Exception {
        return ProgramListing.instructionsInRangeJson(
            SESSION.program(programHandle).program(),
            startAddress,
            endAddress
        );
    }

    public static synchronized String instructionAt(
        long programHandle,
        String addressText
    ) throws Exception {
        return ProgramListing.instructionAtJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String dataAt(
        long programHandle,
        String addressText
    ) {
        return ProgramListing.dataAtJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String dataContaining(
        long programHandle,
        String addressText
    ) {
        return ProgramListing.dataContainingJson(SESSION.program(programHandle).program(), addressText);
    }

    public static synchronized String functionSummary(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.summaryJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String functionSummaries(
        long programHandle,
        String[] functionAddresses
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.summariesJson(
            SESSION.program(programHandle).program(),
            functionAddresses
        ));
    }

    public static synchronized long basicBlockCount(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> {
            Program program = SESSION.program(programHandle).program();
            Function function = ProgramModel.requireFunctionAt(program, functionAddress);
            return ProgramListing.basicBlockCount(
                new BasicBlockModel(program),
                function,
                TaskMonitors.create(-1)
            );
        });
    }

    public static synchronized long instructionCount(
        long programHandle,
        String functionAddress
    ) {
        Program program = SESSION.program(programHandle).program();
        Function function = ProgramModel.requireFunctionAt(program, functionAddress);
        return ProgramListing.instructionCount(program, function);
    }

    public static synchronized String callers(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.callersJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String callees(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.calleesJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String strings(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.stringsJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String constants(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.constantsJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String dataRefs(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.dataRefsJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String imports(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.importsJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String exports(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramFunctionRelations.exportsJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized String sourceMap(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramSourceMaps.sourceMapJson(
            SESSION.program(programHandle).program(),
            functionAddress
        ));
    }

    public static synchronized long startTransaction(
        long programHandle,
        String description
    ) throws Exception {
        return OutputSilencer.suppress(() -> SESSION.insertTransaction(ProgramEdits.startTransaction(
            SESSION.program(programHandle),
            description
        )));
    }

    public static synchronized void endTransaction(
        long transactionHandle,
        boolean commit
    ) throws Exception {
        OutputSilencer.suppress(() -> {
            ProgramEdits.endTransaction(SESSION.transaction(transactionHandle), commit);
            SESSION.removeTransaction(transactionHandle);
            return null;
        });
    }

    public static synchronized void setFunctionPlateComment(
        long transactionHandle,
        String functionAddress,
        String comment
    ) throws Exception {
        OutputSilencer.suppress(() -> {
            ProgramEdits.setFunctionPlateComment(
                SESSION.transaction(transactionHandle),
                functionAddress,
                comment
            );
            return null;
        });
    }

    public static synchronized String functionPlateComment(
        long programHandle,
        String functionAddress
    ) throws Exception {
        return OutputSilencer.suppress(() -> ProgramEdits.functionPlateComment(
            SESSION.program(programHandle),
            functionAddress
        ));
    }
}
