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

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

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

import com.togethersoft.openapi.sci.*;

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.openapi.ide.message.IdeMessageManagerAccess;
import com.togethersoft.openapi.ide.message.IdeMessageType;

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 Visitor pattern.
 * @author TogetherSoft
 */
public class Pattern extends ClassPatternBase implements SciPattern {

    private static final String PATTERN_NAME = "Visitor";

    public Pattern() {
        setPatternDisplayName(PATTERN_NAME);

        setPropertyValue(SELECTED_CLASS_ROLE, null);
        setPropertyValue(VISITOR, VISITOR);
        setPropertyValue(CREATE_VISITOR_ADAPTER, Boolean.FALSE);
        setPropertyValue(VISITOR_ADAPTER, "VisitorAdapter");
        setPropertyValue(CONCRETE_VISITOR, null);
        setPropertyValue(ELEMENT, ELEMENT);
        setPropertyValue(CONCRETE_ELEMENT, null);
        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;
    }

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

            setPropertyValue(SELECTED_CLASS_ROLE, null);
            setPropertyValue(VISITOR, VISITOR);
            setPropertyValue(CREATE_VISITOR_ADAPTER, Boolean.FALSE);
            setPropertyValue(VISITOR_ADAPTER, "VisitorAdapter");
            setPropertyValue(CONCRETE_VISITOR, null);
            setPropertyValue(ELEMENT, ELEMENT);
            setPropertyValue(CONCRETE_ELEMENT, null);

            myClassRoleVector.removeAllElements();

            if (getSelectedClass() == null) {
                addPropertyValue(CONCRETE_ELEMENT, "ConcreteElement");
            }
            else {
                // Fill class roles
                myClassRoleVector.addElement(VISITOR);
                myClassRoleVector.addElement(ELEMENT);

                if (!getSelectedClass().hasProperty(SciProperty.INTERFACE)) {
                    myClassRoleVector.addElement(VISITOR_ADAPTER);
                    myClassRoleVector.addElement(CONCRETE_VISITOR);
                    myClassRoleVector.addElement(CONCRETE_ELEMENT);
                }

                String currentRole = null;
                if (SciPatternUtil.findExtendedPatternLinkClient(getSelectedClass(), PATTERN_NAME, CONCRETE_ELEMENT) != null) {
                    currentRole = CONCRETE_ELEMENT;
                }
                else if (SciPatternUtil.findExtendedPatternLinkClient(getSelectedClass(), PATTERN_NAME, CONCRETE_VISITOR) != null) {
                    currentRole = CONCRETE_VISITOR;
                }
                else if (SciPatternUtil.findExtendedPatternLinkClient(getSelectedClass(), PATTERN_NAME, VISITOR_ADAPTER) != null) {
                    currentRole = VISITOR_ADAPTER;
                }
                else if (SciPatternUtil.findPatternLinkSupplier(getSelectedClass(), PATTERN_NAME, ELEMENT) != null) {
                    currentRole = VISITOR;
                }
                else {
                    currentRole = ELEMENT;
                }
                setPropertyValue(SELECTED_CLASS_ROLE, currentRole);
            }

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

    public boolean canApply() {
        try {
            String visitorName = classNameShouldBeCorrect(VISITOR);
            SciClass visitorClass = findClassByName(visitorName);
            shouldBeWritable(VISITOR, visitorClass);

            boolean createVisitorAdapter = !myVisitorIsNotAnInterface && getBooleanPropertyValue(CREATE_VISITOR_ADAPTER);
            if (createVisitorAdapter) {
                if (getLanguage().equals(SciLanguage.JAVA) &&
                    visitorClass != null &&
                    !visitorClass.hasProperty(SciProperty.INTERFACE)) {
                    throw new PatternBaseException("Could not create adapter when visitor is not an interface");
                }

                String adapterName = classNameShouldBeCorrect(VISITOR_ADAPTER);
                SciClass adapterClass = findClassByName(adapterName);
                cannotBeAnInterface(VISITOR_ADAPTER, adapterClass);
                shouldBeWritable(VISITOR_ADAPTER, adapterClass);
            }

            String elementName = classNameShouldBeCorrect(ELEMENT);
            SciClass elementClass = findClassByName(elementName);
            checkConcreteElements(visitorClass, CONCRETE_VISITOR, false);
            checkConcreteElements(elementClass, CONCRETE_ELEMENT, false);
            return true;
        }
        catch(PatternBaseException e) {
            return false;
        }
    }

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

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

        boolean createVisitorAdapter = !myVisitorIsNotAnInterface && getBooleanPropertyValue(CREATE_VISITOR_ADAPTER);

