/*
 * Copyright (C) 2002-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.ui;

import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.formbuilder.PersistentForm;
import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.PageState;
import java.math.BigDecimal;
import com.arsdigita.formbuilder.PersistentFormSection;
import com.arsdigita.bebop.ColumnPanel;
import com.arsdigita.bebop.FormSection;
import com.arsdigita.formbuilder.PersistentComponentFactory;
import com.arsdigita.formbuilder.PersistentComponent;
import com.arsdigita.formbuilder.PersistentWidget;
import com.arsdigita.formbuilder.WidgetLabel;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.MetaForm;
import com.arsdigita.formbuilder.util.FormBuilderUtil;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.persistence.OID;
import com.arsdigita.util.UncheckedWrapperException;

import com.arsdigita.formbuilder.ui.BaseEditAddObserver;
import com.arsdigita.formbuilder.ui.NewControl;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.formbuilder.ui.ControlProperties;
import com.arsdigita.formbuilder.util.GlobalizationUtil;
import com.arsdigita.formbuilder.util.FormBuilderUtil;
import com.arsdigita.bebop.ControlLink;
import com.arsdigita.bebop.BaseLink;
import com.arsdigita.bebop.Link;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.SingleSelectionModel;
import com.arsdigita.bebop.event.ActionListener;
import com.arsdigita.bebop.event.ActionEvent;
import com.arsdigita.bebop.util.Traversal;
import com.arsdigita.bebop.form.Widget;
import com.arsdigita.bebop.event.PrintListener;


/**
 * This class provides a basic UI component for editing the
 * controls on a persistent form. It is designed to be dropped
 * into any page without requiring any significant additional
 * infrasructure
 */
public class ControlEditor extends SimpleContainer {
    private ParameterSingleSelectionModel m_control;

    private SingleSelectionModel m_form;

    private NewControl m_new_control;
    private NewSection m_new_section;
    private BoxPanel m_view_form;
    private ControlProperties m_control_props;
    private MoveControl m_move_control;

    /**
     * Constructor. Creates a new control editor widget,
     * for editing the form specified in the single
     * selection model. The key returned by the single
     * selection model should be an instance of the
     * {@link java.math.BigDecimal} class.
     *
     * @param app the application type
     * @param form the form to edit
     */
    public ControlEditor(String app,
                         SingleSelectionModel form) {
        this(app, form, false);
    }

    /**
     * Constructor. Creates a new control editor widget,
     * for editing the form specified in the single
     * selection model. The key returned by the single
     * selection model should be an instance of the
     * {@link java.math.BigDecimal} class.
     *
     * This constructor also allows the programmer
     * to turn on the use of form sections, although
     * they must also call the setFormSectionModelBuilder
     * method to populate the list box.
     *
     * @param app the application type
     * @param form the form to edit
     * @param wantFormSeciton whether to display list box for
     * adding form sections.
     */
    public ControlEditor(String app,
                         SingleSelectionModel form,
                         boolean wantFormSection) {
        m_form = form;

        m_new_control = new NewControl(app);
        m_new_section = new NewSection(form);

        String helpURL = FormBuilderUtil.getConfig().getControlsHelpLink();
        if (helpURL != null) {
            add(new Link(new Label(GlobalizationUtil.globalize
                                   ("formbuilder.ui.help")), helpURL));
            add(new Label("")); // spacer
        }

        m_control = new ParameterSingleSelectionModel(new BigDecimalParameter("control"));

        m_view_form = new BoxPanel(BoxPanel.VERTICAL);

        addEditableComponent(m_view_form, m_new_control);

        if (wantFormSection) {
            addEditableComponent(m_view_form, m_new_section);
        }

        m_view_form.add(new FormItemControlsForm("view_form"));

        m_control_props = new ControlProperties(m_form,
                                                m_new_control.getSelection(),
                                                m_control,
                                                app);

        m_move_control = new MoveControl(m_form,
                                         m_control);

        m_new_section.addProcessListener(new NewSectionProcessListener());
        m_new_control.addProcessListener(new NewControlProcessListener());
        m_control_props.addCompletionListener(new ControlPropsCompletionListener());
        m_move_control.addActionListener(new MoveControlActionListener());

        add(m_view_form);
        addEditableComponent(this, m_control_props);
        addEditableComponent(this, m_move_control);
    }

    protected void addEditableComponent(Container container,
                                        Component child) {
        container.add(child);
    }

    protected PersistentComponentFactory getFormSection(PageState state,
                                                        BigDecimal sectionID) {        
        PersistentComponentFactory section = null;
        try {
            section = (PersistentComponentFactory)DomainObjectFactory.newInstance(
                new OID(PersistentComponent.BASE_DATA_OBJECT_TYPE,
                        sectionID));
        } catch (DataObjectNotFoundException ex) {
            throw new UncheckedWrapperException("cannot instantiate section", ex);
        }
        return section;
    }

    // XXX PrintListener will change to ListModel when (if)
    // optiongroups finally become model driven
    /**
     * Sets the form section model builder for populating
     * the drop down list of form sections. The model
     * builder is actually a PrintListener, which should
     * add new Options to the select box
     *
     * @param l the print listener for populating the list
     */
    public void setFormSectionModelBuilder(PrintListener l) {
        m_new_section.setFormSectionModelBuilder(l);
    }

