/**************************************************************************************
 * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the LGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package org.codehaus.aspectwerkz.transform.inlining;

import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer;
import org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor;
import org.codehaus.aspectwerkz.transform.Context;

import java.io.File;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;

/**
 * Implementation of the transformation context interface for the delegation weaving.
 * 
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
 */
public class ContextImpl implements Context {
    /**
     * The name of the class.
     */
    private final String m_className;

    /**
     * The initial bytecode of the class
     */
    private byte[] m_initialBytecode;

    /**
     * The current bytecode of the class
     */
    private byte[] m_currentBytecode;

    /**
     * The class loader for the class being transformed.
     */
    private final ClassLoader m_loader;

    /**
     * Marks the class being transformed as advised.
     */
    private boolean m_advised = false;

    /**
     * Marks the class being transformed as advised.
     */
    private boolean m_prepared = false;

    /**
     * Marks the context as read-only.
     */
    private boolean m_readOnly = false;

    /**
     * Meta-data for the transformation.
     */
    private Map m_metaData = new HashMap();

    /**
     * The contextual list of SystemDefinitions
     */
    private final List m_definitions;

    /**
     * The emitted join points.
     */
    private final List m_emittedInlinedJoinPoint = new ArrayList();

    /**
     * Creates a new context.
     * 
     * @param loader the class loader
     */
    public ContextImpl(final String className, final byte[] bytecode, final ClassLoader loader) {
        m_className = className.replace('.', '/');
        m_loader = loader;
        m_initialBytecode = bytecode;
        m_currentBytecode = bytecode;
        m_definitions = SystemDefinitionContainer.getHierarchicalDefs(m_loader);
    }

    public String getClassName() {
        return m_className;
    }

    /**
     * Returns the initial bytecode.
     * 
     * @return bytecode
     */
    public byte[] getInitialBytecode() {
        return m_initialBytecode;
    }

    /**
     * Returns the current bytecode.
     * 
     * @return bytecode
     */
    public byte[] getCurrentBytecode() {
        return m_currentBytecode;
    }

    /**
     * Sets the current bytecode.
     * 
     * @param bytecode
     */
    public void setCurrentBytecode(final byte[] bytecode) {
        m_currentBytecode = bytecode;
    }

    /**
     * Returns the class abstraction.
     * 
     * @return null
     */
    public Object getClassAbstraction() {
        return null;
    }

    /**
     * Returns the class loader.
     * 
     * @return the class loader
     */
    public ClassLoader getLoader() {
        return m_loader;
    }

    /**
     * The definitions context (with hierarchical structure)
     * 
     * @return
     */
    public List getDefinitions() {
        return m_definitions;
    }

    /**
     * Marks the class being transformed as advised. The marker can at most be set once per class per transformer
     */
    public void markAsAdvised() {
        m_advised = true;
    }

    /**
     * Marks the class as prepared.
     */
    public void markAsPrepared() {
        m_prepared = true;
    }

    /**
     * Resets the isAdviced flag.
     */
    public void resetAdvised() {
        m_advised = false;
    }

    /**
     * Checks if the class being transformed has beed advised.
     * 
     * @return boolean
     */
    public boolean isAdvised() {
        return m_advised;
    }

    /**
     * Checks if the class is prepared.
     * 
     * @return
     */
    public boolean isPrepared() {
        return m_prepared;
    }

    /**
     * Marks the context as read-only.
     */
    public void markAsReadOnly() {
        m_readOnly = true;
    }

    /**
     * Checks if the context is read-only.
     * 
     * @return boolean
     */
    public boolean isReadOnly() {
        return m_readOnly;
    }

    /**
     * Returns meta-data for the transformation.
     * 
     * @param key the key
     * @return the value
     */
    public Object getMetaData(final Object key) {
        return m_metaData.get(key);
    }

    /**
     * Adds new meta-data for the transformation.
     * 
     * @param key the key
     * @param value the value
     */
    public void addMetaData(final Object key, final Object value) {
        if (m_readOnly) {
            throw new IllegalStateException("context is read only");
        }
        m_metaData.put(key, value);
    }

    /**
     * Dumps the class to specific directory.
     * 
     * @param dumpDir
     */
    public void dump(final String dumpDir) {
        try {
            File dir = new File(dumpDir + File.separator + m_className.substring(0, m_className.lastIndexOf('/')));
            dir.mkdirs();
            FileOutputStream os = new FileOutputStream(dumpDir
                + File.separator
                + m_className.replace('.', '/')
                + ".class");
            os.write(m_currentBytecode);
            os.close();
        } catch (Exception e) {
            AspectWerkzPreProcessor.log("failed to dump " + m_className);
            e.printStackTrace();
        }
    }

    /**
     * Adds a new EmittedInlinedJoinPoint
     * 
     * @param jp
     */
    public void addEmittedInlinedJoinPoint(final EmittedInlinedJoinPoint jp) {
        m_emittedInlinedJoinPoint.add(jp);
    }

    /**
     * Returns all the EmittedInlinedJoinPoint
     * 
     * @return
     */
    public List getEmittedInlinedJoinPoint() {
        return m_emittedInlinedJoinPoint;
    }

    /**
     * A structure that keeps required information needed to regenerate a JIT joinpoint. The weaver emits this
     * information so that we can add initalization code to the weaved class. Note that EmittedJP are really Emitted -
     * and can be a subset of actual JP (f.e. call, where information is lost in between each weave phase)
     * 
     * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
     * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
     */
    public static class EmittedInlinedJoinPoint {
        public final String targetClassName;

        public final int joinPointType;

        public final String calleeName;

        public final String calleeDesc;

        public final int calleeModifiers;

        public final String callerClassName;

        public final String callerName;

        public final String callerDesc;

        public final int sequence;

        public final int joinPointHash;

        public final String joinPointClassName;

        /**
         * Creates a new instance.
         * 
         * @param joinPointType
         * @param targetClassName
         * @param calleeName
         * @param calleeDesc
         * @param calleeModifiers
         * @param callerClassName
         * @param callerName
         * @param callerDesc
         * @param sequence
         * @param joinPointHash
         * @param joinPointClassName
         */
        public EmittedInlinedJoinPoint(final int joinPointType,
                                       final String targetClassName,
                                       final String calleeName,
                                       final String calleeDesc,
                                       final int calleeModifiers,
                                       final String callerClassName,
                                       final String callerName,
                                       final String callerDesc,
                                       final int sequence,
                                       final int joinPointHash,
                                       final String joinPointClassName) {
            this.joinPointType = joinPointType;
            this.targetClassName = targetClassName;
            this.calleeName = calleeName;
            this.calleeDesc = calleeDesc;
            this.calleeModifiers = calleeModifiers;
            this.callerClassName = callerClassName;
            this.callerName = callerName;
            this.callerDesc = callerDesc;
            this.sequence = sequence;
            this.joinPointHash = joinPointHash;
            this.joinPointClassName = joinPointClassName;
        }
    }
}