/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/quercus/lib/bam/BamModule.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Emil Ong
 */

package com.caucho.quercus.lib.bam;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
import java.util.logging.Logger;

import com.caucho.bam.BamError;
import com.caucho.bam.actor.ActorSender;
import com.caucho.bam.actor.SimpleActorSender;
import com.caucho.bam.stream.MessageStream;
import com.caucho.bam.stream.NullActor;
import com.caucho.bam.stream.NullMessageStream;
import com.caucho.hemp.broker.HempBroker;
import com.caucho.hmtp.HmtpClient;
import com.caucho.quercus.annotation.ClassImplementation;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.env.BooleanValue;
import com.caucho.quercus.env.ConstStringValue;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.StringValue;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.xmpp.im.ImMessage;
import com.caucho.xmpp.im.ImPresence;
import com.caucho.xmpp.im.RosterItem;
import com.caucho.xmpp.im.RosterQuery;
import com.caucho.xmpp.im.Text;

/**
 * BAM functions
 */
@ClassImplementation
public class BamModule extends AbstractQuercusModule
{
  private static final Logger log
    = Logger.getLogger(BamModule.class.getName());
  private static final L10N L = new L10N(BamModule.class);

  private static final StringValue PHP_SELF 
    = new ConstStringValue("PHP_SELF");

  private static final StringValue SERVER_NAME 
    = new ConstStringValue("SERVER_NAME");

  private static BamPhpActor getActor(Env env)
  {
    Value actorValue = env.getGlobalValue("_quercus_bam_actor");

    if (actorValue != null && ! actorValue.isNull())
      return (BamPhpActor) actorValue.toJavaObject();

    return null;
  }

  private static ActorSender getActorClient(Env env)
  {
    ActorSender connection
      = (ActorSender) env.getSpecialValue("_quercus_bam_connection");

    // create a connection lazily
    if (connection == null) {
      HempBroker broker = HempBroker.getCurrent();

      String address = "php@" + env.getGlobalVar("_SERVER").get(SERVER_NAME);
      String resource = env.getGlobalVar("_SERVER").get(PHP_SELF).toString();

      if (resource.indexOf('/') == 0)
        resource = resource.substring(1);
      
      NullActor stream = new NullActor(address, broker);

      connection = new SimpleActorSender(stream, broker, address, resource);
      env.addCleanup(new BamConnectionResource(connection));
      env.setSpecialValue("_quercus_bam_connection", connection);
    }

    return connection;
  }

  private static BamPhpServiceManager getServiceManager(Env env)
  {
    Value managerValue = env.getGlobalValue("_quercus_bam_service_manager");

    if (managerValue != null && ! managerValue.isNull())
      return (BamPhpServiceManager) managerValue.toJavaObject();

    return null;
  }

  private static MessageStream getBrokerStream(Env env)
  {
    BamPhpActor actor = getActor(env);

    if (actor != null)
      return actor.getBroker();

    ActorSender connection = getActorClient(env);

    return connection.getBroker();
  }

  private static String getAddress(Env env)
  {
    BamPhpActor actor = getActor(env);

    if (actor != null)
      return actor.getAddress();

    ActorSender connection = getActorClient(env);

    return connection.getAddress();
  }

  public static Value bam_login(Env env, 
                                String url, 
                                String username, 
                                String password)
  {
    BamPhpActor actor = getActor(env);

    if (actor != null)
      return env.error("bam_login not available from actor script");

    HmtpClient client = null;//new HmtpClient(url);

    BamConnectionResource resource = new BamConnectionResource(client);
    env.addCleanup(resource);

    try {
      client.connect(username, password);
    }
    catch (Exception e) {
      e.printStackTrace();
      return env.error("Unable to connect to BAM server", e);
    }

    env.setSpecialValue("_quercus_bam_connection", client);

    return BooleanValue.TRUE;
  }

  public static Value bam_service_exists(Env env, String address)
  {
    BamPhpServiceManager manager = getServiceManager(env);

    if (manager == null)
      return env.error("bam_service_exists must be called from " + 
                       "service manager script");

    return BooleanValue.create(manager.hasChild(address));
  }