    public void respond(PageState state)
        throws javax.servlet.ServletException {
        super.respond(state);

        String name = state.getControlEventName();
        String value = state.getControlEventValue();

        if (name.equals("edit")) {
            m_control.setSelectedKey(state, new BigDecimal(value));
            m_view_form.setVisible(state, false);
            m_control_props.setVisible(state, true);
        } else if (name.equals("delete")) {
            m_control.setSelectedKey(state, new BigDecimal(value));

            BigDecimal form_id = (BigDecimal)m_form.getSelectedKey(state);
            BigDecimal control_id = (BigDecimal)m_control.getSelectedKey(state);
            
            PersistentFormSection fs = (PersistentFormSection)FormBuilderUtil.instantiateObject(form_id);
            PersistentComponent c = (PersistentComponent)FormBuilderUtil.instantiateObject(control_id);
            
            fs.removeComponent(c);
            
            try {
                PersistentWidget w = (PersistentWidget)c;
                WidgetLabel label = WidgetLabel.findByWidget(w);
                fs.removeComponent(label);
                label.delete();
            } catch (ClassCastException ex) {
                // Nada
            } catch (DataObjectNotFoundException ex) {
                // Nada
            }

            if (!(c instanceof PersistentFormSection))
                c.delete();
            m_control.setSelectedKey(state, null);
        } else if (name.equals("move")) {
            m_control.setSelectedKey(state, new BigDecimal(value));
            m_view_form.setVisible(state, false);
            m_move_control.setVisible(state, true);
        }
    }

    protected SingleSelectionModel getFormModel() {
        return m_form;
    }

    public void register(Page page) {
        super.register(page);
        page.addComponentStateParam(this,
                                    m_control.getStateParameter());

        page.setVisibleDefault(m_view_form, true);
        page.setVisibleDefault(m_control_props, false);
        page.setVisibleDefault(m_move_control, false);
    }

    private class NewSectionProcessListener implements FormProcessListener {
        public void process(FormSectionEvent e)
            throws FormProcessException {
            PageState state = e.getPageState();

            BigDecimal form_id = (BigDecimal)m_form.getSelectedKey(state);
            PersistentFormSection form = (PersistentFormSection)FormBuilderUtil.instantiateObject(form_id);
 
            BigDecimal id = m_new_section.getSelectedSection(state);
            PersistentComponentFactory section = getFormSection(state, id);
            form.addComponent(section);
        }
    }

    private class NewControlProcessListener implements FormProcessListener {
        public void process(FormSectionEvent e)
            throws FormProcessException {

            m_control.setSelectedKey(e.getPageState(), null);
            m_view_form.setVisible(e.getPageState(), false);
            m_control_props.setVisible(e.getPageState(), true);
        }
    }

    private class ControlPropsCompletionListener implements FormCompletionListener {
        public void complete(FormSectionEvent e)
            throws FormProcessException {

            m_control.setSelectedKey(e.getPageState(), null);
            m_view_form.setVisible(e.getPageState(), true);
            m_control_props.setVisible(e.getPageState(), false);
        }
    }

    private class MoveControlActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {

            m_control.setSelectedKey(e.getPageState(), null);
            m_view_form.setVisible(e.getPageState(), true);
            m_move_control.setVisible(e.getPageState(), false);
        }
    }


    /** 
     * Allows subclasses to control when to add the edit/move/delete links
     */
    protected boolean addItemEditObserver(PageState state) {
        return true;
    }

    private class FormItemControlsForm extends MetaForm {
        public FormItemControlsForm(String name) {
            super(name);
        }

        public Form buildForm(PageState state) {
            BigDecimal form_id = (BigDecimal)m_form.getSelectedKey(state);
            PersistentFormSection section = (PersistentFormSection)FormBuilderUtil.instantiateObject(form_id);

            if (addItemEditObserver(state)) {
                section.setComponentAddObserver(new ItemEditAddObserver(ControlEditor.this, state));
                section.setFormContainer(new ColumnPanel(3));
            } else {
                section.setFormContainer(new ColumnPanel(2));
            }
            
            Form f = null;
            if (section instanceof PersistentForm) {
                f = (Form)section.createComponent();
            } else {
                f = new Form("view_form", new ColumnPanel(1));
                f.add((FormSection)section.createComponent());
            }

            f.addInitListener(new PlaceholdersInitListener());
            
            // Make the controls readonly
            Traversal t = new Traversal() {
                    public void act(Component c) {
                        try {
                            Widget widget = (Widget)c;
                                widget.setDisabled();
                                widget.setReadOnly();
                        } catch (ClassCastException ex) {
                            // Nada
                        }
                    }
                };
            t.preorder(f);
            return f;
        }

        private class ItemEditAddObserver extends BaseEditAddObserver {
            Component m_handler;
            PageState m_state;

            public ItemEditAddObserver(Component handler,
                                       PageState state) {
                m_handler = handler;
                m_state = state;
            }

            protected BaseLink createLink(String dest,
                                          PersistentComponentFactory component) {
                return new CallbackLink(m_handler,
                                        "[" + dest + "]",
                                        dest,
                                        component.getID().toString());
            }

            private class CallbackLink extends ControlLink {
                Component m_handler;
                String m_action;
                String m_component;

                public CallbackLink(Component handler,
                                    String label,
                                    String action,
                                    String component) {
                    super(label);

                    m_handler = handler;
                    m_action = action;
                    m_component = component;
                }

                public void setControlEvent(PageState state) {
                    state.setControlEvent(m_handler, m_action, m_component);
                }
            }
        }
    }
}
