/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.formbuilder;


// Used in a super class constructor
import com.arsdigita.persistence.OID;

import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.metadata.ObjectType;

// Thrown if the underlying DataObject with given id cannot be found
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.util.UncheckedWrapperException;

// Listeners that you may add to a widget
import com.arsdigita.formbuilder.parameters.PersistentParameterListener;

// We are persisting ParameterModels
import com.arsdigita.bebop.parameters.ParameterModel;

// For instantiating listeners
import com.arsdigita.formbuilder.util.FormBuilderUtil;

// The PersistentWidget creates a Bebop Widget
import com.arsdigita.bebop.form.Widget;

// For the validation listener association
import com.arsdigita.persistence.DataAssociation;
import com.arsdigita.persistence.DataAssociationCursor;

import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;

// For the ACSObject id
import java.math.BigDecimal;
import java.io.IOException;

// ACS 5 uses Log4J for logging
import org.apache.log4j.Logger;


/**
 * This class is responsible for persisting attributes common to
 * Bebop Widgets. Sub classes of this abstract class manage
 * persistence of different Bebop Widgets (TextArea etc.).
 *
 * @author Peter Marklund
 * @version $Id: //core-platform/dev/src/com/arsdigita/formbuilder/PersistentWidget.java#13 $
 *
 */
