/*
 *  Copyright (c) 2007 Mockito contributors
 *  This program is made available under the terms of the MIT License.
 */
package org.powermock.api.mockito.repackaged.cglib.transform.impl;

import org.powermock.api.mockito.repackaged.asm.Type;
import org.powermock.api.mockito.repackaged.cglib.core.CodeEmitter;
import org.powermock.api.mockito.repackaged.cglib.core.CodeGenerationException;
import org.powermock.api.mockito.repackaged.cglib.core.Constants;
import org.powermock.api.mockito.repackaged.cglib.core.ReflectUtils;
import org.powermock.api.mockito.repackaged.cglib.core.Signature;
import org.powermock.api.mockito.repackaged.cglib.core.TypeUtils;
import org.powermock.api.mockito.repackaged.cglib.transform.ClassEmitterTransformer;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @author Juozas Baliuka
 */
public class AddDelegateTransformer extends ClassEmitterTransformer {
    private static final String DELEGATE = "$CGLIB_DELEGATE";
    private static final Signature CSTRUCT_OBJECT =
      TypeUtils.parseSignature("void <init>(Object)");
    
    private Class[] delegateIf;
    private Class delegateImpl;
    private Type delegateType;
    
    /** Creates a new instance of AddDelegateTransformer */
    public AddDelegateTransformer(Class delegateIf[], Class delegateImpl) {
        try {
            delegateImpl.getConstructor(new Class[]{ Object.class });
            this.delegateIf = delegateIf;
            this.delegateImpl = delegateImpl;
            delegateType = Type.getType(delegateImpl);
        } catch (NoSuchMethodException e) {
            throw new CodeGenerationException(e);
        }
    }
    
    public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) {
        
        if(!TypeUtils.isInterface(access)){
            
        Type[] all = TypeUtils.add(interfaces, TypeUtils.getTypes(delegateIf));
        super.begin_class(version, access, className, superType, all, sourceFile);
        
        declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT,
                      DELEGATE,
                      delegateType,
                      null);
        for (int i = 0; i < delegateIf.length; i++) {
            Method[] methods = delegateIf[i].getMethods();
            for (int j = 0; j < methods.length; j++) {
                if (Modifier.isAbstract(methods[j].getModifiers())) {
                    addDelegate(methods[j]);
                }
            }
        }
        }else{
           super.begin_class(version, access, className, superType, interfaces, sourceFile);
        }
    }

    public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) {
        final CodeEmitter e = super.begin_method(access, sig, exceptions);
        if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) {
            return new CodeEmitter(e) {
                private boolean transformInit = true;
                public void visitMethodInsn(int opcode, String owner, String name, String desc) {
                    super.visitMethodInsn(opcode, owner, name, desc);
                    if (transformInit && opcode == Constants.INVOKESPECIAL) {
                        load_this();
                        new_instance(delegateType);
                        dup();
                        load_this();
                        invoke_constructor(delegateType, CSTRUCT_OBJECT);
                        putfield(DELEGATE);
                        transformInit = false;
                    }
                }
            };
        }
        return e;
    }

    private void addDelegate(Method m) {
        Method delegate;
        try {
            delegate = delegateImpl.getMethod(m.getName(), m.getParameterTypes());
            if (!delegate.getReturnType().getName().equals(m.getReturnType().getName())){
                throw new IllegalArgumentException("Invalid delegate signature " + delegate);
            }
        } catch (NoSuchMethodException e) {
            throw new CodeGenerationException(e);
        }

        final Signature sig = ReflectUtils.getSignature(m);
        Type[] exceptions = TypeUtils.getTypes(m.getExceptionTypes());
        CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, sig, exceptions);
        e.load_this();
        e.getfield(DELEGATE);
        e.load_args();
        e.invoke_virtual(delegateType, sig);
        e.return_value();
        e.end_method();
    }
}



