package com.redhat.ceylon.eclipse.imp.refine;

import static com.redhat.ceylon.eclipse.imp.editor.Util.getCurrentEditor;
import static com.redhat.ceylon.eclipse.imp.parser.CeylonSourcePositionLocator.findNode;
import static com.redhat.ceylon.eclipse.imp.proposals.CeylonContentProposer.getRefinementTextFor;

import java.util.Collections;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;

import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.DeclarationWithProximity;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ProducedReference;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.ProducedTypedReference;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.eclipse.imp.editor.CeylonEditor;
import com.redhat.ceylon.eclipse.imp.editor.Util;
import com.redhat.ceylon.eclipse.imp.parser.CeylonParseController;
import com.redhat.ceylon.eclipse.imp.wizard.NewUnitWizard;

public class CreateSubtypeHandler extends AbstractHandler {

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
        CeylonEditor editor = (CeylonEditor) getCurrentEditor();
        Tree.CompilationUnit cu = editor.getParseController().getRootNode();
        if (cu==null) return null;
        Node node = getSelectedNode(editor);
        ProducedType type;
        if (node instanceof Tree.BaseType) {
        	type = ((Tree.BaseType) node).getTypeModel();
        }
        else if (node instanceof Tree.ClassOrInterface) {
        	type = ((Tree.ClassOrInterface) node).getDeclarationModel().getType();
        }
        else {
        	return null;
        }
        
        StringBuilder def = new StringBuilder();
        TypeDeclaration td = type.getDeclaration();
        def.append("class $className");
        //TODO: add a type parameter for each type
        //      argument that is itself a type 
        //      parameter
        if (td instanceof Class) {
        	Class c = (Class) td;
        	if (c.getParameterList().getParameters().isEmpty()) {
        		def.append("()");
        	}
        	else {
        		def.append("(");
        		for (Parameter p: c.getParameterList().getParameters()) {
        			ProducedTypedReference ptr = type.getTypedParameter(p);
        			def.append(ptr.getType().getProducedTypeName())
        			.append(" ").append(p.getName()).append(", ");
        		}
        		def.setLength(def.length()-2);
        		def.append(")");
        	}
        }
        else {
        	def.append("()");
        }
        if (td instanceof Class) {
        	Class c = (Class) td;
        	def.append(" extends ").append(td.getName());
        	if (!td .getTypeParameters().isEmpty()) {
        		def.append("<");
        		for (ProducedType ta: type.getTypeArgumentList()) {
        			def.append(ta.getProducedTypeName()).append(", ");
        		}
        		def.setLength(def.length()-2);
        		def.append(">");
        	}
        	if (c.getParameterList().getParameters().isEmpty()) {
        		def.append("()");
        	}
        	else {
        		def.append("(");
        		for (Parameter p: c.getParameterList().getParameters()) {
        			def.append(p.getName()).append(", ");
        		}
        		def.setLength(def.length()-2);
        		def.append(")");
        	}
        }
        else {
        	def.append(" satisfies ").append(td.getName());
        	if (!td.getTypeParameters().isEmpty()) {
        		def.append("<");
        		for (ProducedType ta: type.getTypeArgumentList()) {
        			def.append(ta.getProducedTypeName()).append(", ");
        		}
        		def.setLength(def.length()-2);
        		def.append(">");
        	}
        }
        def.append(" {\n");
        for (DeclarationWithProximity dwp: td.getMatchingMemberDeclarations("", 0).values()) {
        	Declaration d = dwp.getDeclaration();
        	if (d.isFormal() /*&& td.isInheritedFromSupertype(d)*/) {
        		ProducedReference pr = d.getProducedReference(type, 
        				Collections.<ProducedType>emptyList()); //TODO: is this OK?
        		def.append("    ").append(getRefinementTextFor(d, pr, ""))
        		        .append("\n");
        	}
        }
        def.append("}");
        NewUnitWizard.open(def.toString(), Util.getFile(editor.getEditorInput()), 
        		"My" + td.getName(), "Create Subtype", 
        		"Create a new Ceylon compilation unit containing the new class.");
        
		return null;
	}

    //TODO: copy/pasted from AbstractFindAction
    private static Node getSelectedNode(CeylonEditor editor) {
        CeylonParseController cpc = editor.getParseController();
        return cpc.getRootNode()==null ? null : 
            findNode(cpc.getRootNode(), 
                (ITextSelection) editor.getSelectionProvider().getSelection());
    }

    //TODO: copy/pasted from RefineFormalMembersHandler
    @Override
    public boolean isEnabled() {
        IEditorPart editor = getCurrentEditor();
        if (super.isEnabled() && 
                editor instanceof CeylonEditor &&
                editor.getEditorInput() instanceof IFileEditorInput) {
            Node node = getSelectedNode((CeylonEditor) editor);
            return node instanceof Tree.BaseType || 
            		node instanceof Tree.ClassOrInterface;
        }
        else {
            return false;
        }
    }
}

@@ -1999,32 +1999,316 @@
 el();%0A        %7D%0A
+        else if (node instanceof Tree.BaseTypeExpression) %7B%0A            type = ((Tree.BaseTypeExpression) node).getTypeModel();%0A        %7D%0A        else if (node instanceof Tree.ExtendedTypeExpression) %7B%0A            type = ((Tree.ExtendedTypeExpression) node).getTypeModel();%0A        %7D%0A
         else if 
@@ -6063,16 +6063,146 @@
 ype %7C%7C %0A
+                    node instanceof Tree.BaseTypeExpression %7C%7C%0A                    node instanceof Tree.ExtendedTypeExpression %7C%7C%0A
         

Create Subtype when a type expression node is selected