package org.perl6.nqp.runtime;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.HashMap;

import org.perl6.nqp.sixmodel.SixModelObject;

public class StaticCodeInfo implements Cloneable {
    /**
     * The compilation unit where the code lives.
     */
    public CompilationUnit compUnit;
    
    /**
     * Method handle for the code ref.
     */
    MethodHandle mh;

    /**
     * Curried method handle for resuming.
     */
    MethodHandle mhResume;

    /**
     * Method name for correlation with stack traces.
     */
    public String methodName;
    
    /**
     * The compilation-unit unique ID of the routine (from QAST cuuid).
     */
    public String uniqueId;
    
    /**
     * Static outer.
     */
    public StaticCodeInfo outerStaticInfo;
    
    /**
     * Most recent invocation, if any.
     */
    public CallFrame priorInvocation;

    /**
     * Static lexicals.
     */
    public SixModelObject[] oLexStatic;
    
    /**
     * Flags for each static lexical usage.
     */
    public byte[] oLexStaticFlags;
    
    /**
     * Names of the lexicals we have of each of the base types.
     */
    public String[] oLexicalNames;
    public String[] iLexicalNames;
    public String[] nLexicalNames;
    public String[] sLexicalNames;
    
    /**
     * Map of handlers.
     */
    public long[][] handlers;
    
    /**
     * Static code object (base of any clones).
     */
    public SixModelObject staticCode;
    
    /**
     * Lexical name maps (produced lazily on first use). Note they are only
     * used when we do lexical lookup by name.
     */
    public HashMap<String, Integer> oLexicalMap;
    public HashMap<String, Integer> iLexicalMap;
    public HashMap<String, Integer> nLexicalMap;
    public HashMap<String, Integer> sLexicalMap;
    
    /**
     * Does this code object have a block exit handler?
     */
    public boolean hasExitHandler;
    
    public Integer oTryGetLexicalIdx(String name) {
        if (oLexicalMap == null) {
            HashMap<String, Integer> map = new HashMap<String, Integer>();
            if (oLexicalNames != null)
                for (int i = 0; i < oLexicalNames.length; i++)
                    map.put(oLexicalNames[i], i);
            oLexicalMap = map;
        }
        return oLexicalMap.get(name);
    }
    
    public Integer iTryGetLexicalIdx(String name) {
        if (iLexicalMap == null) {
            HashMap<String, Integer> map = new HashMap<String, Integer>();
            if (iLexicalNames != null)
                for (int i = 0; i < iLexicalNames.length; i++)
                    map.put(iLexicalNames[i], i);
            iLexicalMap = map;
        }
        return iLexicalMap.get(name);
    }
    
    public Integer nTryGetLexicalIdx(String name) {
        if (nLexicalMap == null) {
            HashMap<String, Integer> map = new HashMap<String, Integer>();
            if (nLexicalNames != null)
                for (int i = 0; i < nLexicalNames.length; i++)
                    map.put(nLexicalNames[i], i);
            nLexicalMap = map;
        }
        return nLexicalMap.get(name);
    }
    
    public Integer sTryGetLexicalIdx(String name) {
        if (sLexicalMap == null) {
            HashMap<String, Integer> map = new HashMap<String, Integer>();
            if (sLexicalNames != null)
                for (int i = 0; i < sLexicalNames.length; i++)
                    map.put(sLexicalNames[i], i);
            sLexicalMap = map;
        }
        return sLexicalMap.get(name);
    }
    
    /**
     * Initializes the static code info data structure.
     */
    public StaticCodeInfo(CompilationUnit compUnit, MethodHandle mh,
            String uniqueId,
            String[] oLexicalNames, String[] iLexicalNames,
            String[] nLexicalNames, String[] sLexicalNames,
            long[][] handlers, SixModelObject staticCode) {
        this.compUnit = compUnit;
        this.mh = mh;
        this.uniqueId = uniqueId;
        this.oLexicalNames = oLexicalNames;
        this.iLexicalNames = iLexicalNames;
        this.nLexicalNames = nLexicalNames;
        this.sLexicalNames = sLexicalNames;
        this.handlers = handlers;
        this.staticCode = staticCode;
        if (oLexicalNames != null) {
            this.oLexStatic = new SixModelObject[oLexicalNames.length];
            this.oLexStaticFlags = new byte[oLexicalNames.length];
        }
        MethodType t = mh.type();
        if (t.parameterCount() == 5 && (t.parameterType(4) == ResumeStatus.Frame.class || t.parameterType(4) == ResumeStatus.class)) { // the latter is wrong and obsolete
            mhResume = MethodHandles.insertArguments(mh, 0, null, null, null, null);
            this.mh = MethodHandles.insertArguments(mh, 4, (Object)null);
        }
    }
    
    public StaticCodeInfo clone() {
        try {
            StaticCodeInfo result = (StaticCodeInfo)super.clone();
            if (result.oLexStatic != null) {
                result.oLexStatic = result.oLexStatic.clone();
                result.oLexStaticFlags = result.oLexStaticFlags.clone();
            }
            return result;
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
