/*
 *  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.vysper.xmpp.modules.extension.xep0050_adhoc_commands;

import org.apache.commons.lang.StringUtils;
import org.apache.vysper.xml.fragment.XMLElement;
import org.apache.vysper.xml.fragment.XMLSemanticError;
import org.apache.vysper.xmpp.addressing.Entity;
import org.apache.vysper.xmpp.modules.core.base.handler.DefaultIQHandler;
import org.apache.vysper.xmpp.protocol.NamespaceURIs;
import org.apache.vysper.xmpp.server.ServerRuntimeContext;
import org.apache.vysper.xmpp.server.SessionContext;
import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
import org.apache.vysper.xmpp.stanza.IQStanza;
import org.apache.vysper.xmpp.stanza.IQStanzaType;
import org.apache.vysper.xmpp.stanza.Stanza;
import org.apache.vysper.xmpp.stanza.StanzaBuilder;
import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
import org.apache.vysper.xmpp.stanza.StanzaErrorType;

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

/**
 */
public class AdhocCommandIQHandler extends DefaultIQHandler {

    protected final Collection<AdhocCommandSupport> adhocCommandSupporters;
    protected final Map<String, AdhocCommandHandler> runningCommands = new HashMap<String, AdhocCommandHandler>();

    public AdhocCommandIQHandler(Collection<AdhocCommandSupport> adhocCommandSupporters) {
        this.adhocCommandSupporters = adhocCommandSupporters;
    }

    @Override
    protected boolean verifyNamespace(Stanza stanza) {
        return verifyInnerNamespace(stanza, NamespaceURIs.XEP0050_ADHOC_COMMANDS);
    }

    @Override
    protected boolean verifyInnerElement(Stanza stanza) {
        return verifyInnerElementWorker(stanza, "command");
    }

    @Override
    protected Stanza handleSet(IQStanza stanza, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) {
        Entity from = stanza.getFrom();
        if (from == null) {
            from = sessionContext.getInitiatingEntity();
        }

        AdhocCommandHandler commandHandler = null;
        String commandNode;
        String requestedSessionId;
        String action;  // execute | cancel
        List<XMLElement> commandElements = null;
        try {
            XMLElement commandElement = stanza.getSingleInnerElementsNamed("command");
            if (commandElement == null) {
                return ServerErrorResponses.getStanzaError(StanzaErrorCondition.BAD_REQUEST, stanza,
                        StanzaErrorType.MODIFY, "command is missing", null, null);
            }
            commandNode = commandElement.getAttributeValue("node");
            requestedSessionId = commandElement.getAttributeValue("sessionid");
            action = commandElement.getAttributeValue("action");

            if (StringUtils.isEmpty(requestedSessionId)) {
                for (AdhocCommandSupport commandSupport : adhocCommandSupporters) {
                    commandHandler = commandSupport.getCommandHandler(commandNode, from);
                    if (commandHandler != null) {
                        runningCommands.put(commandHandler.getSessionId(), commandHandler);
                        break;
                    }
                }
            } else {
                commandHandler = runningCommands.get(requestedSessionId);
                if (commandHandler == null) {
                    return ServerErrorResponses.getStanzaError(StanzaErrorCondition.BAD_REQUEST, stanza,
                            StanzaErrorType.CANCEL, "command session id not found: " + requestedSessionId, null, null);
                }
            }
            commandElements = commandElement.getInnerElements();
        } catch (XMLSemanticError xmlSemanticError) {
            return ServerErrorResponses.getStanzaError(StanzaErrorCondition.BAD_REQUEST, stanza,
                    StanzaErrorType.MODIFY, "command is not well-formed", null, null);
        }

        if ("cancel".equals(action)) {
            runningCommands.remove(requestedSessionId);
            return buildResponse(stanza, from, commandNode, requestedSessionId, "canceled");
        }

        // handle unauthorized access (or command does not exist at all)
        if (commandHandler == null) {
            return ServerErrorResponses.getStanzaError(StanzaErrorCondition.FORBIDDEN, stanza,
                    StanzaErrorType.CANCEL, "command is not available", null, null);
        }

        List<Note> notes = new ArrayList<Note>();
        final XMLElement result = commandHandler.process(commandElements, notes);

        final String sessionId = commandHandler.getSessionId();
        final boolean isExecuting = commandHandler.isExecuting();

        final Stanza response = buildResponse(stanza, from, commandNode, sessionId, 
                                             isExecuting ? "executing" : "completed", result, notes,
                                              commandHandler.isPrevAllowed(), commandHandler.isNextAllowed());
        return response;
    }

