/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY 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 along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.bpm.console.server;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.bpm.console.client.model.ProcessInstanceRef;
import org.jboss.bpm.console.server.integration.ManagementFactory;
import org.jboss.bpm.console.server.integration.ProcessManagement;
import org.jboss.bpm.console.server.integration.TaskManagement;
import org.jboss.bpm.console.server.plugin.FormAuthorityRef;
import org.jboss.bpm.console.server.plugin.FormDispatcherPlugin;
import org.jboss.bpm.console.server.plugin.PluginMgr;
import org.jboss.bpm.console.server.util.RsComment;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author Heiko.Braun <heiko.braun@jboss.com>
 */
@Path("form")
@RsComment(
    title = "Form Processing",
    description = "Web based form processing"
)
public class FormProcessingFacade
{
  private static final Log log = LogFactory.getLog(FormProcessingFacade.class);

  private FormDispatcherPlugin formPlugin;

  private ProcessManagement processManagement;

  private TaskManagement taskManagement;
  private static final String SUCCESSFULLY_PROCESSED_INPUT =
      "<div style='font-family:sans-serif; padding:10px;'>" +
          "<h3>Successfully processed input</h3><p/>" +
          "You can now close this window." +
          "</div>";

  /**
   * Lazy load the {@link org.jboss.bpm.console.server.integration.TaskManagement}
   */
  private TaskManagement getTaskManagement()
  {
    if(null==this.taskManagement)
    {
      ManagementFactory factory = ManagementFactory.newInstance();
      this.taskManagement = factory.createTaskManagement();
      log.debug("Using ManagementFactory impl:" + factory.getClass().getName());
    }

    return this.taskManagement;
  }

  private ProcessManagement getProcessManagement()
  {
    if(null==this.processManagement)
    {
      ManagementFactory factory = ManagementFactory.newInstance();
      this.processManagement = factory.createProcessManagement();
      log.debug("Using ManagementFactory impl:" + factory.getClass().getName());
    }

    return this.processManagement;
  }

  /**
   * Lazy load the {@link org.jboss.bpm.console.server.integration.TaskManagement}
   */
  private FormDispatcherPlugin getFormDispatcherPlugin()
  {
    if(null==this.formPlugin)
    {
      this.formPlugin = PluginMgr.load(FormDispatcherPlugin.class);
      log.debug("Using FormDispatcherPlugin impl:" + this.formPlugin);
    }

    return this.formPlugin;
  }


  @GET
  @Path("task/{id}/render")
  @Produces("text/html")
  public Response renderTaskUI(
      @PathParam("id")
      String taskId
  )
  {
    return provideForm(new FormAuthorityRef(taskId));
  }

  @GET
  @Path("process/{id}/render")
  @Produces("text/html")
  public Response renderProcessUI(
      @PathParam("id")
      String definitionId
  )
  {
    return provideForm(new FormAuthorityRef(definitionId, FormAuthorityRef.Type.PROCESS));
  }

  @POST
  @Path("task/{id}/complete")
  @Produces("text/html")
  @Consumes("multipart/form-data")
  public Response closeTaskWithUI(
      @Context
      HttpServletRequest request,
      @PathParam("id")
      String taskId,
      MultipartFormDataInput payload
  )
  {
    FieldMapping mapping = createFieldMapping(payload);

    // complete task
    String username = request.getUserPrincipal() != null ?
        request.getUserPrincipal().getName() : null;

    String outcomeDirective = mapping.directives.get("outcome");

    if(outcomeDirective!=null)
    {
      getTaskManagement().completeTask(
          Long.valueOf(taskId), // TODO: change to string id's
          outcomeDirective, // actually a plugin implementation detail
          mapping.processVars,
          username
      );
    }
    else
    {
      getTaskManagement().completeTask(
          Long.valueOf(taskId),
          mapping.processVars,
          username
      );
    }

    return Response.ok(SUCCESSFULLY_PROCESSED_INPUT).build();
  }

  @POST
  @Path("process/{id}/complete")
  @Produces("text/html")
  @Consumes("multipart/form-data")
  public Response startProcessWithUI(
      @Context
      HttpServletRequest request,
      @PathParam("id")
      String definitionId,
      MultipartFormDataInput payload
  )
  {
    FieldMapping mapping = createFieldMapping(payload);

    // start process
    ProcessInstanceRef instance =
        getProcessManagement().newInstance(definitionId, mapping.processVars);

    return Response.ok(SUCCESSFULLY_PROCESSED_INPUT).build();
  }

  private Response provideForm(FormAuthorityRef authorityRef)
  {
    DataHandler dh = getFormDispatcherPlugin().provideForm(
        authorityRef
    );

    if(null==dh)
    {
      throw new RuntimeException("No UI associated with "+authorityRef.getType()+" " + authorityRef.getReferenceId());
    }

    return Response.ok(dh.getDataSource()).type("text/html").build();
  }

  private FieldMapping createFieldMapping(MultipartFormDataInput payload)
  {
    FieldMapping mapping = new FieldMapping();

    Map<String, InputPart> formData = payload.getFormData();
    Iterator<String> partNames = formData.keySet().iterator();

    while(partNames.hasNext())
    {
      final String partName = partNames.next();
      final InputPart part = formData.get(partName);
      final MediaType mediaType = part.getMediaType();

      if(MediaType.TEXT_PLAIN_TYPE.equals(mediaType))
      {
        // RFC2045: Each part has an optional "Content-Type" header
        // that defaults to "text/plain".
        // Can go into process without conversion
        if(mapping.isReserved(partName))
          mapping.directives.put(partName, part.getBodyAsString());
        else
          mapping.processVars.put(partName, part.getBodyAsString());
      }
      else
      {
        // anything else turns into a DataHandler
        final byte[] data = part.getBodyAsString().getBytes();
        DataHandler dh = new DataHandler(
            new DataSource()
            {
              public InputStream getInputStream() throws IOException
              {
                return new ByteArrayInputStream(data);
              }

              public OutputStream getOutputStream() throws IOException
              {
                throw new RuntimeException("This is a readonly DataHandler");
              }

              public String getContentType()
              {
                return mediaType.getType();
              }

              public String getName()
              {
                return partName;
              }
            }
        );

        mapping.processVars.put(partName, dh);
      }
    }

    return mapping;
  }

  private class FieldMapping
  {
    final String[] reservedNames = {"outcome", "form"};   // TODO: implementation detail of the form plugin

    Map<String,Object> processVars = new HashMap<String,Object>();
    Map<String,String> directives = new HashMap<String,String>();

    public boolean isReserved(String name)
    {
      boolean result = false;
      for(String s : reservedNames)
      {
        if(s.equals(name))
        {
          result = true;
          break;
        }
      }
      return result;
    }
  }
}
