/*-------------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved.
-------------------------------------------------------------------------------*/

package com.togethersoft.modules.patterns.UNIVERSAL.LINK.Objectifier;

import java.util.Enumeration;
import java.util.Vector;

import com.togethersoft.openapi.ide.message.IdeMessageManagerAccess;
import com.togethersoft.openapi.ide.message.IdeMessageType;

import com.togethersoft.openapi.sci.*;

import com.togethersoft.openapi.sci.enum.SciOperationEnumeration;
import com.togethersoft.openapi.sci.enum.SciInheritanceEnumeration;

import com.togethersoft.openapi.sci.pattern.SciPattern;
import com.togethersoft.openapi.sci.pattern.SciPatternUtil;
import com.togethersoft.openapi.sci.pattern.SciPatternProperty;

import com.togethersoft.openapi.sci.visitor.SciExpressionVisitorAdapter;

import com.togethersoft.openapi.util.propertyMap.Property;

import com.togethersoft.modules.patterns.UNIVERSAL.PatternBase;
import com.togethersoft.modules.patterns.UNIVERSAL.PatternBaseException;
import com.togethersoft.modules.patterns.UNIVERSAL.PatternUIBuilder;

/**
 * Link Objectifier pattern
 * @author TogetherSoft
 */
public class Pattern extends PatternBase implements SciPattern {

    private static final String PATTERN_NAME = "Objectifier";

    public Pattern() {
        setPatternDisplayName(PATTERN_NAME);
    }

    protected void finalize() throws Throwable {
        super.finalize();

        if (myUIBuilder != null) {
            myUIBuilder.removeAllListenedInspectorProperties();
            myUIBuilder = null;
        }

        myClientClass = null;
        myConcreteObjectifierClass = null;
    }

    public boolean prepare() {
        try {
            super.prepare();
            notImplementedForIDL();

            SciElement element = getElement();
            if (element == null || !(element instanceof SciInheritance)) {
                return false;
            }

            myClientClass = (SciClass) getPropertyValue(SciPatternProperty.LINK_SOURCE_CLASS);
            if (myClientClass == null ||
                myClientClass.hasProperty(SciProperty.INTERFACE) ||
                hasInterfaceTag(myClientClass)) {
                return false;
            }

            myConcreteObjectifierClass = (SciClass) getPropertyValue(SciPatternProperty.LINK_DESTINATION_CLASS);
            if (myConcreteObjectifierClass == null ||
                myConcreteObjectifierClass.hasProperty(SciProperty.INTERFACE) ||
                hasInterfaceTag(myConcreteObjectifierClass)) {
                return false;
            }

            setPropertyValue(CLIENT, myClientClass.getName());
            setPropertyValue(OBJECTIFIER, OBJECTIFIER);
            setPropertyValue(ATTRIBUTE, OBJECTIFIER.toLowerCase());
            setPropertyValue(EXISTING_OBJECTIFIER, myConcreteObjectifierClass.getName());
            setPropertyValue(CONCRETE_OBJECTIFIER, null);
            setPropertyValue(INITIALIZATION_VARIANT, NONE);

            initializeInspector();
            return true;
        }
        catch(PatternBaseException e) {
            return false;
        }
    }

    public boolean canApply() {
        try {
            String objectifierName = classNameShouldBeCorrect(OBJECTIFIER);
            SciClass objectifierClass = SciUtil.findClassByName(getClientContainer(), objectifierName);
            if (objectifierClass != null) {
                throw new PatternBaseException("Class " + objectifierName + " already exists");
            }

            identifierShouldBeCorrect(ATTRIBUTE);
            return true;
        }
        catch(PatternBaseException e) {
            return false;
        }
    }

