package org.llvm;

import java.io.PrintStream;

import org.bridj.Pointer;

import static org.llvm.binding.LLVMLibrary.*;

/**
 * Implements various analyses of the LLVM IR.
 */
public class ExecutionEngine {

    private LLVMExecutionEngineRef engine;

    LLVMExecutionEngineRef engine() {
        return engine;
    }

    ExecutionEngine(LLVMExecutionEngineRef engine) {
        this.engine = engine;
    }

    public void finalise() {
        dispose();
    }

    public void dispose() {
        LLVMDisposeExecutionEngine(engine);
    }

    public static ExecutionEngine createForModule(Module m) {
        Pointer<Pointer<Byte>> outError = Pointer.allocateBytes(1, 1024);

        Pointer<LLVMExecutionEngineRef> pEE = Pointer
                .allocateTypedPointer(LLVMExecutionEngineRef.class);

        boolean err = LLVMCreateExecutionEngineForModule(pEE, m.module(),
                outError) != 0;
        if (err) {
            String msg = outError.get().getCString();
            throw new RuntimeException("can't create execution engine: " + msg);
        }

        return new ExecutionEngine(pEE.get());
    }

    /*public static native int LLVMCreateInterpreterForModule(
            Pointer<Pointer<LLVMOpaqueExecutionEngine>> outInterp,
            LLVMModuleRef m, Pointer<Pointer<Byte>> outError);

    public static native int LLVMCreateJITCompilerForModule(
            Pointer<Pointer<LLVMOpaqueExecutionEngine>> outJIT,
            LLVMModuleRef m, int optLevel, Pointer<Pointer<Byte>> outError);

    public static native int LLVMCreateExecutionEngine(
            Pointer<Pointer<LLVMOpaqueExecutionEngine>> outEE,
            LLVMModuleProviderRef mp, Pointer<Pointer<Byte>> outError);

    public static native int LLVMCreateInterpreter(
            Pointer<Pointer<LLVMOpaqueExecutionEngine>> outInterp,
            LLVMModuleProviderRef mp, Pointer<Pointer<Byte>> outError);

    public static native int LLVMCreateJITCompiler(
            Pointer<Pointer<LLVMOpaqueExecutionEngine>> outJIT,
            LLVMModuleProviderRef mp, int optLevel,
            Pointer<Pointer<Byte>> outError);*/

    public void createJITCompilerForModule(Module m, int optLevel) throws LLVMException {
        Pointer<Pointer<Byte>> ppByte = Pointer.pointerToCStrings("");
        Pointer<LLVMExecutionEngineRef> pExec = Pointer.allocate(LLVMExecutionEngineRef.class);
        pExec.set(engine);
        int retval = LLVMCreateJITCompilerForModule(pExec, m.module(), optLevel, ppByte);
        if (retval != 0) {
            Pointer<Byte> pByte = ppByte.getPointer(Byte.class);
            final String message = pByte.getCString();
            LLVMDisposeMessage(pByte);
            throw new LLVMException(message);
        }
    }

    public void runStaticConstructors() {
        LLVMRunStaticConstructors(engine);
    }

    public void runStaticDestructors() {
        LLVMRunStaticDestructors(engine);
    }

    public boolean runFunctionAsMain(Value f, int argC,
            Pointer<Pointer<Byte>> argV, Pointer<Pointer<Byte>> envP) {
        return LLVMRunFunctionAsMain(engine, f.value(), argC, argV, envP) != 0;
    }

    public GenericValue runFunction(Value f, GenericValue... args) {
        // Pointer<Pointer<LLVMOpaqueGenericValue>> args) {

        return new GenericValue(LLVMRunFunction(engine, f.value(),
                args.length, internalize(args)));
    }

    public void freeMachineCodeForFunction(Value f) {
        LLVMFreeMachineCodeForFunction(engine, f.value());
    }

    public void addModule(Module m) {
        LLVMAddModule(engine, m.module());
    }

    /**
     * @deprecated Use LLVMAddModule instead.
     */
    public void addModuleProvider(LLVMModuleProviderRef mp) {
        LLVMAddModuleProvider(engine, mp);
    }

    public Module removeModule(Module m) {
        Pointer<Pointer<Byte>> outError = Pointer.allocateBytes(1, 1024);
        Pointer<LLVMModuleRef> outMod = Pointer
                .allocateTypedPointer(LLVMModuleRef.class);
        boolean err = LLVMRemoveModule(engine, m.module(), outMod, outError) != 0;
        if (err) {
            String msg = outError.get().getCString();
            throw new RuntimeException("can't remove module: " + msg);
        }
        return new Module(outMod.get());
    }

    // TODO: make sure of this
    public Module removeModuleProvider(LLVMModuleProviderRef mp) {
        Pointer<Pointer<Byte>> outError = Pointer.allocateBytes(1, 1024);
        Pointer<LLVMModuleRef> outMod = Pointer
                .allocateTypedPointer(LLVMModuleRef.class);
        boolean err = LLVMRemoveModuleProvider(engine, mp, outMod, outError) != 0;
        if (err) {
            String msg = outError.get().getCString();
            throw new RuntimeException("can't remove module provider: " + msg);
        }
        return new Module(outMod.get());
    }

    public Value findFunction(String name) { // Pointer<Pointer<LLVMOpaqueValue>> outFn) {
        Pointer<Byte> cstr = Pointer.pointerToCString(name);
        Pointer<LLVMValueRef> outFn = Pointer
                .allocateTypedPointer(LLVMValueRef.class);
        boolean err = LLVMFindFunction(engine, cstr, outFn) != 0;
        if (err) {
            throw new RuntimeException("LLVMFindFunction can't find " + name);
        }
        return new Value(outFn.get());
    }

    // TODO: this probably is returning a ValueRef for the recompiled Fn
    public Pointer<?> recompileAndRelinkFunction(Value fn) {
        return LLVMRecompileAndRelinkFunction(engine, fn.value());
    }

    public LLVMTargetDataRef getExecutionEngineTargetData() {
        return LLVMGetExecutionEngineTargetData(engine);
    }

    public void addTargetData(PassManager manager) {
        LLVMAddTargetData(getExecutionEngineTargetData(), manager.manager());
    }

    public void addGlobalMapping(Value global, Pointer<?> addr) {
        LLVMAddGlobalMapping(engine, global.value(), addr);
    }

    public Pointer<?> getPointerToGlobal(Value global) {
        return LLVMGetPointerToGlobal(engine, global.value());
    }

    static Pointer<LLVMGenericValueRef> internalize(GenericValue[] values) {
        int n = values.length;
        LLVMGenericValueRef[] inner = new LLVMGenericValueRef[n];
        for (int i = 0; i < n; i++) {
            inner[i] = values[i].ref();
        }

        Pointer<LLVMGenericValueRef> array = Pointer.allocateTypedPointers(
                LLVMGenericValueRef.class, n);
        array.setArray(inner);

        return array;
    }

}