        String elementName = getStringPropertyValue(ELEMENT);
        SciClass elementClass = getClassByName(elementName, true);
        elementClass.setProperty(SciProperty.ABSTRACT, true);

        String visitorName = getStringPropertyValue(VISITOR);
        SciClass visitorClass = getClassByName(visitorName, true);
        visitorClass.setProperty(SciProperty.ABSTRACT, true);

        if (createPatternLinks()) {
            SciPatternUtil.addPatternLink(visitorClass, elementClass, PATTERN_NAME, VISITOR, ELEMENT, false);
            SciPatternUtil.addPatternLink(elementClass, visitorClass, PATTERN_NAME, null, VISITOR, true);
        }

        SciOperation acceptOperation = getFactory().newOperation();
        acceptOperation.setName(ACCEPT);

        SciParameter parameter = getFactory().newParameter();
        parameter.setName(VISITOR.toLowerCase());
        parameter.getType().setReferencedElement(visitorClass);
        parameter.setType( getLanguageHelper().makePointerType(parameter.getType()) ); // [VZ]

        acceptOperation.getParameterList().paste(parameter, null, false);

        acceptOperation.setProperty(SciProperty.ABSTRACT, true);
        if (SciUtil.findMemberBySignature(elementClass, acceptOperation) == null) {
            elementClass.paste(acceptOperation, null, false);
        }
        acceptOperation.setProperty(SciProperty.ABSTRACT, false);

        Enumeration concreteElementEnum = propertiesEnumeration(CONCRETE_ELEMENT);
        while (concreteElementEnum.hasMoreElements()) {
            String concreteElementName = (String) ((Property)concreteElementEnum.nextElement()).getValue();
            if (concreteElementName == null || concreteElementName.length() == 0) {
                continue;
            }

            SciClass concreteElementClass = getClassByName(concreteElementName, false);
            SciPatternUtil.createInheritance(concreteElementClass, elementClass);

            try {
                String accessor = SciLanguage.CPP.equals(getLanguage()) ? "->" : ".";
                String code = "visitor" + accessor + PREFIX_VISIT + concreteElementClass.getName() + "(this);\n";
                acceptOperation.setBody(getGenericFactory().newCodeBlock(code));
            }
            catch(SciGenericFactoryException e) {
                IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, CODE_GENERATION_ERROR);
            }

            if (SciUtil.findMemberBySignature(concreteElementClass, acceptOperation) == null) {
                concreteElementClass.paste(acceptOperation, null, false);
            }

            SciPatternUtil.makeStubImplementation(concreteElementClass, elementClass, false, exportDocumentation());
            if (createPatternLinks()) {
                SciPatternUtil.addPatternLink(elementClass, concreteElementClass, PATTERN_NAME, null, CONCRETE_ELEMENT, false);
            }

            SciOperation visitOperation = getFactory().newOperation();
            visitOperation.setName(PREFIX_VISIT + concreteElementClass.getName());
            visitOperation.setProperty(SciProperty.ABSTRACT, true);

            SciParameter visitParameter = getFactory().newParameter();
            visitParameter.setName(ELEMENT);
            visitParameter.getType().setReferencedElement(concreteElementClass);
            visitParameter.setType( getLanguageHelper().makePointerType(visitParameter.getType()) ); // [VZ]

            visitOperation.getParameterList().paste(visitParameter, null, false);