    public void apply() {
        if (!canApply()) {
            return;
        }
        super.apply();

        if (myUIBuilder != null) {
            myUIBuilder.removeAllListenedInspectorProperties();
            myUIBuilder = null;
        }

        // Create Objectifier interface.
        String objectifierName = getStringPropertyValue(OBJECTIFIER);
        SciClass objectifierClass = getFactory().newClass();
        objectifierClass.setName(objectifierName);
        objectifierClass = (SciClass) getClientContainer().paste(objectifierClass, null, true);
        objectifierClass.setProperty(SciProperty.INTERFACE, true);

        // Create inheritance link from Concrete Objectifer to Objectifier.
        // Copy all public methods from Concrete Objectifer to Objectifier.
        SciPatternUtil.makeStubInterface(objectifierClass, myConcreteObjectifierClass, false);

        // Find or create new Concrete Objectifiers.
        // Create inheritance links to Objectifier.
        // Create stub implemented functions in Concrete Objectifiers.
        String name = null;
        SciClass aClass = null;
        Vector concreteObjectifiersVector = new Vector();
        Enumeration names = propertiesEnumeration(CONCRETE_OBJECTIFIER);
        while (names.hasMoreElements()) {
            name = (String)((Property)names.nextElement()).getValue();
            aClass = SciUtil.findClassByName(getClientContainer(), name);
            if (aClass == null) {
                aClass = getFactory().newClass();
                aClass.setName(name);
                aClass = (SciClass) getClientContainer().paste(aClass, null, true);
            }

            SciPatternUtil.makeStubImplementation(aClass, objectifierClass, false, false);
            concreteObjectifiersVector.addElement(aClass);
        }

        // Create Objectifier attribute in Client class.
        String attributeName = getStringPropertyValue(ATTRIBUTE);
        SciAttribute attribute = SciPatternUtil.createAttribute(myClientClass, objectifierClass, attributeName, true);
        attribute.setType( getLanguageHelper().makePointerType(attribute.getType()) ); // [VZ]

        // Implement Objectifiers operations in Client class.
        SciOperation operation = null;
        SciOperation clientOperation = null;
        SciOperationEnumeration operations = objectifierClass.operations();
        try {
            final SciExpression attributeExpression = getGenericFactory().newExpression(attributeName);
            while (operations.hasMoreElements()) {
                operation = operations.nextSciOperation();
                if (operation.hasProperty(SciProperty.PRIVATE) ||
                    operation.hasProperty(SciProperty.CONSTRUCTOR) ||
                    operation.hasProperty(SciProperty.DESTRUCTOR)) {
                    continue;
                }

                clientOperation = (SciOperation) SciUtil.findMemberBySignature(myClientClass, operation);
                if (clientOperation == null) {
                    clientOperation = (SciOperation) myClientClass.paste(operation, null, false);
                    SciPatternUtil.makeDelegatedImplementation(clientOperation, attributeName, true);
                }
                else {
                    SciCodeBlock body = clientOperation.getBody();
                    body.visitExpressions(new SciExpressionVisitorAdapter() {
                        public Object visitReferenceExpression(SciReferenceExpression expression) {
                            if ("super".equals(expression.getText())) {
                                expression.replace(attributeExpression);
                            }
                            return null;
                        }
                    });

                    clientOperation.setBody(body);
                }
            }
        }
        catch(SciGenericFactoryException e) {
            return;
        }

        // Remove inheritance links from Client to all Concrete Objectifiers.
        SciInheritanceEnumeration inheritances = myClientClass.getExtendsList().inheritances();
        while (inheritances.hasMoreElements()) {
            SciInheritance inheritance = inheritances.nextSciInheritance();
            if (myConcreteObjectifierClass.equals(inheritance.getReferencedElement())) {
                inheritance.delete();
                continue;
            }

            for (int i = 0; i < concreteObjectifiersVector.size(); i++) {
                if (((SciClass)concreteObjectifiersVector.elementAt(i)).equals(inheritance.getReferencedElement())) {
                    inheritance.delete();
                    break;
                }
            }
        }

        if (myClientClass.getLanguage().equals(SciLanguage.JAVA)) {
            inheritances = myClientClass.getImplementsList().inheritances();
            while (inheritances.hasMoreElements()) {
                SciInheritance inheritance = inheritances.nextSciInheritance();
                for (int i = 0; i < concreteObjectifiersVector.size(); i++) {
                    if (((SciClass)concreteObjectifiersVector.elementAt(i)).equals(inheritance.getReferencedElement())) {
                        inheritance.delete();
                        break;
                    }
                }
            }
        }

        concreteObjectifiersVector.removeAllElements();

        // Write code for initializing reference from client to objectifier depending on selected variant.
        String variant = getStringPropertyValue(INITIALIZATION_VARIANT);
        if (CONSTRUCTOR_PARAMETER.equals(variant)) {
            SciPatternUtil.addAttributeToConstructors(attribute);
        }
        else if (SET_METHOD.equals(variant)) {
            SciPatternUtil.createSetMethod(attribute);
        }
        else if (HARDCODED.equals(variant)) {
            try {
                String expressionText = "new " + myConcreteObjectifierClass.getQualifiedName() + "()";
                SciExpression initialValue = getGenericFactory().newExpression(expressionText);

                if (SciLanguage.JAVA.equals(myClientClass.getLanguage())) {
                    attribute.setInitialValue(initialValue);
                }
                else {
                    // we need to paste
                }
            }
            catch(SciGenericFactoryException e) {
                IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, CODE_GENERATION_ERROR);
            }
        }

