/******************************************************************************
* Copyright (C) 2006-2012  IFS Institute for Software and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Original authors:
*     Dennis Hunziker
*     Ueli Kistler
*     Reto Schuettel
*     Robin Stocker
* Contributors:
*     Fabio Zadrozny <fabiofz@gmail.com> - initial implementation
******************************************************************************/
/* 
 * Copyright (C) 2006, 2007  Dennis Hunziker, Ueli Kistler
 * Copyright (C) 2007  Reto Schuettel, Robin Stocker
 *
 * IFS Institute for Software, HSR Rapperswil, Switzerland
 * 
 */

package org.python.pydev.refactoring.codegenerator.constructorfield.edit;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Assign;
import org.python.pydev.parser.jython.ast.Attribute;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.Expr;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.NameTok;
import org.python.pydev.parser.jython.ast.NameTokType;
import org.python.pydev.parser.jython.ast.argumentsType;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.parser.jython.ast.factory.NodeHelper;
import org.python.pydev.refactoring.ast.adapters.FunctionDefAdapter;
import org.python.pydev.refactoring.ast.adapters.IClassDefAdapter;
import org.python.pydev.refactoring.ast.adapters.INodeAdapter;
import org.python.pydev.refactoring.codegenerator.constructorfield.request.ConstructorFieldRequest;
import org.python.pydev.refactoring.core.edit.AbstractInsertEdit;

public class ConstructorMethodEdit extends AbstractInsertEdit {

    private static final String KWARGS = "kwargs";
    private static final String ARGS = "args";
    private int offsetStrategy;
    private List<INodeAdapter> attributes;
    private IClassDefAdapter classAdapter;

    public ConstructorMethodEdit(ConstructorFieldRequest req) {
        super(req);
        this.classAdapter = req.classAdapter;
        this.attributes = req.attributeAdapters;
        this.offsetStrategy = req.offsetStrategy;
    }

    /**
     * Tricky :)
     * @throws MisconfigurationException 
     */
    @Override
    protected SimpleNode getEditNode() throws MisconfigurationException {
        List<IClassDefAdapter> bases = classAdapter.getBaseClasses();

        //extractArguments
        NameTokType varArg = null;
        NameTokType kwArg = null;

        Set<String> argsNames = new LinkedHashSet<String>();

        for (IClassDefAdapter baseClass : bases) {
            FunctionDefAdapter init = baseClass.getFirstInit();
            if (init != null) {
                if (!init.getArguments().hasOnlySelf()) {
                    argsNames.addAll(init.getArguments().getSelfFilteredArgNames());
                }
                if (varArg == null && init.getArguments().hasVarArg()) {
                    varArg = new NameTok(ARGS, NameTok.VarArg);
                }

                if (kwArg == null && init.getArguments().hasKwArg()) {
                    kwArg = new NameTok(KWARGS, NameTok.KwArg);
                }
            }
        }

        //addOwnArguments
        for (INodeAdapter adapter1 : attributes) {
            argsNames.add(nodeHelper.getPublicAttr(adapter1.getName()));
        }
        List<exprType> argsExprList = new ArrayList<exprType>();
        Name selfArg = new Name(NodeHelper.KEYWORD_SELF, Name.Param, false);
        argsExprList.add(selfArg);
        for (String parameter : argsNames) {
            argsExprList.add(new Name(parameter.trim(), Name.Param, false));
        }

        exprType[] argsExpr = argsExprList.toArray(new exprType[0]);
        argumentsType args = new argumentsType(argsExpr, varArg, kwArg, null, null, null, null, null, null, null);

        //constructorCalls
        List<stmtType> body = new ArrayList<stmtType>();
        for (IClassDefAdapter base : bases) {
            Expr init = extractConstructorInit(base);
            if (init != null) {
                body.add(init);
            }
        }

        //initAttributes
        for (INodeAdapter adapter : attributes) {
            exprType target = new Attribute(new Name(NodeHelper.KEYWORD_SELF, Name.Load, false), new NameTok(
                    adapter.getName(), NameTok.Attrib), Attribute.Store);
            Assign initParam1 = new Assign(new exprType[] { target }, new Name(nodeHelper.getPublicAttr(adapter
                    .getName()), Name.Load, false));
            Assign initParam = initParam1;
            body.add(initParam);
        }

        //create function def
        return new FunctionDef(new NameTok(NodeHelper.KEYWORD_INIT, NameTok.FunctionName), args,
                body.toArray(new stmtType[0]), null, null);
    }

    private Expr extractConstructorInit(IClassDefAdapter base) {
        FunctionDefAdapter init = base.getFirstInit();
        if (init != null) {
            if (!init.getArguments().hasOnlySelf()) {
                Attribute classInit = new Attribute(new Name(moduleAdapter.getBaseContextName(this.classAdapter,
                        base.getName()), Name.Load, false), new NameTok(NodeHelper.KEYWORD_INIT, NameTok.Attrib),
                        Attribute.Load);
                List<exprType> constructorParameters = init.getArguments().getSelfFilteredArgs();

                Name selfArg = new Name(NodeHelper.KEYWORD_SELF, Name.Load, false);
                constructorParameters.add(0, selfArg);

                exprType[] argExp = constructorParameters.toArray(new exprType[0]);
                Name varArg = null;
                Name kwArg = null;

                if (init.getArguments().hasVarArg()) {
                    varArg = new Name(ARGS, Name.Load, false);
                }

                if (init.getArguments().hasKwArg()) {
                    kwArg = new Name(KWARGS, Name.Load, false);
                }

                Call initCall = new Call(classInit, argExp, null, varArg, kwArg);
                return new Expr(initCall);
            }
        }
        return null;
    }

    @Override
    public int getOffsetStrategy() {
        return offsetStrategy;
    }

}