  /**
   * Registers a "child" service that is represented by the given script.
   **/
  public static Value bam_register_service(Env env, String address, String script)
  {
    BamPhpServiceManager manager = getServiceManager(env);

    if (manager == null)
      return env.error("bam_register_service must be called from " + 
                       "service manager script");

    Path path = env.getSelfDirectory().lookup(script);

    if (! path.exists())
      return env.error("script not found: " + script);

    BamPhpActor child = new BamPhpActor();
    child.setAddress(address);
    child.setScript(path);
    // child.setBroker(manager.getBroker());

    //InjectManager container = InjectManager.getCurrent();
    //container.injectObject(child);

    manager.addChild(address, child);

    return BooleanValue.TRUE;
  }

  /**
   * Registers a "child" service that is represented by the given script.
   **/
  public static Value bam_unregister_service(Env env, String address)
  {
    BamPhpServiceManager manager = getServiceManager(env);

    if (manager == null)
      return env.error("bam_unregister_service must be called from " + 
                       "service manager script");

    BamPhpActor service = manager.removeChild(address);

    if (service == null)
      return BooleanValue.FALSE;

    // XXX: manager.getBroker().removeMailbox(service);

    return BooleanValue.TRUE;
  }

  public static Value bam_actor_exists(Env env, String address)
  {
    BamPhpActor actor = getActor(env);

    if (actor == null)
      return env.error("bam_actor_exists must be called from actor script");

    return BooleanValue.create(actor.hasChild(address));
  }

  /**
   * Registers a "child" actor that is represented by the given script.
   **/
  public static Value bam_register_actor(Env env, String address, String script)
  {
    BamPhpActor actor = getActor(env);

    if (actor == null)
      return env.error("bam_register_actor must be called from actor script");

    BamPhpActor child = new BamPhpActor();
    child.setAddress(address);

    Path path = env.getSelfDirectory().lookup(script);

    if (! path.exists())
      return env.error("script not found: " + script);

    child.setScript(path);

    //InjectManager container = InjectManager.getCurrent();
    //container.injectObject(child);

    actor.addChild(address, child);

    return BooleanValue.TRUE;
  }

  public static String bam_my_address(Env env)
  {
    return getAddress(env);
  }

  //
  // Utilities
  //

  public static String bam_bare_address(Env env, String uri)
  {
    int slash = uri.indexOf('/');

    if (slash < 0)
      return uri;

    return uri.substring(0, slash);
  }

  public static String bam_address_resource(Env env, String uri)
  {
    int slash = uri.indexOf('/');

    if (slash < 0 || slash == uri.length() - 1)
      return "";

    return uri.substring(slash + 1);
  }

  //
  // Transmit
  //

  public static void bam_send_message(Env env, String to, Serializable value)
  {
    getBrokerStream(env).message(to, getAddress(env), value);
  }

  public static void bam_send_message_error(Env env, 
                                            String to, 
                                            Serializable value, 
                                            BamError error)
  {
    getBrokerStream(env).messageError(to, getAddress(env), value, error);
  }

  public static Value bam_send_query(Env env, 
                                         long id, 
                                         String to, 
                                         Serializable value)
  {
    String from = getAddress(env);
    getBrokerStream(env).query(id, to, from, value);

    return BooleanValue.TRUE;
  }

  public static void bam_send_query_result(Env env, 
                                           long id, 
                                           String to,
                                           Serializable value)
  {
    getBrokerStream(env).queryResult(id, to, getAddress(env), value);
  }

  public static void bam_send_query_error(Env env, 
                                          long id, String to,
                                          Serializable value, BamError error)
  {
    getBrokerStream(env).queryError(id, to, getAddress(env), value, error);
  }

