package org.jboss.as.console.mbui.behaviour;

import com.google.gwt.core.client.Scheduler;
import org.jboss.as.console.client.Console;
import org.jboss.as.console.client.domain.model.SimpleCallback;
import org.jboss.as.console.mbui.JBossQNames;
import org.jboss.as.console.mbui.model.mapping.AddressMapping;
import org.jboss.as.console.mbui.model.mapping.DMRMapping;
import org.jboss.dmr.client.ModelNode;
import org.jboss.dmr.client.ModelType;
import org.jboss.dmr.client.Property;
import org.jboss.dmr.client.dispatch.DispatchAsync;
import org.jboss.dmr.client.dispatch.impl.DMRAction;
import org.jboss.dmr.client.dispatch.impl.DMRResponse;
import org.useware.kernel.gui.behaviour.ModelDrivenCommand;
import org.useware.kernel.gui.behaviour.Precondition;
import org.useware.kernel.gui.behaviour.PresentationEvent;
import org.useware.kernel.gui.behaviour.Procedure;
import org.useware.kernel.gui.behaviour.StatementContext;
import org.useware.kernel.model.Dialog;
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.structure.InteractionUnit;
import org.useware.kernel.model.structure.QName;

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

import static org.jboss.dmr.client.ModelDescriptionConstants.*;

/**
 * @author Heiko Braun
 * @date 1/21/13
 */
public class    LoadResourceProcedure extends Procedure {

    public final static QName ID = JBossQNames.LOAD_ID;
    private static final Resource<ResourceType> LOAD = new Resource<ResourceType>(ID, ResourceType.Interaction);

    private final DispatchAsync dispatcher;
    private final Dialog dialog;

    private InteractionUnit unit;
    private AddressMapping address;
    //private StatementContext statementContext;

    public LoadResourceProcedure (
            final Dialog dialog,
            final QName justification,
            DispatchAsync dispatcher) {

        super(ID, justification);
        this.dialog = dialog;
        this.dispatcher = dispatcher;

        init();

        setCommand(new ModelDrivenCommand() {
            @Override
            public void execute(Dialog dialog, Object data) {

                loadResource(unit.getLabel(), address);
            }
        });

        // behaviour model meta data
        setInputs(LOAD);
        setOutputs(new Resource<ResourceType>(justification, ResourceType.Presentation));
    }

    private void init() {
        unit = dialog.findUnit(getJustification());

        DMRMapping mapping = (DMRMapping) unit.findMapping(MappingType.DMR);
        address = AddressMapping.fromString(mapping.getResolvedAddress());

        // check preconditions of the address token
        final Map<String, Integer> requiredStatements = address.getRequiredStatements();

        // any value expression key becomes a precondition matched against the statement context

        setPrecondition(new Precondition() {
            @Override
            public boolean isMet(StatementContext statementContext) {

                // fail fast if not scope active
                if(!getRuntimeAPI().isActive(unit.getId())) return false;

                boolean missingStatement = false;
                for(String key : requiredStatements.keySet())
                {
                    Integer numRequired = requiredStatements.get(key);
                    Integer numOccurances = statementContext.collect(key).size();
                    missingStatement = (numOccurances<numRequired);
                    if(missingStatement) break; // exit upon first value expression that cannot be resolved
                }
                return !missingStatement;
            }
        });


    }

    private void loadResource(final String name, AddressMapping address) {

        //System.out.println("Exec: "+ID+" on behalf of " + unit.getId());

        // TODO: resolve once and re-use
        StatementContext statementContext = statementScope.getContext(unit.getId());

        final ModelNode operation = address.asResource(statementContext);
        operation.get(OP).set(READ_RESOURCE_OPERATION);
        operation.get(INCLUDE_RUNTIME).set(true);

        dispatcher.execute(new DMRAction(operation), new SimpleCallback<DMRResponse>() {
            @Override
            public void onSuccess(DMRResponse dmrResponse) {
                final  ModelNode response = dmrResponse.get();

                if(response.isFailure())
                {
                    Console.error("Failed to load resource", response.getFailureDescription());
                }
                else
                {

                    final PresentationEvent presentation = new PresentationEvent(getJustification());

                    // the result is either a single resource or a collection
                    ModelNode result = response.get(RESULT);

                    if(ModelType.LIST==result.getType())
                    {
                        List<ModelNode> collection = result.asList();
                        List normalized = new ArrayList<ModelNode>(collection.size());
                        for(ModelNode model : collection)
                        {
                            ModelNode payload = model.get(RESULT).asObject();
                            assignKeyFromAddressNode(payload, model.get(ADDRESS));
                            normalized.add(payload);
                        }
                        presentation.setPayload(normalized);
                    }
                    else
                    {
                        ModelNode payload = result.asObject();
                        assignKeyFromAddressNode(payload, operation.get(ADDRESS));
                        presentation.setPayload(payload);
                    }

                    // unit and target are the same
                    presentation.setTarget(getJustification());

                    Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
                        @Override
                        public void execute() {
                            coordinator.fireEvent(presentation);
                        }
                    });

                }
            }



        });
    }


    /**
     * the model representations we use internally carry along the entity keys.
     * these are derived from the resource address, but will be available as synthetic resource attributes.
     *
     * @param payload
     * @param address
     */
    private static void assignKeyFromAddressNode(ModelNode payload, ModelNode address) {
        List<Property> props = address.asPropertyList();
        Property lastToken = props.get(props.size()-1);
        payload.get("entity.key").set(lastToken.getValue().asString());
    }

    @Override
    public String toString() {
        return "LoadResource "+ getJustification();
    }
}
