/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/

package com.sun.star.comp.extensionoptions;

import com.sun.star.lib.uno.helper.Factory;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XTypeProvider;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.Any;
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.Exception;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.awt.XContainerWindowEventHandler;
import com.sun.star.awt.XControl;
import com.sun.star.awt.XControlModel;
import com.sun.star.awt.XControlContainer;
import com.sun.star.container.XNameAccess;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.PropertyState;
import com.sun.star.beans.XPropertySet;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.beans.PropertyVetoException;
import com.sun.star.util.XChangesBatch;

/** A handler which supports multiple options pages which all
 *  have the same controls.
 */
public class OptionsEventHandler {

    public static class _OptionsEventHandler extends WeakBase
        implements XServiceInfo, XContainerWindowEventHandler {

        static private final String __serviceName =
        "com.sun.star.comp.extensionoptions.OptionsEventHandler";
    
        private XComponentContext m_cmpCtx;

        private XMultiComponentFactory m_xMCF;

        private XNameAccess m_xAccessLeaves;

        /**Names of supported options pages. 
         */
        private String[] m_arWindowNames = {
            "Writer1", "Writer2", "Writer3", "Calc1", "Calc2", "Calc3",
            "Draw1", "Draw2", "Draw3", "Node1_1", "Node1_2", "Node1_3",
            "Node2_1", "Node2_2", "Node2_3", "Node3_1", "Node3_2", "Node3_3"};

        /**Names of the controls which are supported by this handler. All these
         *controls must have a "Text" property.
         */
        private String[] m_arStringControls = {
            "String0", "String1", "String2", "String3", "String4"};
        
        public _OptionsEventHandler(XComponentContext xCompContext) {
                m_cmpCtx = xCompContext;
                m_xMCF = m_cmpCtx.getServiceManager();                

            //Create the com.sun.star.configuration.ConfigurationUpdateAccess
            //for the registry node which contains the data for our option
            //pages.
            XMultiServiceFactory xConfig;
            try {
                xConfig = (XMultiServiceFactory) UnoRuntime.queryInterface(
                    XMultiServiceFactory.class,
                    m_cmpCtx.getServiceManager().createInstanceWithContext(
                        "com.sun.star.configuration.ConfigurationProvider", m_cmpCtx));
            } catch (com.sun.star.uno.Exception e) {
                e.printStackTrace();
                return;
            }

            //One argument for creating the ConfigurationUpdateAccess is the "nodepath".
            //Our nodepath point to the node of which the direct subnodes represent the
            //different options pages.
            Object[] args = new Object[1];
            args[0] = new PropertyValue(
                "nodepath", 0, "/org.openoffice.desktop.deployment.options.ExtensionData/Leaves",
                PropertyState.DIRECT_VALUE);

            //We get the com.sun.star.container.XNameAccess from the instance of
            //ConfigurationUpdateAccess and save it for later use.
            try {
                m_xAccessLeaves = (XNameAccess) UnoRuntime.queryInterface(
                    XNameAccess.class, xConfig.createInstanceWithArguments(
                        "com.sun.star.configuration.ConfigurationUpdateAccess", args));

            } catch (com.sun.star.uno.Exception e) {
                e.printStackTrace();
                return;
            }
        }
        
        /** This method returns an array of all supported service names.
         * @return Array of supported service names.
         */
        public String[] getSupportedServiceNames() {
            return getServiceNames();
        }

        /** This method is a simple helper function to used in the
         * static component initialisation functions as well as in
         * getSupportedServiceNames.
         */
        public static String[] getServiceNames() {
            String[] sSupportedServiceNames = { __serviceName };
            return sSupportedServiceNames;
        }
      
        /** This method returns true, if the given service will be
         * supported by the component.
         * @param sServiceName Service name.
         * @return True, if the given service name will be supported.
         */
        public boolean supportsService( String sServiceName ) {
            return sServiceName.equals( __serviceName );
        }
    
        /** Return the class name of the component.
         * @return Class name of the component.
         */
        public String getImplementationName() {
            return  _OptionsEventHandler.class.getName();
        }

        //XContainerWindowEventHandler
        public boolean callHandlerMethod(com.sun.star.awt.XWindow aWindow,
                                        Object aEventObject, String sMethod)
            throws WrappedTargetException {
            if (sMethod.equals("external_event") ){
                try {
                    return handleExternalEvent(aWindow, aEventObject);
                } catch (com.sun.star.uno.RuntimeException re) {
                    throw re;
                } catch (com.sun.star.uno.Exception e) {
                    throw new WrappedTargetException(sMethod, this, e);
                }
            }
                
            return true;
        }
        
        //XContainerWindowEventHandler
        public String[] getSupportedMethodNames() {
            return new String[] {"external_event"};
        }

        private boolean handleExternalEvent(com.sun.star.awt.XWindow aWindow, Object aEventObject)
            throws com.sun.star.uno.Exception {
            try {
                String sMethod = AnyConverter.toString(aEventObject);
                if (sMethod.equals("ok")) {
                    saveData(aWindow);
                } else if (sMethod.equals("back") || sMethod.equals("initialize")) {
                    loadData(aWindow);
                }
            } catch (com.sun.star.lang.IllegalArgumentException e) {
                throw new com.sun.star.lang.IllegalArgumentException(
                    "Method external_event requires a string in the event object argument.",
                    this, (short) -1);
            }
            
            return true;
        }

        private void saveData(com.sun.star.awt.XWindow aWindow)
            throws com.sun.star.lang.IllegalArgumentException,
            com.sun.star.uno.Exception { 

            //Determine the name of the options page. This serves two purposes. First, if this
            //options page is supported by this handler and second we use the name two locate
            //the corresponding data in the registry.
            String sWindowName = getWindowName(aWindow);
            if (sWindowName == null)
                throw new com.sun.star.lang.IllegalArgumentException(
                    "This window is not supported by this handler", this, (short) -1);

            //To access the separate controls of the window we need to obtain the
            //XControlContainer from the window implementation
            XControlContainer xContainer = (XControlContainer) UnoRuntime.queryInterface(
                XControlContainer.class, aWindow);
            if (xContainer == null)
                throw new com.sun.star.uno.Exception(
                    "Could not get XControlContainer from window.", this);

            //This is an implementation which will be used for several options pages
            //which all have the same controls. m_arStringControls is an array which
            //contains the names. 
            for (int i = 0; i < m_arStringControls.length; i++) {

                //To obtain the data from the controls we need to get their model.
                //First get the respective control from the XControlContainer.
                XControl xControl = xContainer.getControl(m_arStringControls[i]);

                //This generic handler and the corresponding registry schema support
                //up to five text controls. However, if a options page does not use all
                //five controls then we will not complain here.
                if (xControl == null)
                    continue;

                //From the control we get the model, which in turn supports the
                //XPropertySet interface, which we finally use to get the data from
                //the control.                
                XPropertySet xProp = (XPropertySet) UnoRuntime.queryInterface(
                    XPropertySet.class, xControl.getModel());

                if (xProp == null)
                    throw new com.sun.star.uno.Exception(
                        "Could not get XPropertySet from control.", this);
                //Get the "Text" property.
                Object aText = xProp.getPropertyValue("Text");
                String sValue = null;

                //The value is still contained in a com.sun.star.uno.Any - so convert it.
                try {
                    sValue = AnyConverter.toString(aText);
                } catch (com.sun.star.lang.IllegalArgumentException e) {
                    throw new com.sun.star.lang.IllegalArgumentException(
                        "Wrong property type.", this, (short) -1);
                }

                //Now we have the actual string value of the control. What we need now is
                //the XPropertySet of the respective property in the registry, so that we
                //can store the value.
                //To access the registry we have previously created a service instance
                //of com.sun.star.configuration.ConfigurationUpdateAccess which supports
                //com.sun.star.container.XNameAccess. The XNameAccess is used to get the
                //particular registry node which represents this options page.
                //Fortunately the name of the window is the same as the registry node.
                XPropertySet xLeaf = (XPropertySet) UnoRuntime.queryInterface(
                    XPropertySet.class, m_xAccessLeaves.getByName(sWindowName));
                if (xLeaf == null)
                    throw new  com.sun.star.uno.Exception(
                        "XPropertySet not supported.", this);

                //Finally we can set the value
                xLeaf.setPropertyValue(m_arStringControls[i], sValue);
            }

            //Committing the changes will cause or changes to be written to the registry.
            XChangesBatch xUpdateCommit = 
                (XChangesBatch) UnoRuntime.queryInterface(XChangesBatch.class, m_xAccessLeaves);
            xUpdateCommit.commitChanges();
        }

        private void loadData(com.sun.star.awt.XWindow aWindow)
            throws com.sun.star.uno.Exception {

            //Determine the name of the window. This serves two purposes. First, if this
            //window is supported by this handler and second we use the name two locate
            //the corresponding data in the registry.
            String sWindowName = getWindowName(aWindow);
            if (sWindowName == null)
                throw new com.sun.star.lang.IllegalArgumentException(
                    "The window is not supported by this handler", this, (short) -1);

            //To access the separate controls of the window we need to obtain the
            //XControlContainer from window implementation
            XControlContainer xContainer = (XControlContainer) UnoRuntime.queryInterface(
                XControlContainer.class, aWindow);
            if (xContainer == null)
                throw new com.sun.star.uno.Exception(
                    "Could not get XControlContainer from window.", this);

            //This is an implementation which will be used for several options pages
            //which all have the same controls. m_arStringControls is an array which
            //contains the names.
            for (int i = 0; i < m_arStringControls.length; i++) {

                //load the values from the registry
                //To access the registry we have previously created a service instance
                //of com.sun.star.configuration.ConfigurationUpdateAccess which supports
                //com.sun.star.container.XNameAccess. We obtain now the section
                //of the registry which is assigned to this options page.
                XPropertySet xLeaf = (XPropertySet) UnoRuntime.queryInterface(
                    XPropertySet.class, m_xAccessLeaves.getByName(sWindowName));
                if (xLeaf == null)
                    throw new  com.sun.star.uno.Exception(
                        "XPropertySet not supported.", this);

                //The properties in the registry have the same name as the respective
                //controls. We use the names now to obtain the property values.
                Object aValue = xLeaf.getPropertyValue(m_arStringControls[i]);

                //Now that we have the value we need to set it at the corresponding
                //control in the window. The XControlContainer, which we obtained earlier
                //is the means to get hold of all the controls.
                XControl xControl = xContainer.getControl(m_arStringControls[i]);

                //This generic handler and the corresponding registry schema support
                //up to five text controls. However, if a options page does not use all
                //five controls then we will not complain here.
                if (xControl == null)
                    continue;

                //From the control we get the model, which in turn supports the
                //XPropertySet interface, which we finally use to set the data at the
                //control
                XPropertySet xProp = (XPropertySet) UnoRuntime.queryInterface(
                    XPropertySet.class, xControl.getModel());
                
                if (xProp == null)
                    throw new com.sun.star.uno.Exception(
                        "Could not get XPropertySet from control.", this);

                //This handler supports only text controls, which are named "Pattern Field"
                //in the dialog editor. We set the "Text" property.
                xProp.setPropertyValue("Text", aValue);
            }
        }

        //Checks if the name property of the window is one of the supported names and returns
        //always a valid string or null
        private String getWindowName(com.sun.star.awt.XWindow aWindow)
            throws com.sun.star.uno.Exception {

            if (aWindow == null)
                new com.sun.star.lang.IllegalArgumentException(
                    "Method external_event requires that a window is passed as argument",
                    this, (short) -1);

            //We need to get the control model of the window. Therefore the first step is
            //to query for it.
            XControl xControlDlg = (XControl) UnoRuntime.queryInterface(
                XControl.class, aWindow);

            if (xControlDlg == null)
                throw new com.sun.star.uno.Exception(
                    "Cannot obtain XControl from XWindow in method external_event.");
            //Now get model
            XControlModel xModelDlg = xControlDlg.getModel();

            if (xModelDlg == null)
                throw new com.sun.star.uno.Exception(
                    "Cannot obtain XControlModel from XWindow in method external_event.", this);
            //The model itself does not provide any information except that its
            //implementation supports XPropertySet which is used to access the data.
            XPropertySet xPropDlg = (XPropertySet) UnoRuntime.queryInterface(
                XPropertySet.class, xModelDlg);
            if (xPropDlg == null)
                throw new com.sun.star.uno.Exception(
                    "Cannot obtain XPropertySet from window in method external_event.", this);

            //Get the "Name" property of the window
            Object aWindowName = xPropDlg.getPropertyValue("Name");

            //Get the string from the returned com.sun.star.uno.Any
            String sName = null;
            try {
                sName = AnyConverter.toString(aWindowName);
            } catch (com.sun.star.lang.IllegalArgumentException e) {
                throw new com.sun.star.uno.Exception(
                    "Name - property of window is not a string.", this);
            }
            
            //Eventually we can check if we this handler can "handle" this options page.
            //The class has a member m_arWindowNames which contains all names of windows
            //for which it is intended
            for (int i = 0; i < m_arWindowNames.length; i++) {
                if (m_arWindowNames[i].equals(sName)) {
                    return sName;
                }
            }
            return null;
        }
    }
  
    
    /**
     * Gives a factory for creating the service.
     * This method is called by the <code>JavaLoader</code>
     * <p>
     * @return  returns a <code>XSingleComponentFactory</code> for creating
     *          the component
     * @param   sImplName the name of the implementation for which a
     *          service is desired
     * @see     com.sun.star.comp.loader.JavaLoader
     */
    public static XSingleComponentFactory __getComponentFactory(String sImplName)
    {
        XSingleComponentFactory xFactory = null;
    
        if ( sImplName.equals( _OptionsEventHandler.class.getName() ) )
            xFactory = Factory.createComponentFactory(_OptionsEventHandler.class,
                                             _OptionsEventHandler.getServiceNames());
        
        return xFactory;
    }

    /**
     * Writes the service information into the given registry key.
     * This method is called by the <code>JavaLoader</code>
     * <p>
     * @return  returns true if the operation succeeded
     * @param   regKey the registryKey
     * @see     com.sun.star.comp.loader.JavaLoader
     */
    public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
        return Factory.writeRegistryServiceInfo(_OptionsEventHandler.class.getName(),
                                                _OptionsEventHandler.getServiceNames(),
                                                regKey);
    }

    /** This method is a member of the interface for initializing an object
     * directly after its creation.
     * @param object This array of arbitrary objects will be passed to the
     * component after its creation.
     * @throws Exception Every exception will not be handled, but will be
     * passed to the caller.
     */
    public void initialize( Object[] object )
        throws com.sun.star.uno.Exception {
    }
    
}
