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

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

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.pattern.SciPattern;
import com.togethersoft.openapi.sci.pattern.SciPatternUtil;

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

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

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

    private static final String PATTERN_NAME = "Singleton";

    public Pattern() {
        setPatternDisplayName(PATTERN_NAME);
    }

    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(SINGLETON, SINGLETON);
            setPropertyValue(SINGLETON_FACTORY, SINGLETON);
            setPropertyValue(UNIQUE_INSTANCE_ATTRIBUTE, INSTANCE);
            setPropertyValue(INSTANCE_METHOD, GET_INSTANCE);
            setPropertyValue(GENERATE_THREAD_SAFE_CODE, Boolean.FALSE);
            setPropertyValue(CREATE_PATTERN_LINKS, Boolean.TRUE);

            myClassRoleVector.removeAllElements();

            if (getSelectedClass() != null) {
                cannotApplyToInterface(getSelectedClass());

                myClassRoleVector.addElement(SINGLETON);
                myClassRoleVector.addElement(SINGLETON_FACTORY);

                if (SciPatternUtil.findAssociatedPatternLinkClient(getSelectedClass(), PATTERN_NAME, SINGLETON_FACTORY) != null) {
                    setPropertyValue(SELECTED_CLASS_ROLE, SINGLETON_FACTORY);
                }
                else {
                    setPropertyValue(SELECTED_CLASS_ROLE, SINGLETON);
                }
            }

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

    public boolean canApply() {
        try {
            String singletonName    = classNameShouldBeCorrect(SINGLETON);
            SciClass singletonClass = findClassByName(singletonName);
            cannotBeAnInterface(SINGLETON, singletonClass);
            shouldBeWritable(SINGLETON, singletonClass);

            String singletonFactoryName     = classNameShouldBeCorrect(SINGLETON_FACTORY);
            SciClass singletonFactoryClass  = findClassByName(singletonFactoryName);
            cannotBeAnInterface(SINGLETON_FACTORY, singletonFactoryClass);
            shouldBeWritable(SINGLETON_FACTORY, singletonFactoryClass);

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

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

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

    /* Get or create Singleton class. */

        String singletonName = getStringPropertyValue(SINGLETON);
        SciClass singletonClass = getClassByName(singletonName, false);

    /* Get or create Singleton Factory class. */

        String singletonFactoryName = getStringPropertyValue(SINGLETON_FACTORY);
        SciClass singletonFactoryClass = getClassByName(singletonFactoryName, false);

        if (createPatternLinks()) {
            SciPatternUtil.addPatternLink(singletonClass, singletonFactoryClass, PATTERN_NAME, null, SINGLETON_FACTORY, false);
        }

        if (singletonClass.equals(singletonFactoryClass)) {
            boolean singletonAlreadyHasConstructors = false;
            SciOperation operation = null;
            SciOperationEnumeration operations = singletonClass.operations();
            while (operations.hasMoreElements()) {
                operation = operations.nextSciOperation();
                if (operation.hasProperty(SciProperty.CONSTRUCTOR)) {
                    singletonAlreadyHasConstructors = true;
                    if (!operation.hasProperty(SciProperty.PRIVATE)) {
                        operation.setProperty(SciProperty.PROTECTED, true);
                    }
                }
            }

            if (!singletonAlreadyHasConstructors) {
                SciOperation constructor = getFactory().newOperation();
                constructor.setProperty(SciProperty.CONSTRUCTOR, true);
                constructor.setProperty(SciProperty.PROTECTED, true);
                singletonClass.paste(constructor, null, false);
            }
        }

        String instanceName = getStringPropertyValue(UNIQUE_INSTANCE_ATTRIBUTE);
        String getInstanceOperationName = getStringPropertyValue(INSTANCE_METHOD);

        try {
            String nullName = getLanguageSpecificNullStatement();

            SciAttribute instanceAttribute = SciPatternUtil.createAttribute(singletonFactoryClass, singletonClass, instanceName, false);
            instanceAttribute.setInitialValue( getGenericFactory().newExpression(nullName) );
            instanceAttribute.setProperty(SciProperty.STATIC, true);
            instanceAttribute.setType( getLanguageHelper().makePointerType(instanceAttribute.getType()) );

            SciOperation getInstanceOperation = getFactory().newOperation();
            getInstanceOperation.setName(getInstanceOperationName);
            getInstanceOperation.setProperty(SciProperty.STATIC, true);

            // create operation body
            String codePattern = EMPTY_STRING;
            Object[] arguments = { /*0*/instanceName, /*1*/singletonName, /*2*/singletonFactoryName, /*3*/nullName };
            if (SciLanguage.JAVA.equals(getLanguage()) &&
                getBooleanPropertyValue(GENERATE_THREAD_SAFE_CODE)) {
                codePattern = PATTERN_THREAD_SAFE_CODE;
            }
            else {
                codePattern = PATTERN_CODE;
            }
            String code = createTextByPattern(codePattern, arguments);
            getInstanceOperation.setBody( getGenericFactory().newCodeBlock(code) );

            if (SciUtil.findMemberBySignature(singletonFactoryClass, getInstanceOperation) == null) {
                getInstanceOperation = (SciOperation) singletonFactoryClass.paste(getInstanceOperation, null, true);
                getInstanceOperation.setReturnType( instanceAttribute.getType() );
                //getInstanceOperation.getReturnType().setText( instanceAttribute.getType().getCanonicalText() );
            }
        }
        catch(SciGenericFactoryException e) {
            IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, CODE_GENERATION_ERROR);
        }
    }

    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);
            }

            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);
            }

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

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

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

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

            if (myUIBuilder.addPropertyToPage(pageName1, GENERATE_THREAD_SAFE_CODE, GENERATE_THREAD_SAFE_CODE,
                PatternUIBuilder.PropertyType.Boolean) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(GENERATE_THREAD_SAFE_CODE);
            if (SciLanguage.CPP.equals(getLanguage())) {
                this.enableItem(GENERATE_THREAD_SAFE_CODE, false);
            }


            if (myUIBuilder.AddCreatePatternLinksProperty(pageName1) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(CREATE_PATTERN_LINKS);

            myUIBuilder.setCreatedPatternUI();
            myUIBuilder.assignListenedInspectorProperties(inspectorPropertiesVector);
        }
        catch (PatternBaseException ex) {
            myUIBuilder = null;
            getIdeInspector().clear();
            return;
        }
    }

    protected void assignActivityResponse() {
        addPropertyMapListener(SELECTED_CLASS_ROLE, new PropertyMapListener() {
            public void propertiesChanged(PropertyMapEvent event) {
                String role = getStringPropertyValue(SELECTED_CLASS_ROLE);
                SciClass singletonClass = null;
                SciClass factoryClass = null;

                if (role != null) {
                    if (role.equals(SINGLETON)) {
                        singletonClass = getSelectedClass();
                        factoryClass = SciPatternUtil.findPatternLinkSupplier(singletonClass, PATTERN_NAME, SINGLETON_FACTORY);
                        if (factoryClass == null) {
                            factoryClass = singletonClass;
                        }
                    }
                    else if (role.equals(SINGLETON_FACTORY)) {
                        factoryClass = getSelectedClass();
                        singletonClass = SciPatternUtil.findAssociatedPatternLinkClient(factoryClass, PATTERN_NAME, SINGLETON_FACTORY);
                    }
                }

                if (singletonClass != null) {
                    setPropertyValue(SINGLETON, singletonClass.getQualifiedName());
                }
                if (factoryClass != null) {
                    setPropertyValue(SINGLETON_FACTORY, factoryClass.getQualifiedName());
                }
                if (singletonClass != null && factoryClass != null) {
                    SciAttribute attribute = SciPatternUtil.findAttribute(factoryClass, singletonClass);
                    if (attribute != null) {
                        setPropertyValue(UNIQUE_INSTANCE_ATTRIBUTE, attribute.getName());
                    }
                    SciOperationEnumeration operations = factoryClass.operations();
                    while (operations.hasMoreElements()) {
                        SciOperation operation = operations.nextSciOperation();
                        if (operation.getReturnType() != null &&
                            singletonClass.equals(operation.getReturnType().getReferencedElement()) &&
                            !operation.getParameterList().parameters().hasMoreElements()) {
                            setPropertyValue(INSTANCE_METHOD, operation.getName());
                            break;
                        }
                    }
                }
            }
        });
    }

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

    private static final String SINGLETON                   = "Singleton";
    private static final String SINGLETON_FACTORY           = "Singleton factory";
    private static final String UNIQUE_INSTANCE_ATTRIBUTE   = "Unique instance attribute";
    private static final String GENERATE_THREAD_SAFE_CODE   = "Generate thread safe code";
    private static final String INSTANCE_METHOD             = "Instance method";

    private static final String GET_INSTANCE                = "getInstance";
    private static final String INSTANCE                    = "instance";

    // Code patterns
    private static final String PATTERN_CODE                = "if ({0} == {3}) '{'\n{0} = new {1}(); '}'\nreturn {0};";
    private static final String PATTERN_THREAD_SAFE_CODE    = "if ({0} == {3}) '{'\nsynchronized({2}.class) '{'\n if ({0} == {3}) '{'\n{0} = new {1}(); '}'\n'}'\n'}'\nreturn {0};";
}
