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

package com.togethersoft.modules.patterns.UNIVERSAL.CLASS.GoF.Adapter;

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

import com.togethersoft.openapi.sci.*;

import com.togethersoft.openapi.sci.enum.SciTagEnumeration;

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

import com.togethersoft.openapi.util.propertyMap.Property;
import com.togethersoft.openapi.util.propertyMap.PropertyMapListener;
import com.togethersoft.openapi.util.propertyMap.PropertyMapEvent;
import com.togethersoft.openapi.util.propertyMap.PropertyMapAdapter;

import com.togethersoft.modules.patterns.UNIVERSAL.CLASS.ClassPatternBase;
import com.togethersoft.modules.patterns.UNIVERSAL.PatternBaseException;
import com.togethersoft.modules.patterns.UNIVERSAL.PatternUIBuilder;

import com.togethersoft.openapi.ide.window.IdeSelectorDialogType;
import com.togethersoft.openapi.ide.inspector.util.editors.SingleElementChooserEditor;


/**
 * GoF Adapter pattern.
 * @author TogetherSoft
 */
public class Pattern extends ClassPatternBase implements SciPattern {

    private static final String PATTERN_NAME =  "Adapter";

    public Pattern() {
        setPatternDisplayName(PATTERN_NAME);

        setPropertyValue(SELECTED_CLASS_ROLE, null);
        setPropertyValue(ADAPTEE, ADAPTEE);
        setPropertyValue(TARGET, TARGET);
        setPropertyValue(ADAPTER, ADAPTER);
        setPropertyValue(VERSION, OBJECT); // can be OBJECT/CLASS
        setPropertyValue(ATTRIBUTE, ADAPTEE.toLowerCase());
        setPropertyValue(INITIALIZATION_VARIANT, CONSTRUCTOR_PARAMETER); // NONE/CONSTRUCTOR_PARAMETER/SET_METHOD
        setPropertyValue(EXPORT_DOCUMENTATION, Boolean.FALSE);
        setPropertyValue(CREATE_PATTERN_LINKS, Boolean.TRUE);

        assignActivityResponse();
    }

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

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

        myClassRoleVector.removeAllElements();
        myClassRoleVector = null;

