/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.jboss.as.console.mbui.reification;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.event.shared.EventBus;
import org.jboss.as.console.client.shared.properties.ModelNodePropertyEditor;
import org.jboss.as.console.client.shared.properties.ModelNodePropertyManagement;
import org.jboss.as.console.mbui.JBossQNames;
import org.jboss.as.console.mbui.model.StereoTypes;
import org.jboss.as.console.mbui.model.mapping.DMRMapping;
import org.jboss.ballroom.client.rbac.SecurityContext;
import org.jboss.dmr.client.ModelNode;
import org.jboss.dmr.client.Property;
import org.useware.kernel.gui.behaviour.InteractionEvent;
import org.useware.kernel.gui.behaviour.PresentationEvent;
import org.useware.kernel.gui.behaviour.SystemEvent;
import org.useware.kernel.gui.behaviour.common.CommonQNames;
import org.useware.kernel.gui.reification.Context;
import org.useware.kernel.gui.reification.ContextKey;
import org.useware.kernel.gui.reification.strategy.ReificationStrategy;
import org.useware.kernel.gui.reification.strategy.ReificationWidget;
import org.useware.kernel.model.behaviour.Resource;
import org.useware.kernel.model.behaviour.ResourceType;
import org.useware.kernel.model.mapping.MappingType;
import org.useware.kernel.model.mapping.Predicate;
import org.useware.kernel.model.structure.InteractionUnit;
import org.useware.kernel.model.structure.QName;

import java.util.List;
import java.util.Map;

import static org.useware.kernel.gui.behaviour.common.CommonQNames.RESET_ID;
import static org.useware.kernel.model.behaviour.ResourceType.*;
import static org.useware.kernel.model.behaviour.ResourceType.System;

/**
 * @author Harald Pehl
 * @author Heiko Braun
 * @date 11/01/2012
 */
public class PropertiesStrategy implements ReificationStrategy<ReificationWidget, StereoTypes>
{

    private ModelNode modelDescription;
    private EventBus eventBus;
    private static final Resource<ResourceType> SAVE_EVENT = new Resource<ResourceType>(JBossQNames.SAVE_ID, Interaction);
    private static final Resource<ResourceType> LOAD_EVENT = new Resource<ResourceType>(JBossQNames.LOAD_ID, Interaction);
    private static final Resource<ResourceType> RESET = new Resource<ResourceType>(RESET_ID, System);
    private SecurityContext securityContext;

    @Override
    public boolean prepare(InteractionUnit<StereoTypes> interactionUnit, Context context) {
        Map<QName, ModelNode> descriptions = context.get (ContextKey.MODEL_DESCRIPTIONS);

        // TODO (BUG): After the first reification the behaviour is modified,
        // so the predicate might apply to a different unit. As a result the correlation id is different!

        QName correlationId = interactionUnit.findMapping(MappingType.DMR, new Predicate<DMRMapping>() {
            @Override
            public boolean appliesTo(DMRMapping candidate) {
                return candidate.getAddress()!=null;
            }
        }).getCorrelationId();
        modelDescription = descriptions.get(correlationId);

        eventBus = context.get(ContextKey.EVENTBUS);
        securityContext = context.get(ContextKey.SECURITY_CONTEXT);

        return eventBus!=null
                && modelDescription!=null
                && securityContext!=null;
    }

    @Override
    public ReificationWidget reify(final InteractionUnit<StereoTypes> interactionUnit, final Context context)
    {
        return new PropertyEditorAdapter(interactionUnit, eventBus, modelDescription);
    }

    @Override
    public boolean appliesTo(final InteractionUnit<StereoTypes> interactionUnit)
    {
        return StereoTypes.Properties == interactionUnit.getStereotype();
    }

    class PropertyEditorAdapter implements ReificationWidget, ModelNodePropertyManagement
    {

        final InteractionUnit interactionUnit;
        private final ModelNodePropertyEditor editor;
        private String objectName = null;
        private SafeHtmlBuilder helpTexts;
        private EventBus coordinator;

        PropertyEditorAdapter(final InteractionUnit<StereoTypes> interactionUnit, EventBus coordinator, final ModelNode modelDescription)
        {
            this.interactionUnit = interactionUnit;
            this.coordinator = coordinator;

            DMRMapping dmrMapping = (DMRMapping)
                    this.interactionUnit.findMapping(MappingType.DMR);

            ModelNode attDesc = modelDescription.get("attributes").asObject();
            if(dmrMapping.getObjects().size()>0
                    && attDesc .hasDefined(dmrMapping.getObjects().get(0)))
            {
                // mapped to ModelType.Object
                objectName = dmrMapping.getObjects().get(0);
                ModelNode description = attDesc.get(objectName).asObject();
                Log.debug(description.toString());

            }
            else
            {
                Log.warn(interactionUnit.getId() + "is missing valid DMR mapping");
            }

            this.editor = new ModelNodePropertyEditor(PropertyEditorAdapter.this, true);

        }

        @Override
        public void onCreateProperty(String reference, Property prop) {

        }

        @Override
        public void onDeleteProperty(String reference, Property prop) {
            Window.alert("Not yet implemented");
        }

        @Override
        public void onChangeProperty(String reference, Property prop) {

        }

        @Override
        public void launchNewPropertyDialoge(String reference) {
            Window.alert("Not yet implemented");
        }

        @Override
        public void closePropertyDialoge() {

        }

        @Override
        public InteractionUnit getInteractionUnit() {
            return interactionUnit;
        }

        @Override
        public void add(final ReificationWidget widget)
        {
            throw new UnsupportedOperationException();
        }

        @Override
        public Widget asWidget()
        {

            // handle resets within this scope
            coordinator.addHandler(SystemEvent.TYPE, new SystemEvent.Handler() {
                @Override
                public boolean accepts(SystemEvent event) {
                    return event.getId().equals(CommonQNames.RESET_ID) ;
                }

                @Override
                public void onSystemEvent(SystemEvent event) {


                    Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
                        @Override
                        public void execute() {
                            // request loading of data
                            InteractionEvent loadEvent = new InteractionEvent(JBossQNames.LOAD_ID);

                            // update interaction units
                            coordinator.fireEventFromSource(
                                    loadEvent,
                                    interactionUnit.getId()
                            );
                        }
                    });
                }
            });


            // handle the results of function calls
            coordinator.addHandler(PresentationEvent.TYPE, new PresentationEvent.PresentationHandler()
            {
                @Override
                public boolean accepts(PresentationEvent event) {
                    boolean matchingId = event.getTarget().equals(getInteractionUnit().getId());

                    // only single resources accepted (might be collection, see LoadResourceProcedure)
                    boolean payloadMatches = event.getPayload() instanceof ModelNode;

                    return matchingId && payloadMatches;
                }

                @Override
                public void onPresentationEvent(PresentationEvent event) {

                    assert (event.getPayload() instanceof ModelNode) : "Unexpected type "+event.getPayload().getClass();

                    ModelNode modelNode = (ModelNode)event.getPayload();
                    if(modelNode.hasDefined(objectName))
                    {
                        List<Property> properties = modelNode.get(objectName).asPropertyList();
                        editor.setProperties("self", properties);
                    }
                }
            });


            // Register inputs and outputs

            Resource<ResourceType> update = new Resource<ResourceType>(getInteractionUnit().getId(), Presentation);

            getInteractionUnit().setOutputs(SAVE_EVENT, LOAD_EVENT);
            getInteractionUnit().setInputs(RESET, update);

            return editor.asWidget();
        }
    }
}