        setPropertyValue(SciPatternProperty.RESULT_ELEMENT, attribute);
    }

    protected void initializeInspector() {
        try {
            myUIBuilder = null;
            myUIBuilder = createUIBuilder(true);

            Vector inspectorPropertiesVector = new Vector();

            // Create page
            String pageName1 = "Pattern properties";
            if (myUIBuilder.addInspectorPage(pageName1, PatternUIBuilder.UIPresentation.Table) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            // Add CLIENT property
            if (myUIBuilder.addPropertyToPage(pageName1, CLIENT, CLIENT,
                PatternUIBuilder.PropertyType.String) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(CLIENT);

            // Add OBJECTIFIER property
            if (myUIBuilder.addPropertyToPage(pageName1, OBJECTIFIER, OBJECTIFIER,
                PatternUIBuilder.PropertyType.String) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(OBJECTIFIER);

            // Add ATTRIBUTE property
            if (myUIBuilder.addPropertyToPage(pageName1, ATTRIBUTE, ATTRIBUTE,
                PatternUIBuilder.PropertyType.String) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(ATTRIBUTE);

            // Add EXISTING_OBJECTIFIER property
            if (myUIBuilder.addPropertyToPage(pageName1, EXISTING_OBJECTIFIER, EXISTING_OBJECTIFIER,
                PatternUIBuilder.PropertyType.String) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(EXISTING_OBJECTIFIER);

            // Add CONCRETE_OBJECTIFIER property
            boolean allowInterface = true;
            if (myUIBuilder.addClassPropertyToPage(pageName1, CONCRETE_OBJECTIFIER, CONCRETE_OBJECTIFIER,
                PatternUIBuilder.PropertyType.MultiValue, allowInterface) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(CONCRETE_OBJECTIFIER);

            // Add INITIALIZATION_VARIANT property
            if (myUIBuilder.addPropertyToPage(pageName1, INITIALIZATION_VARIANT, INITIALIZATION_VARIANT,
                PatternUIBuilder.PropertyType.StringChooser, myInitializationVariants) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(INITIALIZATION_VARIANT);

            myUIBuilder.setCreatedPatternUI();
            myUIBuilder.assignListenedInspectorProperties(inspectorPropertiesVector);

            enableItem(CLIENT, false);
            enableItem(EXISTING_OBJECTIFIER, false);
        }
        catch (PatternBaseException ex) {
            myUIBuilder = null;
            getIdeInspector().clear();
            return;
        }
    }

    private SciContainer getClientContainer() {
        return myClientClass.getContainingPackage();
    }

    private PatternUIBuilder myUIBuilder = null;

    private SciClass myClientClass = null;
    private SciClass myConcreteObjectifierClass = null;

    private static final String CLIENT                  = "Client";
    private static final String OBJECTIFIER             = "Objectifier";
    private static final String EXISTING_OBJECTIFIER    = "Existing concrete objectifier";
    private static final String CONCRETE_OBJECTIFIER    = "Concrete objectifiers";

    private static final String myInitializationVariants[] = new String[] { NONE, CONSTRUCTOR_PARAMETER, SET_METHOD, HARDCODED };
}
