/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.gwt.user.client.ui.rta.cmd.internal;

import org.xwiki.gwt.user.client.StringUtils;
import org.xwiki.gwt.user.client.Cache.CacheCallback;
import org.xwiki.gwt.user.client.ui.rta.RichTextArea;

import com.google.gwt.core.client.JsonUtils;

/**
 * Base class for executables that insert or update HTML elements.
 * 
 * @param <C> the type of configuration object used to update the element
 * @param <E> the type of element that is inserted or updated
 * @version $Id$
 */
public abstract class AbstractInsertElementExecutable<C, E extends com.google.gwt.dom.client.Element> extends
    InsertHTMLExecutable
{
    /**
     * Creates a configuration object from a DOM element.
     * 
     * @param <C> the type of configuration object returned by the read method
     * @param <E> the type of DOM element being read
     */
    public static interface ConfigDOMReader<C, E extends com.google.gwt.dom.client.Element>
    {
        /**
         * Reads the configuration from the given DOM element.
         * 
         * @param element a DOM element
         * @return the newly created configuration object
         */
        C read(E element);
    }

    /**
     * Updates a DOM element from a configuration object.
     * 
     * @param <C> the type of configuration object that is used to update the DOM element
     * @param <E> the type of DOM element being updated
     */
    public static interface ConfigDOMWriter<C, E extends com.google.gwt.dom.client.Element>
    {
        /**
         * Writes the given configuration to the specified DOM element.
         * 
         * @param config the configuration object used to update the DOM element
         * @param element the DOM element being updated
         */
        void write(C config, E element);
    }

    /**
     * Creates configuration objects from JSON.
     * 
     * @param <C> the type of configuration object returned by the parse method
     */
    public static interface ConfigJSONParser<C>
    {
        /**
         * Creates a new configuration object from its JSON representation.
         * 
         * @param json the JSON representation of of a configuration object
         * @return a new configuration object
         */
        C parse(String json);
    }

    /**
     * Serializes a configuration object to JSON.
     * 
     * @param <C> the type of configuration object that is being serialized to JSON
     */
    public static interface ConfigJSONSerializer<C>
    {
        /**
         * Serializes the given configuration object to JSON.
         * 
         * @param config the configuration object to be serialized
         * @return the JSON serialization
         */
        String serialize(C config);
    }

    /**
     * Base class for all classes that serialize configuration objects to JSON. It includes some utility methods.
     * 
     * @param <C> the type of configuration object that is being serialized to JSON
     */
    public abstract static class AbstractConfigJSONSerializer<C> implements ConfigJSONSerializer<C>
    {
        /**
         * Serializes a property of the {@link ImageConfig}.
         * 
         * @param property the name of the property to serialize
         * @param value the value of the specified property
         * @return the {@code property:value} JSON pair if the property value is not {@code null}, the empty string
         *         otherwise
         */
        protected String serialize(String property, Object value)
        {
            return value != null ? property + ":" + JsonUtils.escapeValue(value.toString()) : "";
        }

        /**
         * Appends a JSON pair to the given {@link StringBuffer}.
         * 
         * @param result the string buffer where to append the given pair
         * @param pair a {@code property:value} JSON pair
         */
        protected void append(StringBuffer result, String pair)
        {
            if (!StringUtils.isEmpty(pair)) {
                if (result.length() > 0) {
                    result.append(",");
                }
                result.append(pair);
            }
        }
    }

    /**
     * The object used to extract a configuration object from an element.
     */
    protected ConfigDOMReader<C, E> configDOMReader;

    /**
     * The object used to update a DOM element from a configuration object.
     */
    protected ConfigDOMWriter<C, E> configDOMWriter;

    /**
     * The object used to serialize a configuration object to JSON.
     */
    protected ConfigJSONParser<C> configJSONParser;

    /**
     * The object used to create a configuration object from JSON.
     */
    protected ConfigJSONSerializer<C> configJSONSerializer;

    /**
     * Creates a new executable that can be used to insert HTML elements in the specified rich text area.
     * 
     * @param rta the execution target
     */
    public AbstractInsertElementExecutable(RichTextArea rta)
    {
        super(rta);
    }

    /**
     * {@inheritDoc}
     * 
     * @see InsertHTMLExecutable#execute(String)
     */
    @Override
    public boolean execute(String json)
    {
        C config = configJSONParser.parse(json);
        E element = getSelectedElement();
        if (element == null) {
            // Insert a new element.
            element = newElement();
            if (!super.execute(element)) {
                return false;
            }
        }
        // Update the selected element.
        configDOMWriter.write(config, element);
        return true;
    }

    /**
     * {@inheritDoc}
     * 
     * @see InsertHTMLExecutable#isExecuted()
     */
    @Override
    public boolean isExecuted()
    {
        // NOTE: We don't use this.getClass().getName() as cache key prefix because we want to be able to compile the
        // code without class meta data information.
        return cache.get(getCacheKeyPrefix() + "#executed", new CacheCallback<Boolean>()
        {
            public Boolean get()
            {
                return getSelectedElement() != null;
            }
        });
    }

    /**
     * {@inheritDoc}
     * 
     * @see InsertHTMLExecutable#getParameter()
     */
    @Override
    public String getParameter()
    {
        E selectedElement = getSelectedElement();
        if (selectedElement == null) {
            return null;
        }
        return configJSONSerializer.serialize(configDOMReader.read(selectedElement));
    }

    /**
     * @return the text to prefix all cache keys used by this class
     */
    protected abstract String getCacheKeyPrefix();

    /**
     * @return the selected element, {@code null} if no element is selected
     */
    protected abstract E getSelectedElement();

    /**
     * @return a new element to be inserted
     */
    protected abstract E newElement();
}

@@ -6021,19 +6021,23 @@
     
-C config = 
+return execute(
 conf
@@ -6056,25 +6056,517 @@
 .parse(json)
-;
+);%0A    %7D%0A%0A    /**%0A     * Inserts a new element if none is selected, otherwise updates the selected element. In both cases the given%0A     * configuration object is used to set the attributes of the target element.%0A     * %0A     * @param config the object used to configure the newly created element or the selected element%0A     * @return %7B@code true%7D if inserting a new element or updating the selected one succeeded, %7B@code false%7D otherwise%0A     */%0A    protected boolean execute(C config)%0A    %7B
 %0A        E e

Synchronize image URL with image dimensions * Allow subclasses of AbstractInsertElementExecutable to adjust the configuration object before inserting/updating the element