public abstract class PersistentWidget extends PersistentComponent
    implements AttributeMetaDataProvider {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/formbuilder/PersistentWidget.java#13 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private static final Logger s_log =
        Logger.getLogger(PersistentWidget.class.getName());

    private Class DEFAULT_VALUE_CLASS = String.class;

    public static final String DEFAULT_VALUE = "defaultValue";

    /**
     * BASE_DATA_OBJECT_TYPE represents the full name of the
     * underlying DataObject of this class.
     */
    public static final String BASE_DATA_OBJECT_TYPE =
        "com.arsdigita.formbuilder.Widget";

    // *** Constructors -------------

    /**
     * Sub classes can create a new widget domain object with
     * this constructor.
     */
    public PersistentWidget(String objectType) {
        super(objectType);
    }

    public PersistentWidget(ObjectType type) {
        super(type);
    }

    public PersistentWidget(DataObject obj) {
        super(obj);
    }

    /**
     * Sub classes may use this constructor to retrieve an existing
     * Widget domain object with an id and object type.
     */
    public PersistentWidget(OID oID)
        throws DataObjectNotFoundException {

        super(oID);
    }

    protected void setup(String parameterName) {
        setParameterName(parameterName);
    }

    public void delete() {
        // This behavior is exceptionally odd since it does not call
        // super and the call to clearValidationListeners is not needed.
        clearValidationListeners();
    }

    // *** Attribute Methods

    public void setParameterName(String parameterName) {
        set("parameterName", parameterName);
    }

    /**
     * Will return null if no value has been set.
     */
    public String getParameterName() {
        return (String)get("parameterName");
    }

    public void addValidationListener(PersistentParameterListener parameterListener) {
        add("listeners", parameterListener);
    }

    /**
     * Return a collection with all PersistentParameterListeners of this widget.
     */
    public Collection getValidationListeners() {

        ArrayList listenerList = new ArrayList();

        DataAssociationCursor listenerCursor =
            ((DataAssociation)get("listeners")).cursor();

        while(listenerCursor.next()) {

            DataObject listenerObject = listenerCursor.getDataObject();
            BigDecimal listenerID = (BigDecimal)listenerObject.get("id");
            String factoryClassName = (String)listenerObject.get("defaultDomainClass");

            PersistentParameterListener listenerFactory =
                (PersistentParameterListener)FormBuilderUtil.instantiateObjectOneArg(factoryClassName, listenerID);

            listenerList.add(listenerFactory);
        }

        listenerCursor.close();

        return listenerList;
    }

    /**
     * Removes all validation listeners associated with this widget
     */
    public void clearValidationListeners() {
        clear("listeners");
    }

    /**
     * The class name of the parameter model to be used for the widget
     */
    public void setParameterModel(String parameterModel) {
        set("parameterModel", parameterModel);
    }

    /**
     * Will return null if no value has been set.
     */
    public String getParameterModel() {
        return (String)get("parameterModel");
    }

    /**
     * You have to make sure that the defaulValue is an instance
     * of the Class returned by the method getValueClass() (for
     * example this is a String for TextAreas and a java.util.Date for
     * Dates).
     */
    public void setDefaultValue(Object defaultValue) {

        // Check that the object has the right type for this Widget
        if (!(getValueClass().isAssignableFrom(defaultValue.getClass()))) {

            throw new IllegalArgumentException("object " + defaultValue.toString() +
                                               " supplied to " + this.toString() +
                                               " setDefaultValue() must be an instanceof " + getValueClass().getName());
        }

        // We need to be able to convert the object to a String
        String stringValue = null;

        if (defaultValue instanceof String) {

            // A String - the easiest case
            stringValue = (String)defaultValue;

        } else if (defaultValue instanceof java.io.Serializable) {

            try {
                // Serialize the object
                java.io.ByteArrayOutputStream byteStream =
                    new java.io.ByteArrayOutputStream();

                java.io.ObjectOutputStream objectStream =
                    new java.io.ObjectOutputStream(byteStream);

                try {
                    objectStream.writeObject(defaultValue);

                    //                stringValue = new String(byteStream.toByteArray(), "ISO-8859-1");
                    stringValue = new sun.misc.BASE64Encoder().encode(byteStream.toByteArray());


                    s_log.debug("setDefaultValue serializing object " + defaultValue.toString() +
                                " to " + stringValue);

                } finally {
                    try {
                        objectStream.close();
                    } catch(Exception e) {
                        s_log.error("Problem closing ObjectOutputStream.", e);
                    }
                }

            } catch (java.io.IOException e) {
                throw new UncheckedWrapperException(e);
            }

        } else {

            // We do not support objects that are not strings and
            // that are not serializable
            throw new IllegalArgumentException("object " + defaultValue.toString() +
                                               " does not implement Serializable which is required for a non-String default" +
                                               " value to be persisted by the Form Builder");
        }

        set(DEFAULT_VALUE, stringValue);
    }

    /**
     * Get the default value of the widget. Java serialization is used to persist objects
     * so that the default value object must implement Serializable. Will return null if
     * no value has been set.
     */
    public Object getDefaultValue() {

        // Get the serialized default value object
        Object defaultValue = get(DEFAULT_VALUE);

        if (defaultValue == null) {
            return null;
        }

        if (!(getValueClass().getName().equals("java.lang.String"))) {
            defaultValue = deserializeDefaultValue((String)defaultValue);
        }

        return defaultValue;
    }

    /**
     *  This provides a method to take a default value and deserialize
     *  it in to its original object.  This is useful if the defaultValue
     *  is actually something like a serialized java.util.Date
     */
    public static Object deserializeDefaultValue(String defaultValue) {
        Object actualDefault = null;
        try {
            s_log.debug("getDefaultValue de-serializing string " + (String)defaultValue);
            
            // This is a serialized object - resurrect it
            // from the String
            java.io.ByteArrayInputStream byteStream =
                new java.io.ByteArrayInputStream((new sun.misc.BASE64Decoder()).decodeBuffer(defaultValue));
            
            //((String)defaultValue).getBytes("ISO-8859-1"));
            
            java.io.ObjectInputStream objectStream =
                new java.io.ObjectInputStream(byteStream);
            
            try {
                actualDefault = objectStream.readObject();
            } finally {
                try {
                    objectStream.close();
                } catch(IOException e) {
                    s_log.error("Problem closing ObjectInputStream.", e);
                }
            }
            
        } catch (Exception e) {
            // an IOException or a ClassNotFoundException
            throw new UncheckedWrapperException(e);
        }

        return actualDefault;
    }


    /**
     * Most Widgets have String as their value class which is what is
     * returned by this default implementation. The PersistentDate
     * class has a different implementation.
     */
    protected Class getValueClass() {

        return DEFAULT_VALUE_CLASS;
    }

    //*** Attribute metadata

    public AttributeMetaDataList getAttributeMetaData() {

        AttributeMetaDataList list = super.getAttributeMetaData();

        list.add(new AttributeMetaData("parameterName", "HTML parameter name", true)); // required
        list.add(new AttributeMetaData(DEFAULT_VALUE, "Default value"));

        return list;
    }

    //*** Internal Helper Methods
    protected ParameterModel instantiateParameterModel() {

        if (getParameterModel() == null) {
            return null;
        } else {
            return (ParameterModel)
                FormBuilderUtil.instantiateObjectOneArg(getParameterModel(), getParameterName());
        }
    }

    protected void copyValuesToWidget(Widget widget) {

        // Set the ParameterModel if any has been specified
        if (getParameterModel() != null) {

            ParameterModel model = instantiateParameterModel();
            widget.setParameterModel(model);
        }

        // Set the default value if any has been set
        if (get(DEFAULT_VALUE) != null) {
            widget.setDefaultValue(getDefaultValue());
        }

        // Add any validation listeners
        addValidationListeners(widget);
    }

    private void addValidationListeners(Widget widget) {

        Iterator listenerIter = getValidationListeners().iterator();
        while (listenerIter.hasNext()) {
            PersistentParameterListener persistentListener =
                (PersistentParameterListener)listenerIter.next();

            widget.addValidationListener(persistentListener.createListener());
        }
    }
}