  public static Value im_send_message(Env env, 
                                      String to, 
                                      String from, 
                                      Value body,
                                      @Optional("chat") String type,
                                      @Optional Value subject,
                                      @Optional String thread,
                                      @Optional Serializable[] extras)
  {
    Text[] subjects = null;

    // extract subject text
    if (subject != null) {
      if (subject.isArray()) {
        subjects = new Text[subject.getSize()];

        int i = 0;
        Iterator<Value> iterator = subject.getValueIterator(env);

        while (iterator.hasNext()) {
          Value subjectValue = iterator.next();

          if (! subjectValue.isString())
            return env.error("subject values must be strings");

          subjects[i++] = new Text(subjectValue.toString());
        }
      }
      else if (subject.isString()) {
        if (! subject.isString())
          return env.error("subject values must be strings");

        subjects = new Text[] { new Text(subject.toString()) };
      }
    }

    // extract body text
    Text[] bodies = null;

    if (body.isArray()) {
      bodies = new Text[body.getSize()];

      int i = 0;
      Iterator<Value> iterator = body.getValueIterator(env);

      while (iterator.hasNext()) {
        Value bodyValue = iterator.next();

        if (! bodyValue.isString())
          return env.error("body values must be strings");

        bodies[i++] = new Text(bodyValue.toString());
      }
    }
    else if (body.isString()) {
      if (! body.isString())
        return env.error("body values must be strings");

      bodies = new Text[] { new Text(body.toString()) };
    }

    ImMessage message = new ImMessage(to, from, type, 
                                      subjects, bodies, thread, extras);
    
    bam_send_message(env, to, message);

    return BooleanValue.TRUE;
  }

  public static RosterItem im_create_roster_item(Env env, 
                                                 String address,
                                                 @Optional String name,
                                                 @Optional String subscription,
                                                 @Optional 
                                                 ArrayList<String> groupList)
  {
    if ("".equals(subscription))
      subscription = "to";

    return new RosterItem(null, address, name, subscription, groupList);
  }

  public static void im_send_roster(Env env, 
                                    long id, String to, 
                                    ArrayList<RosterItem> roster)
  {
    bam_send_query_result(env, id, to, new RosterQuery(roster));
  }
  
  private static ImPresence createPresence(Env env, 
                                           String to, 
                                           String from,
                                           String show,
                                           String status,
                                           int priority,
                                           ArrayList<Serializable> extras)
  {
    if ("".equals(from))
      from = getAddress(env);

    if ("".equals(show))
      show = null;

    Text statusText = null;

    if (! "".equals(status))
      statusText = new Text(status);

    return new ImPresence(to, from, show, statusText, priority, extras);
  }