            if (SciUtil.findMemberBySignature(visitorClass, visitOperation) == null) {
                visitOperation = (SciOperation) visitorClass.paste(visitOperation, null, false);
                visitOperation.setProperty(SciProperty.ABSTRACT, true);
            }
        }

        if (createVisitorAdapter) {
            String visitorAdapterName = getStringPropertyValue(VISITOR_ADAPTER);
            SciClass visitorAdapterClass = getClassByName(visitorAdapterName, false);
            SciPatternUtil.makeStubImplementation(visitorAdapterClass, visitorClass, false, exportDocumentation());
            if (createPatternLinks()) {
                SciPatternUtil.addPatternLink(visitorClass, visitorAdapterClass, PATTERN_NAME, null, VISITOR_ADAPTER, false);
            }

            visitorClass = visitorAdapterClass;
            visitorClass.setProperty(SciProperty.ABSTRACT, true);
        }

        Enumeration concreteVisitorEnum = propertiesEnumeration(CONCRETE_VISITOR);
        while (concreteVisitorEnum.hasMoreElements()) {
            String concreteVisitorName = (String) ((Property)concreteVisitorEnum.nextElement()).getValue();
            if (concreteVisitorName == null || concreteVisitorName.length() == 0) {
                continue;
            }

            SciClass concreteVisitorClass = getClassByName(concreteVisitorName, false);
            SciPatternUtil.makeStubImplementation(concreteVisitorClass, visitorClass, false, exportDocumentation());
            if (createPatternLinks()) {
                SciPatternUtil.addPatternLink(visitorClass, concreteVisitorClass, PATTERN_NAME, null, CONCRETE_VISITOR, false);
            }
        }
    }

    protected void allowAllClassesInVisitor(boolean allow) {
        int selectorType = (allow) ? IdeSelectorDialogType.CLASSES_AND_INTERFACES : IdeSelectorDialogType.INTERFACES;
        setInspectorPropertyEditor(VISITOR, new SingleElementChooserEditor(null, selectorType));
    }

    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 VISITOR property
            boolean allowInterface = true;
            if (myUIBuilder.addClassPropertyToPage(pageName1, VISITOR, VISITOR,
                PatternUIBuilder.PropertyType.SingleValue, allowInterface) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(VISITOR);

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

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

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

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

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

            // 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(CREATE_VISITOR_ADAPTER, true);
            enableItem(VISITOR_ADAPTER, getBooleanPropertyValue(CREATE_VISITOR_ADAPTER));
        }
        catch (PatternBaseException ex) {
            myUIBuilder = null;
            getIdeInspector().clear();
            return;
       }
    }

    protected void assignActivityResponse() {
        addPropertyMapListener(CREATE_VISITOR_ADAPTER, new PropertyMapAdapter() {
            public void propertiesChanged(PropertyMapEvent event) {
                boolean createVisitorAdapter = getBooleanPropertyValue(CREATE_VISITOR_ADAPTER);
                enableItem(VISITOR_ADAPTER, createVisitorAdapter);
                allowAllClassesInVisitor(!createVisitorAdapter);
            }
        });

        addPropertyMapListener(VISITOR, new PropertyMapAdapter() {
            public void propertiesChanged(PropertyMapEvent event) {
                String visitorName = getStringPropertyValue(VISITOR);
                if (visitorName == null || visitorName.equals(EMPTY_STRING)) {
                    return;
                }
                SciClass visitorClass = Pattern.this.findClassByName(visitorName);
                if (myVisitorIsNotAnInterface != (visitorClass != null && !visitorClass.hasProperty(SciProperty.INTERFACE))) {
                    enableItem(CREATE_VISITOR_ADAPTER, myVisitorIsNotAnInterface);
                    enableItem(VISITOR_ADAPTER, myVisitorIsNotAnInterface);
                    myVisitorIsNotAnInterface = !myVisitorIsNotAnInterface;
                }
            }
        });

        addPropertyMapListener(SELECTED_CLASS_ROLE, new PropertyMapListener() {
            public void propertiesChanged(PropertyMapEvent event) {
                SciClass visitorClass = null;
                SciClass visitorAdapterClass = null;
                SciClass elementClass = null;

                String role = getStringPropertyValue(SELECTED_CLASS_ROLE);
                if (role != null) {
                    if (role.equals(VISITOR)) {
                        visitorClass = getSelectedClass();
                        visitorAdapterClass = SciPatternUtil.findPatternLinkSupplier(visitorClass, PATTERN_NAME, VISITOR_ADAPTER);
                        elementClass = SciPatternUtil.findPatternLinkSupplier(visitorClass, PATTERN_NAME, ELEMENT);
                    }
                    else if (role.equals(ELEMENT)) {
                        elementClass = getSelectedClass();
                        visitorClass = SciPatternUtil.findPatternLinkSupplier(elementClass, PATTERN_NAME, VISITOR);
                        visitorAdapterClass = SciPatternUtil.findPatternLinkSupplier(visitorClass, PATTERN_NAME, VISITOR_ADAPTER);
                    }
                    else if (role.equals(VISITOR_ADAPTER)) {
                        visitorAdapterClass = getSelectedClass();
                        visitorClass = SciPatternUtil.findExtendedPatternLinkClient(visitorAdapterClass, PATTERN_NAME, VISITOR_ADAPTER);
                        elementClass = SciPatternUtil.findPatternLinkSupplier(visitorClass, PATTERN_NAME, ELEMENT);
                    }
                    else if (role.equals(CONCRETE_VISITOR)) {
                        visitorAdapterClass = SciPatternUtil.findExtendedPatternLinkClient(getSelectedClass(), PATTERN_NAME, CONCRETE_VISITOR);
                        if (visitorAdapterClass == null) {
                            setPropertyValue(CONCRETE_VISITOR, null);
                            addPropertyValue(CONCRETE_VISITOR, getSelectedClass().getQualifiedName());
                        }
                        else {
                            visitorClass = SciPatternUtil.findExtendedPatternLinkClient(visitorAdapterClass, PATTERN_NAME, VISITOR_ADAPTER);
                            if (visitorClass == null) {
                                visitorClass = visitorAdapterClass;
                                visitorAdapterClass = null;
                            }
                            elementClass = SciPatternUtil.findPatternLinkSupplier(visitorClass, PATTERN_NAME, ELEMENT);
                        }
                    }
                    else if (role.equals(CONCRETE_ELEMENT)) {
                        elementClass = SciPatternUtil.findExtendedPatternLinkClient(getSelectedClass(), PATTERN_NAME, CONCRETE_ELEMENT);
                        if (elementClass == null) {
                            setPropertyValue(CONCRETE_ELEMENT, null);
                            addPropertyValue(CONCRETE_ELEMENT, getSelectedClass().getQualifiedName());
                        }
                        else {
                            visitorClass = SciPatternUtil.findPatternLinkSupplier(elementClass, PATTERN_NAME, VISITOR);
                            visitorAdapterClass = SciPatternUtil.findPatternLinkSupplier(visitorClass, PATTERN_NAME, VISITOR_ADAPTER);
                        }
                    }
                }

                if (visitorClass != null) {
                    setPropertyValue(VISITOR, visitorClass.getQualifiedName());
                    setPropertyValue(CREATE_VISITOR_ADAPTER, Boolean.FALSE);
                    enableItem(CREATE_VISITOR_ADAPTER, false);
                    if (visitorAdapterClass == null) {
                        Enumeration concreteVisitorEnumeration =
                            SciPatternUtil.findPatternLinkSuppliers(visitorClass, PATTERN_NAME, CONCRETE_VISITOR);
                        if (concreteVisitorEnumeration.hasMoreElements()) {
                            setPropertyValue(CONCRETE_VISITOR, null);
                            while (concreteVisitorEnumeration.hasMoreElements()) {
                                addPropertyValue(CONCRETE_VISITOR, ((SciClass)concreteVisitorEnumeration.nextElement()).getQualifiedName());
                            }
                        }
                        enableItem(VISITOR_ADAPTER, false);
                    }
                    else {
                        enableItem(VISITOR_ADAPTER, true);
                        setPropertyValue(CREATE_VISITOR_ADAPTER, Boolean.TRUE);
                    }
                }

                if (elementClass != null) {
                    setPropertyValue(ELEMENT, elementClass.getQualifiedName());
                    Enumeration concreteElementEnumeration = SciPatternUtil.findPatternLinkSuppliers(elementClass, PATTERN_NAME, CONCRETE_ELEMENT);
                    if (concreteElementEnumeration.hasMoreElements()) {
                        setPropertyValue(CONCRETE_ELEMENT, null);
                        while (concreteElementEnumeration.hasMoreElements()) {
                            addPropertyValue(CONCRETE_ELEMENT, ((SciClass)concreteElementEnumeration.nextElement()).getQualifiedName());
                        }
                    }
                }

                if (visitorAdapterClass != null) {
                    setPropertyValue(VISITOR_ADAPTER, visitorAdapterClass.getQualifiedName());
                    Enumeration concreteVisitorEnumeration =
                        SciPatternUtil.findPatternLinkSuppliers(visitorAdapterClass, PATTERN_NAME, CONCRETE_VISITOR);
                    if (concreteVisitorEnumeration.hasMoreElements()) {
                        setPropertyValue(CONCRETE_VISITOR, null);
                        while (concreteVisitorEnumeration.hasMoreElements()) {
                            addPropertyValue(CONCRETE_VISITOR, ((SciClass)concreteVisitorEnumeration.nextElement()).getQualifiedName());
                        }
                    }
                }
            }
        });
    }

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

    private boolean myVisitorIsNotAnInterface = false;

    // Property keys
    private static final String VISITOR             = "Visitor";
    private static final String VISITOR_ADAPTER     = "Visitor adapter";
    private static final String CONCRETE_VISITOR    = "Concrete visitors";
    private static final String CONCRETE_ELEMENT    = "Concrete elements";
    private static final String CREATE_VISITOR_ADAPTER = "Create visitor adapter";

    // Operation names
    private static final String ELEMENT             = "Element";
    private static final String ACCEPT              = "accept";
}