        VERSION_ITEMS = null;
    }

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

            setPropertyValue(SELECTED_CLASS_ROLE, null);
            setPropertyValue(ADAPTEE, ADAPTEE);
            setPropertyValue(TARGET, TARGET);
            setPropertyValue(ADAPTER, ADAPTER);
            setPropertyValue(VERSION, OBJECT);
            setPropertyValue(ATTRIBUTE, ADAPTEE.toLowerCase());
            setPropertyValue(INITIALIZATION_VARIANT, CONSTRUCTOR_PARAMETER);

            myClassRoleVector.removeAllElements();

            if (getSelectedClass() == null) {
            }
            else {
                // Fill class roles
                myClassRoleVector.addElement(ADAPTEE);
                myClassRoleVector.addElement(TARGET);
                if (!getSelectedClass().hasProperty(SciProperty.INTERFACE)) {
                    myClassRoleVector.addElement(ADAPTER);
                }

                String currentRole = null;
                SciClass adapterClass = SciPatternUtil.findPatternLinkSupplier(getSelectedClass(), PATTERN_NAME, ADAPTER);
                if (adapterClass != null) {
                    if (SciPatternUtil.patternLinkExists(adapterClass, getSelectedClass(), PATTERN_NAME, ADAPTEE)) {
                        currentRole = ADAPTEE;
                    }
                    else {
                        currentRole = TARGET;
                    }
                }
                else {
                    if (getSelectedClass().hasProperty(SciProperty.INTERFACE)) {
                        currentRole = TARGET;
                    }
                    else {
                        currentRole = ADAPTER;
                    }
                }
                setPropertyValue(SELECTED_CLASS_ROLE, currentRole);
            }

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

    public boolean canApply() {
        try {
            // check class names
            String   adapteeName = classNameShouldBeCorrect(ADAPTEE);
            SciClass adapteeClass = findClassByName(adapteeName);

            String   targetName = classNameShouldBeCorrect(TARGET);
            SciClass targetClass = findClassByName(targetName);

            String   adapterName = classNameShouldBeCorrect(ADAPTER);
            SciClass adapterClass = findClassByName(adapterName);

            cannotBeAnInterface(ADAPTER, adapterClass);
            shouldBeWritable(ADAPTER, adapterClass);

            if (isClassVersion()) {
                if (SciLanguage.JAVA.equals(getLanguage()) &&
                    (targetClass != null) &&
                    !targetClass.hasProperty(SciProperty.INTERFACE)) {
                  throw new PatternBaseException("In 'Class' version of the pattern Target should be an interface");
                }

                if (adapteeClass != null) {
                  shouldExtend(adapterClass, adapteeClass);
                }
                else {
                  shouldExtend(adapterClass, adapteeName);
                }
            }
            else {
                String attributeName = identifierShouldBeCorrect(ATTRIBUTE);

                if (adapteeClass != null) {
                  checkAttributeName(attributeName, adapterClass, adapteeClass);
                }
                else {
                  checkAttributeName(attributeName, adapterClass, adapteeName);
                }

                if (targetClass != null) {
                  shouldExtend(adapterClass, targetClass);
                }
                else {
                  shouldExtend(adapterClass, targetName);
                }
            }

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

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

        myClassRoleVector.removeAllElements();
        myUIBuilder.removeAllListenedInspectorProperties();
        myUIBuilder = null;

        boolean classVersion = isClassVersion();

/* Get or create adaptee class. */

        String adapteeName = getStringPropertyValue(ADAPTEE);
        SciClass adapteeClass = getClassByName(adapteeName, false);

/* Get or create adapter class. Create inheritance from adaptee (class version)
   or attribute (object version), if this inheritance doesn't exist.*/

        String adapterName = getStringPropertyValue(ADAPTER);
        SciClass adapterClass = getClassByName(adapterName, false);

        String attributeName = getStringPropertyValue(ATTRIBUTE);
        SciAttribute attribute = null;
        if (classVersion) {
            SciPatternUtil.createInheritance(adapterClass, adapteeClass);
        }
        else {

            attribute = SciPatternUtil.createAttribute(adapterClass, adapteeClass, attributeName, false);
            attribute.setType( getLanguageHelper().makePointerType(attribute.getType()) );
        }

        if (createPatternLinks()) {
            // addPatternLink(SciClass source, SciClass target, String patternName, String clientRole, String supplierRole, boolean hidden)
            SciPatternUtil.addPatternLink(adapteeClass, adapterClass, PATTERN_NAME, ADAPTEE, ADAPTER, false);
            SciPatternUtil.addPatternLink(adapterClass, adapteeClass, PATTERN_NAME, null, ADAPTEE, true);
        }

/* Get or create target class.
   Create inheritance to adapter, if this inheritance doesn't exist. */

        String targetName = getStringPropertyValue(TARGET);
        SciClass targetClass = getClassByName(targetName, true);
        SciPatternUtil.createInheritance(adapterClass, targetClass);
        if (createPatternLinks()) {
            SciPatternUtil.addPatternLink(targetClass, adapterClass, PATTERN_NAME, TARGET, ADAPTER, /*hidden=*/false);
            SciPatternUtil.addPatternLink(adapterClass, targetClass, PATTERN_NAME, null, TARGET, /*hidden=*/true);
        }

/* Create sample operation, if all classes are new. */

        if (myAllClassesAreNew) {
            SciOperation sampleOperation = getFactory().newOperation();
            sampleOperation.setName("sampleOperation1");
            SciOperation sampleOperation1 = (SciOperation)targetClass.paste(sampleOperation, null, false);
            sampleOperation1.setProperty(SciProperty.ABSTRACT, true);
            sampleOperation1.getTagList().setTagValue(null, "Class Adaptee contains operation sampleOperation1.");
            ((SciOperation)adapteeClass.paste(sampleOperation, null, false)).setProperty(SciProperty.ABSTRACT, false);

            sampleOperation.setName("sampleOperation2");
            SciOperation sampleOperation2 = (SciOperation)targetClass.paste(sampleOperation, null, false);
            sampleOperation2.setProperty(SciProperty.ABSTRACT, true);
            sampleOperation2.getTagList().setTagValue(null, "Class Adaptee doesn't contain operation sampleOperation2.");
        }

/* Create operations in adapter class. Set body for these operations. */

        Enumeration targetOperations = SciUtil.allMembers(targetClass, true);
        while (targetOperations.hasMoreElements()) {
            Object element = targetOperations.nextElement();
            if (!(element instanceof SciOperation)) {
                continue;
            }
            SciOperation targetOperation = (SciOperation)element;
            if (!targetOperation.hasProperty(SciProperty.ABSTRACT)) {
                continue;
            }
            SciOperation adapteeOperation = (SciOperation)SciUtil.findMemberBySignature(adapteeClass, targetOperation, true);
            if (adapteeOperation != null &&
                (adapteeOperation.hasProperty(SciProperty.ABSTRACT) || adapteeOperation.hasProperty(SciProperty.PRIVATE))) {
                adapteeOperation = null;
            }

            if (SciUtil.findMemberBySignature(adapterClass, targetOperation) == null) {
                if (classVersion && adapteeOperation != null) {
                    continue;
                }

                SciOperation adapterOperation = (SciOperation)adapterClass.paste(targetOperation, null, false);
                if (!exportDocumentation()) {
                    SciTagEnumeration tags = adapterOperation.getTagList().tags();
                    while (tags.hasMoreElements()) {
                        tags.nextSciTag().setValue(null);
                    }
                }

                adapterOperation.setProperty(SciProperty.ABSTRACT, false);
                if (adapteeOperation == null) {
                    SciPatternUtil.makeStubImplementation(adapterOperation);
                }
                else {
                    SciPatternUtil.makeDelegatedImplementation(adapterOperation, getStringPropertyValue(ATTRIBUTE), false);
                }
            }
        }

/* Add initialization in Adapter class. */

        if (!classVersion) {
            String initializationVariant = getStringPropertyValue(INITIALIZATION_VARIANT);
            //String attributeName = getStringPropertyValue(ATTRIBUTE);
            if (CONSTRUCTOR_PARAMETER.equals(initializationVariant)) {
                SciPatternUtil.createStubConstructors(adapterClass, targetClass);
                SciPatternUtil.addAttributeToConstructors(attribute);
            }
            else if (SET_METHOD.equals(initializationVariant)) {
                SciPatternUtil.createSetMethod(attribute);
            }
        }
    }

    protected void allowAllClassesInTarget(boolean allowClasses) {
        int selectorType = allowClasses ? IdeSelectorDialogType.CLASSES_AND_INTERFACES : IdeSelectorDialogType.INTERFACES;
        setInspectorPropertyEditor(TARGET, new SingleElementChooserEditor(null, selectorType));
    }

    private boolean isObjectVersion() {
        return OBJECT.equals(getStringPropertyValue(VERSION));
    }

    private boolean isClassVersion() {
        return CLASS.equals(getStringPropertyValue(VERSION));
    }

    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 SELECTED_CLASS_ROLE property
            if (getStringPropertyValue(SELECTED_CLASS_ROLE) != null) {
                if (myUIBuilder.addPropertyToPage(pageName1, SELECTED_CLASS_ROLE, SELECTED_CLASS_ROLE,
                    PatternUIBuilder.PropertyType.StringChooser, myClassRoleVector.toArray()) == null) {
                    throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
                }
                inspectorPropertiesVector.addElement(SELECTED_CLASS_ROLE);
            }

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

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

            // Add ADAPTER property
            allowInterface = false;
            if (myUIBuilder.addClassPropertyToPage(pageName1, ADAPTER, ADAPTER,
                PatternUIBuilder.PropertyType.SingleValue, allowInterface) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(ADAPTER);

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

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

            // Add INITIALIZATION_VARIANT property
            String[] items2 = new String[] { NONE, CONSTRUCTOR_PARAMETER, SET_METHOD };
            if (myUIBuilder.addPropertyToPage(pageName1, INITIALIZATION_VARIANT, INITIALIZATION_VARIANT,
                PatternUIBuilder.PropertyType.StringChooser, items2) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(INITIALIZATION_VARIANT);

            // Add EXPORT_DOCUMENTATION property
            if (myUIBuilder.AddExportDocumentationProperty(pageName1) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(EXPORT_DOCUMENTATION);

            // Add "Create pattern links" property
            if (myUIBuilder.AddCreatePatternLinksProperty(pageName1) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(CREATE_PATTERN_LINKS);

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

            enableItem(VERSION, true);
            enableItem(INITIALIZATION_VARIANT, isObjectVersion());
        }
        catch (PatternBaseException ex) {
            myUIBuilder = null;
            getIdeInspector().clear();
            return;
       }
    }

    protected void assignActivityResponse() {
        addPropertyMapListener(SELECTED_CLASS_ROLE, new PropertyMapListener() {
            // SELECTED_CLASS_ROLE changed
            public void propertiesChanged(PropertyMapEvent event) {
                SciClass adapteeClass = null;
                SciClass targetClass = null;
                SciClass adapterClass = null;
                String role = getStringPropertyValue(SELECTED_CLASS_ROLE);

                if (role != null) {
                    if (role.equals(ADAPTEE)) {
                        adapteeClass = getSelectedClass();
                        adapterClass = SciPatternUtil.findPatternLinkSupplier(adapteeClass, PATTERN_NAME, ADAPTER);
                        targetClass = SciPatternUtil.findPatternLinkSupplier(adapterClass, PATTERN_NAME, TARGET);
                    }
                    else if (role.equals(TARGET)) {
                        targetClass = getSelectedClass();
                        adapterClass = SciPatternUtil.findPatternLinkSupplier(targetClass, PATTERN_NAME, ADAPTER);
                        adapteeClass = SciPatternUtil.findPatternLinkSupplier(adapterClass, PATTERN_NAME, ADAPTEE);
                    }
                    else if (role.equals(ADAPTER)) {
                        adapterClass = getSelectedClass();
                        adapteeClass = SciPatternUtil.findPatternLinkSupplier(adapterClass, PATTERN_NAME, ADAPTEE);
                        targetClass = SciPatternUtil.findPatternLinkSupplier(adapterClass, PATTERN_NAME, TARGET);
                    }
                }

                if (adapteeClass != null) {
                    setPropertyValue(ADAPTEE, adapteeClass.getQualifiedName());
                }
                if (adapterClass != null) {
                    setPropertyValue(ADAPTER, adapterClass.getQualifiedName());
                }
                if (targetClass != null) {
                    setPropertyValue(TARGET, targetClass.getQualifiedName());
                }
                if (adapterClass != null && adapteeClass != null) {
                    SciAttribute attribute = SciPatternUtil.findAttribute(adapterClass, adapteeClass);
                    if (attribute != null) {
                        setPropertyValue(VERSION, OBJECT);
                        setPropertyValue(ATTRIBUTE, attribute.getName());
                        setPropertyValue(INITIALIZATION_VARIANT, NONE);
                    }
                    else {
                        setPropertyValue(VERSION, CLASS);
                    }

                    enableItem(INITIALIZATION_VARIANT, false);
                    enableItem(VERSION, false);
                }
                else {
                    enableItem(INITIALIZATION_VARIANT, isObjectVersion());
                    enableItem(VERSION, true);
                }
            }
        });

        addPropertyMapListener(VERSION, new PropertyMapAdapter() {
            // property VERSION handler
            public void propertiesChanged(PropertyMapEvent event) {
                enableItem(INITIALIZATION_VARIANT, isObjectVersion());
                enableItem(ATTRIBUTE, isObjectVersion());
                allowAllClassesInTarget(isObjectVersion());
            }
        });

    }

    private PatternUIBuilder    myUIBuilder = null;
    private Vector              myClassRoleVector = new Vector();

    private static final String ADAPTER = "Adapter";
    private static final String ADAPTEE = "Adaptee";
    private static final String TARGET  = "Target";
    private static final String VERSION = "Version";
    private static final String CLASS   = "Class";
    private static final String OBJECT  = "Object";

    private String[] VERSION_ITEMS = new String[] { OBJECT, CLASS };
}