    private Stanza buildResponse(IQStanza stanza, Entity from, String commandNode, String sessionId, 
                                 final String status) {
        return buildResponse(stanza, from, commandNode, sessionId, status, null, null, false, false);
    }
    
    private Stanza buildResponse(IQStanza stanza, Entity from, String commandNode, String sessionId,
                                 final String status, XMLElement result,
                                 List<Note> notes, boolean isPrevAllowed, boolean isNextAllowed) {
        final StanzaBuilder iqStanza = StanzaBuilder.createIQStanza(null, from, IQStanzaType.RESULT, stanza.getID());
        iqStanza.startInnerElement("command");
        iqStanza.declareNamespace("", NamespaceURIs.XEP0050_ADHOC_COMMANDS);
        iqStanza.addAttribute("node", commandNode);
        iqStanza.addAttribute("sessionid", sessionId);
        iqStanza.addAttribute("status", status);
        if (notes != null && notes.size() > 0) {
            for (Note note : notes) {
                iqStanza.startInnerElement("note");
                iqStanza.addAttribute("type", note.getType().name());
                if (note.getText() != null) iqStanza.addText(note.getText());
                iqStanza.endInnerElement();
            }
        }
        if (isNextAllowed || isPrevAllowed) {
            iqStanza.startInnerElement("action");
            if (isPrevAllowed) iqStanza.startInnerElement("prev").endInnerElement();
            if (isNextAllowed) iqStanza.startInnerElement("next").endInnerElement();
            iqStanza.endInnerElement();
        }
        if (result != null) {
            iqStanza.addPreparedElement(result);
        }
        iqStanza.endInnerElement();

        return iqStanza.build();
    }
/*
<iq from='shakespeare.lit'
    id='add-user-1'
    to='bard@shakespeare.lit/globe'
    type='result'
    xml:lang='en'>
  <command xmlns='http://jabber.org/protocol/commands' 
           node='http://jabber.org/protocol/admin#add-user'
           sessionid='add-user:20040408T0337Z'
           status='executing'>
    <x xmlns='jabber:x:data' type='form'>
      <title>Adding a User</title>
      <instructions>Fill out this form to add a user.</instructions>
      <field type='hidden' var='FORM_TYPE'>
        <value>http://jabber.org/protocol/admin</value>
      </field>
      <field label='The Jabber ID for the account to be added'
             type='jid-single'
             var='accountjid'>
        <required/>
      </field>
      <field label='The password for this account'
             type='text-private'
             var='password'/>
      <field label='Retype password'
             type='text-private'
             var='password-verify'/>
      <field label='Email address'
             type='text-single'
             var='email'/>
      <field label='Given name'
             type='text-single'
             var='given_name'/>
      <field label='Family name'
             type='text-single'
             var='surname'/>
    </x>
  </command>
</iq>
    
     */
    

    @Override
    protected Stanza handleGet(IQStanza stanza, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) {

        Entity to = stanza.getTo();
        Entity from = stanza.getFrom();

        if (from == null) {
            from = sessionContext.getInitiatingEntity();
        }

        StanzaBuilder stanzaBuilder = StanzaBuilder.createIQStanza(stanza.getTo(), stanza.getFrom(),
                IQStanzaType.RESULT, stanza.getID());
        return stanzaBuilder.build();
    }
}