package org.jboss.seam.remoting;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.TextMessage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.remoting.messaging.PollError;
import org.jboss.seam.remoting.messaging.PollRequest;
import org.jboss.seam.remoting.wrapper.Wrapper;
import org.jboss.seam.servlet.ContextualHttpServletRequest;
import org.jboss.seam.util.XML;

/**
 * Handles JMS Message poll requests.
 *
 * @author Shane Bryzak
 */
public class PollHandler extends BaseRequestHandler implements RequestHandler
{
  private static final LogProvider log = Logging.getLogProvider(SubscriptionHandler.class);

  private static final byte[] ERRORS_TAG_OPEN_START = "<errors token=\"".getBytes();
  private static final byte[] ERRORS_TAG_OPEN_END = "\">".getBytes();

  private static final byte[] ERROR_TAG_OPEN_START = "<error code=\"".getBytes();
  private static final byte[] ERROR_TAG_OPEN_END = "\">".getBytes();
  private static final byte[] ERROR_TAG_CLOSE = "</error>".getBytes();

  private static final byte[] MESSAGES_TAG_OPEN_START = "<messages token=\"".getBytes();
  private static final byte[] MESSAGES_TAG_OPEN_END = "\">".getBytes();
  private static final byte[] MESSAGES_TAG_CLOSE = "</messages>".getBytes();

  private static final byte[] MESSAGE_TAG_OPEN_START = "<message type=\"".getBytes();
  private static final byte[] MESSAGE_TAG_OPEN_END = "\">".getBytes();
  private static final byte[] MESSAGE_TAG_CLOSE = "</message>".getBytes();

  private static final byte[] VALUE_TAG_OPEN = "<value>".getBytes();
  private static final byte[] VALUE_TAG_CLOSE = "</value>".getBytes();

  public void handle(HttpServletRequest request, final HttpServletResponse response)
      throws Exception
  {
    // We're sending an XML response, so set the response content type to text/xml
    response.setContentType("text/xml");

    // Parse the incoming request as XML
    SAXReader xmlReader = XML.getSafeSaxReader();
    Document doc = xmlReader.read(request.getInputStream());
    Element env = doc.getRootElement();

    final List<PollRequest> polls = unmarshalRequests(env);

    new ContextualHttpServletRequest(request)
    {
       @Override
       public void process() throws Exception
       {        
          for (PollRequest req : polls)
          {
             req.poll();
          }
      
          // Package up the response
          marshalResponse(polls, response.getOutputStream());      
       }
    }.run();
  }


  private List<PollRequest> unmarshalRequests(Element env)
      throws Exception
  {
    try
    {
      List<PollRequest> requests = new ArrayList<PollRequest>();

      List<Element> requestElements = env.element("body").elements("poll");
      for (Element e : requestElements)
      {
        requests.add(new PollRequest(e.attributeValue("token"),
                                     Integer.parseInt(e.attributeValue("timeout"))));
      }

      return requests;
    }
    catch (Exception ex)
    {
      log.error("Error unmarshalling subscriptions from request", ex);
      throw ex;
    }
  }

  private void marshalResponse(List<PollRequest> reqs, OutputStream out)
      throws IOException
  {
    out.write(ENVELOPE_TAG_OPEN);
    out.write(BODY_TAG_OPEN);

    for (PollRequest req : reqs)
    {
      if (req.getErrors() != null && req.getErrors().size() > 0)
      {
        out.write(ERRORS_TAG_OPEN_START);
        out.write(req.getToken().getBytes());
        out.write(ERRORS_TAG_OPEN_END);
        for (PollError err : req.getErrors())
        {
          writeError(err, out);
        }
      }
      else  if (req.getMessages() != null && req.getMessages().size() > 0)
      {
        out.write(MESSAGES_TAG_OPEN_START);
        out.write(req.getToken().getBytes());
        out.write(MESSAGES_TAG_OPEN_END);
        for (Message m : req.getMessages()) {
          try {
            writeMessage(m, out);
          }
          catch (JMSException ex) {
          }
          catch (IOException ex) {
          }
        }
        out.write(MESSAGES_TAG_CLOSE);
      }
    }

    out.write(BODY_TAG_CLOSE);
    out.write(ENVELOPE_TAG_CLOSE);
    out.flush();
  }

  private void writeMessage(Message m, OutputStream out)
      throws IOException, JMSException
  {
    out.write(MESSAGE_TAG_OPEN_START);

    // We need one of these to maintain a list of outbound references
    CallContext ctx = new CallContext();
    Object value = null;

    if (m instanceof TextMessage)
    {
      out.write("text".getBytes());
      value = ((TextMessage) m).getText();
    }
    else if (m instanceof ObjectMessage)
    {
      out.write("object".getBytes());
      value = ((ObjectMessage) m).getObject();
    }

    out.write(MESSAGE_TAG_OPEN_END);

    out.write(VALUE_TAG_OPEN);
    ctx.createWrapperFromObject(value, "").marshal(out);
    out.write(VALUE_TAG_CLOSE);

    out.write(REFS_TAG_OPEN);

    // Using a for-loop, because stuff can get added to outRefs as we recurse the object graph
    for (int i = 0; i < ctx.getOutRefs().size(); i++)
    {
      Wrapper wrapper = ctx.getOutRefs().get(i);

      out.write(REF_TAG_OPEN_START);
      out.write(Integer.toString(i).getBytes());
      out.write(REF_TAG_OPEN_END);

      wrapper.serialize(out);

      out.write(REF_TAG_CLOSE);
    }

    out.write(REFS_TAG_CLOSE);

    out.write(MESSAGE_TAG_CLOSE);
  }

  private void writeError(PollError error, OutputStream out)
      throws IOException
  {
    out.write(ERROR_TAG_OPEN_START);
    out.write(error.getCode().getBytes());
    out.write(ERROR_TAG_OPEN_END);
    out.write(error.getMessage().getBytes());
    out.write(ERROR_TAG_CLOSE);
  }
}
