/*****************************************************************
 *   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 org.apache.cayenne.modeler.dialog.datadomain;

import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.cayenne.access.DataDomain;
import org.apache.cayenne.access.DataRowStore;
import org.apache.cayenne.map.event.DomainEvent;
import org.apache.cayenne.modeler.ProjectController;
import org.scopemvc.controller.basic.BasicController;
import org.scopemvc.core.Control;
import org.scopemvc.core.ControlException;
import org.scopemvc.core.ModelChangeEvent;
import org.scopemvc.core.ModelChangeListener;
import org.scopemvc.core.Selector;
import org.scopemvc.view.swing.SPanel;

/**
 * A controller for CacheSyncConfigDialog and its subviews. Builds a model out of a
 * DataDomain properties map, and on save updates DataDomain properties with configuration
 * changes made by the user. This controller manages one main dialog view, and its
 * subviews organized using CardLayout. Each subview as well as the main dialog have their
 * own independent models.
 * 
 * @author Andrei Adamchik
 */
public class CacheSyncConfigController extends BasicController implements
        ModelChangeListener {

    private static Logger logObj = Logger.getLogger(CacheSyncConfigController.class);

    // using strings instead of the actioal factory classes, since we
    // JMS and JavaGroups libraries may not be around, and Modeler
    // may throw CNFE
    private static final String JGROUPS_FACTORY_CLASS = "org.apache.cayenne.event.JavaGroupsBridgeFactory";
    private static final String JMS_FACTORY_CLASS = "org.apache.cayenne.event.JMSBridgeFactory";

    public static final String SAVE_CONFIG_CONTROL = "cayenne.modeler.cacheSyncConfig.save.button";
    public static final String CANCEL_CONFIG_CONTROL = "cayenne.modeler.cacheSyncConfig.cancel.button";

    public static final String JGROUPS_DEFAULT_CONTROL = "cayenne.modeler.jgroupConfig.radio1";

    public static final String JGROUPS_URL_CONTROL = "cayenne.modeler.jgroupConfig.radio2";

    protected Map existingCards;
    protected boolean modified;
    protected ProjectController eventController;

    public CacheSyncConfigController(ProjectController eventController) {
        this.eventController = eventController;
    }

    /**
     * Creates and shows a new modal dialog window. Registers as a listener for its own
     * model to update subviews on model changes.
     */
    public void startup() {
        DataDomain domain = eventController.getCurrentDataDomain();
        String factory = (String) domain.getProperties().get(
                DataRowStore.EVENT_BRIDGE_FACTORY_PROPERTY);

        CacheSyncTypesModel topModel = buildTypesModel(factory);
        setModel(topModel);
        setView(new CacheSyncConfigDialog());

        // build cards, showing the one corresponding to DataDomain state
        prepareChildren(factory);
        super.startup();
    }

    /**
     * ModelChangeListener implementation that updates "modified" status and changes
     * dialog subview on model changes.
     */
    public void modelChanged(ModelChangeEvent inEvent) {
        logObj.info("ModelChangeEvent: " + inEvent.getSelector());

        Selector selector = inEvent.getSelector();

        if (selector.startsWith(CacheSyncTypesModel.FACTORY_LABEL_SELECTOR)) {
            changeConfigView();
            modified = true;
            logObj.info("** Factory selection modified..");
        }
        else {
            modified = true;
            logObj.info("** Property modified modified..");
        }
    }

    /**
     * Overrides super implementation to process controls from this controller's view and
     * its subviews.
     */
    protected void doHandleControl(Control control) throws ControlException {
        logObj.info("Control: " + control);

        if (control.matchesID(CANCEL_CONFIG_CONTROL)) {
            shutdown();
        }
        else if (control.matchesID(SAVE_CONFIG_CONTROL)) {
            commitChanges();
        }
        else if (control.matchesID(JGROUPS_DEFAULT_CONTROL)) {
            jgroupsDefaultConfig();
        }
        else if (control.matchesID(JGROUPS_URL_CONTROL)) {
            jgroupsURLConfig();
        }
    }

    protected void jgroupsDefaultConfig() {
        JGroupsConfigPanel view = (JGroupsConfigPanel) existingCards
                .get(CacheSyncTypesModel.JGROUPS_FACTORY_LABEL);
        if (view != null) {
            view.showDefaultConfig();
        }
    }

    protected void jgroupsURLConfig() {
        JGroupsConfigPanel view = (JGroupsConfigPanel) existingCards
                .get(CacheSyncTypesModel.JGROUPS_FACTORY_LABEL);
        if (view != null) {
            view.showCustomConfig();
        }
    }

    /**
     * Stores configuration changes in the data domain properties.
     */
    protected void commitChanges() {
        logObj.info("Has changes?: " + modified);

        if (modified) {
            // extract model from current card
            CacheSyncTypesModel topModel = (CacheSyncTypesModel) getModel();
            SPanel card = (SPanel) existingCards.get(topModel.getFactoryLabel());
            CacheSyncConfigModel model = (CacheSyncConfigModel) card.getShownModel();

            DataDomain domain = eventController.getCurrentDataDomain();

            logObj.warn("domain properties BEFORE: " + domain.getProperties());
            model.storeProperties(domain.getProperties());

            logObj.warn("domain properties: " + domain.getProperties());

            eventController.fireDomainEvent(new DomainEvent(this, domain));
        }

        shutdown();
    }

    /**
     * Changes a subview to a panel specific for the currently selected configuration
     * type.
     */
    protected void changeConfigView() {
        CacheSyncTypesModel topModel = (CacheSyncTypesModel) getModel();
        CacheSyncConfigModel newModel = buildModel(topModel);

        // NOTE: card doesn't have a controller, since it does not need it
        String label = topModel.getFactoryLabel();
        SPanel card = (SPanel) existingCards.get(label);
        card.setBoundModel(newModel);
        ((CacheSyncConfigDialog) getView()).showCard(label);
    }

    protected CacheSyncTypesModel buildTypesModel(String factory) {

        if (factory == null) {
            factory = DataRowStore.EVENT_BRIDGE_FACTORY_DEFAULT;
        }

        String label;

        if (JGROUPS_FACTORY_CLASS.equals(factory)) {
            label = CacheSyncTypesModel.JGROUPS_FACTORY_LABEL;
        }
        else if (JMS_FACTORY_CLASS.equals(factory)) {
            label = CacheSyncTypesModel.JMS_FACTORY_LABEL;
        }
        else {
            label = CacheSyncTypesModel.CUSTOM_FACTORY_LABEL;
        }

        CacheSyncTypesModel model = new CacheSyncTypesModel();
        model.setFactoryLabel(label);
        model.addModelChangeListener(this);
        return model;
    }

    protected CacheSyncConfigModel buildModel(CacheSyncTypesModel topModel) {
        String label = topModel.getFactoryLabel();
        String factory;

        if (label.equals(CacheSyncTypesModel.JGROUPS_FACTORY_LABEL)) {
            factory = JGROUPS_FACTORY_CLASS;
        }
        else if (label.equals(CacheSyncTypesModel.JMS_FACTORY_LABEL)) {
            factory = JMS_FACTORY_CLASS;
        }
        else {
            // reset factory
            factory = null;
        }

        return buildModel(factory);
    }

    protected CacheSyncConfigModel buildModel(String factory) {

        CacheSyncConfigModel model;

        if (JGROUPS_FACTORY_CLASS.equals(factory)) {
            model = new JGroupsConfigModel();
        }
        else if (JMS_FACTORY_CLASS.equals(factory)) {
            model = new JMSConfigModel();
        }
        else {
            model = new CacheSyncConfigModel();
        }

        model.setMap(new HashMap(eventController.getCurrentDataDomain().getProperties()));
        model.setFactoryClass(factory);
        model.addModelChangeListener(this);

        return model;
    }

    protected void prepareChildren(String factory) {
        existingCards = new HashMap();
        CacheSyncConfigDialog topView = (CacheSyncConfigDialog) getView();

        // note that none of the panels need a controller
        // if they issue controls, they will use this object taken from parent

        JGroupsConfigPanel jgroupsPanel = new JGroupsConfigPanel();
        existingCards.put(CacheSyncTypesModel.JGROUPS_FACTORY_LABEL, jgroupsPanel);
        topView.addCard(jgroupsPanel, CacheSyncTypesModel.JGROUPS_FACTORY_LABEL);

        JMSConfigPanel jmsPanel = new JMSConfigPanel();
        existingCards.put(CacheSyncTypesModel.JMS_FACTORY_LABEL, jmsPanel);
        topView.addCard(jmsPanel, CacheSyncTypesModel.JMS_FACTORY_LABEL);

        CustomRemoteEventsConfigPanel customFactoryPanel = new CustomRemoteEventsConfigPanel();
        existingCards.put(CacheSyncTypesModel.CUSTOM_FACTORY_LABEL, customFactoryPanel);
        topView.addCard(customFactoryPanel, CacheSyncTypesModel.CUSTOM_FACTORY_LABEL);

        if (factory == null) {
            factory = DataRowStore.EVENT_BRIDGE_FACTORY_DEFAULT;
        }

        // display the right initial card
        // can't call "changeConfigView", since it will reset custom factories..
        Object model = buildModel(factory);

        if (JGROUPS_FACTORY_CLASS.equals(factory)) {
            jgroupsPanel.setBoundModel(model);
            ((CacheSyncConfigDialog) getView())
                    .showCard(CacheSyncTypesModel.JGROUPS_FACTORY_LABEL);
        }
        else if (JMS_FACTORY_CLASS.equals(factory)) {
            jmsPanel.setBoundModel(model);
            ((CacheSyncConfigDialog) getView())
                    .showCard(CacheSyncTypesModel.JMS_FACTORY_LABEL);
        }
        else {
            customFactoryPanel.setBoundModel(model);
            ((CacheSyncConfigDialog) getView())
                    .showCard(CacheSyncTypesModel.CUSTOM_FACTORY_LABEL);
        }
    }
}