  public static void im_send_presence(Env env, 
                                      String to, 
                                      @Optional String from,
                                      @Optional String show,
                                      @Optional String status,
                                      @Optional int priority,
                                      @Optional ArrayList<Serializable> extras)
  {
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  public static void im_send_presence_unavailable(Env env, 
                                                  String to, 
                                                  @Optional String from,
                                                  @Optional String show,
                                                  @Optional String status,
                                                  @Optional int priority,
                                                  @Optional 
                                                  ArrayList<Serializable> 
                                                  extras)
  {
    // XXX: needs to be ImPresenceUnavailable
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  /**
   * Makes a subscription request.
   **/
  public static void im_send_presence_subscribe(Env env, 
                                                String to, 
                                                @Optional String from,
                                                @Optional String show,
                                                @Optional String status,
                                                @Optional int priority,
                                                @Optional 
                                                ArrayList<Serializable> extras)
  {
    // XXX: presenceSubscribe
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  /**
   * Approves a subscription request.
   **/
  public static void im_send_presence_subscribed(Env env, 
                                                 String to, 
                                                 @Optional String from,
                                                 @Optional String show,
                                                 @Optional String status,
                                                 @Optional int priority,
                                                 @Optional 
                                                 ArrayList<Serializable> extras)
  {
    // presenceSubscribed
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  /**
   * Makes an unsubscription request.
   **/
  public static void im_send_presence_unsubscribe(Env env, 
                                                  String to, 
                                                  @Optional String from,
                                                  @Optional String show,
                                                  @Optional String status,
                                                  @Optional int priority,
                                                  @Optional 
                                                  ArrayList<Serializable> 
                                                  extras)
  {
    // XXX: presenceUnsubscribe
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  /**
   * Rejects a subscription request.
   **/
  public static void im_send_presence_unsubscribed(Env env, 
                                                   String to, 
                                                   @Optional String from,
                                                   @Optional String show,
                                                   @Optional String status,
                                                   @Optional int priority,
                                                   @Optional 
                                                   ArrayList<Serializable> 
                                                   extras)
  {
    // presenceUnsubscribe
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  public static void im_send_presence_probe(Env env, 
                                            String to, 
                                            @Optional String from,
                                            @Optional String show,
                                            @Optional String status,
                                            @Optional int priority,
                                            @Optional 
                                            ArrayList<Serializable> extras)
  {
    // presenceProbe
    ImPresence presence = 
      createPresence(env, to, from, show, status, priority, extras);

    bam_send_message(env, to, presence);
  }

  /**
   * Dispatches messages, queries, and presences to handler functions based
   * on their prefixes.
   **/
  public static Value bam_dispatch(Env env)
  {
    // manager script dispatch
    BamPhpServiceManager manager = getServiceManager(env);

    if (manager != null) {
      AbstractFunction function = null;

      if (env.getGlobalValue("_quercus_bam_start_service") != null) {
        function = env.findFunction("bam_start_service");
      } 
      else if (env.getGlobalValue("_quercus_bam_stop_service") != null) {
        function = env.findFunction("bam_stop_service");
      }

      if (function == null) {
        env.setGlobalValue("_quercus_bam_function_return", BooleanValue.FALSE);

        return BooleanValue.FALSE;
      }

      Value address = env.getGlobalValue("_quercus_bam_service_address");
      Value ret = function.call(env, address);

      env.setGlobalValue("_quercus_bam_function_return", ret);

      return BooleanValue.TRUE;
    }

    // actor script dispatch

    Value eventTypeValue = env.getGlobalValue("_quercus_bam_event_type");

    if (eventTypeValue == null)
      return BooleanValue.FALSE;

    BamEventType eventType = (BamEventType) eventTypeValue.toJavaObject();

    Value to = env.getGlobalValue("_quercus_bam_to");
    Value from = env.getGlobalValue("_quercus_bam_from");
    Value value = env.getGlobalValue("_quercus_bam_value");

    AbstractFunction function = findFunction(env, eventType.getPrefix(), value);

    if (function == null) {
      log.fine(L.l("bam handler function not found for {0}", eventType));

      return BooleanValue.FALSE;
    }

    Value functionReturn = BooleanValue.FALSE;

    if (eventType.hasId() && eventType.hasError()) {
      Value id = env.getGlobalValue("_quercus_bam_id");
      Value error = env.getGlobalValue("_quercus_bam_error");

      functionReturn = function.call(env, id, to, from, value, error);
    } 
    else if (! eventType.hasId() && eventType.hasError()) {
      Value error = env.getGlobalValue("_quercus_bam_error");

      functionReturn = function.call(env, to, from, value, error);
    }
    else if (eventType.hasId() && ! eventType.hasError()) {
      Value id = env.getGlobalValue("_quercus_bam_id");

      functionReturn = function.call(env, id, to, from, value);
    }
    else {
      functionReturn = function.call(env, to, from, value);
    }

    env.setGlobalValue("_quercus_bam_function_return", functionReturn);

    return functionReturn;
  }

  /**
   * Finds the handler function for a value with the given prefix.  If there
   * is a specific handler for a specific value type, that is returned 
   * otherwise the generic handler (with the name of the prefix) is returned
   * if found.
   **/
  private static AbstractFunction findFunction(Env env, 
                                               String prefix, 
                                               Value value)
  {
    if (value == null)
      return env.findFunction(prefix);

    Object obj = value.toJavaObject();

    if (obj == null)
      return env.findFunction(prefix);

    String typeName = obj.getClass().getSimpleName().toLowerCase(Locale.ENGLISH);
    String functionName = prefix + '_' + typeName;

    AbstractFunction function = env.findFunction(functionName);

    if (function == null)
      function = env.findFunction(prefix);

    return function;
  }

}

