/*
 * Copyright (C) 2012 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed 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.jboss.errai.bus.server.service;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.jboss.errai.bus.client.api.builder.DefaultRemoteCallBuilder;
import org.jboss.errai.bus.client.api.messaging.Message;
import org.jboss.errai.bus.client.api.messaging.RequestDispatcher;
import org.jboss.errai.bus.client.util.ErrorHelper;
import org.jboss.errai.bus.server.DefaultTaskManager;
import org.jboss.errai.bus.server.QueueUnavailableException;
import org.jboss.errai.bus.server.ServerMessageBusImpl;
import org.jboss.errai.bus.server.api.ServerMessageBus;
import org.jboss.errai.bus.server.api.SessionProvider;
import org.jboss.errai.bus.server.io.websockets.WebSocketServer;
import org.jboss.errai.bus.server.service.bootstrap.BootstrapContext;
import org.jboss.errai.bus.server.service.bootstrap.OrderedBootstrap;
import org.slf4j.Logger;

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

import static org.slf4j.LoggerFactory.getLogger;

/**
 * Default implementation of the ErraiBus server-side service.
 */
@Singleton
public class ErraiServiceImpl<S> implements ErraiService<S> {

  private ServerMessageBus bus;
  private ErraiServiceConfigurator config;
  private SessionProvider<S> sessionProvider;
  private RequestDispatcher dispatcher;
  private List<Runnable> shutdownHooks = new ArrayList<Runnable>();
  private final Logger log = getLogger(getClass());

  // Silence WELD-001529
  private ErraiServiceImpl() {}
  
  /**
   * Initializes the errai service with a bus and configurator
   *
   * @param configurator - the configurator to take care of the configuration for the service
   */
  @Inject
  public ErraiServiceImpl(final ErraiServiceConfigurator configurator) {
    this.bus = new ServerMessageBusImpl(this, configurator);
    this.config = configurator;
    boostrap();
  }

  private void boostrap() {
    BootstrapContext context = new BootstrapContext(this, bus, config);
    new OrderedBootstrap().execute(context);

    if (ErraiConfigAttribs.ENABLE_WEB_SOCKET_SERVER.getBoolean(config)) {
      WebSocketServer server = new WebSocketServer(this);
      server.start();
    }
  }

  /**
   * Passes off the message to the bus for handling
   *
   * @param message - the message to store/deliver
   */
  @Override
  public void store(Message message) {
    if (message == null) {
      return;
    }

    message.addResources(config.getResourceProviders());

    /*
     * Pass the message off to the messaging bus for handling.
     */
    try {
      getDispatcher().dispatchGlobal(message);
    }
    catch (QueueUnavailableException e) {
      throw e;
    }
    catch (Throwable t) {
      t.printStackTrace();
      if (!message.hasResource("Exception")) {
        message.setResource("Exception", t.getCause());
        ErrorHelper.sendClientError(bus, message, t.getMessage(), t);
      }
    }
  }

  @Override
  public void store(Collection<Message> messages) {
    for (Message m : messages) {
      store(m);
    }
  }

  @Override
  public void stopService() {
    bus.stop();
    DefaultTaskManager.get().requestStop();

    for (Runnable runnable : shutdownHooks) {
      try {
        runnable.run();
      }
      catch (Throwable e) {
        log.error("error executing shutdown hook", e);
      }
    }
    
    bus = null;
    config = null;
    sessionProvider = null;
    dispatcher = null;
    shutdownHooks = null;

    DefaultRemoteCallBuilder.destroyProxyFactory();
    ErraiServiceSingleton.resetProxyAndService();
  }

  /**
   * Gets the bus associated with this service
   *
   * @return the bus associated with this service
   */
  @Override
  public ServerMessageBus getBus() {
    return bus;
  }

  /**
   * Gets the configuration used to initalize the service
   *
   * @return the errai service configurator
   */
  @Override
  public ErraiServiceConfigurator getConfiguration() {
    return config;
  }

  @Override
  public void addShutdownHook(Runnable runnable) {
    shutdownHooks.add(runnable);
  }

  @Override
  public SessionProvider<S> getSessionProvider() {
    return sessionProvider;
  }

  @Override
  public void setSessionProvider(SessionProvider<S> sessionProvider) {
    if (this.sessionProvider != null) {
      throw new IllegalStateException("cannot set session provider more than once.");
    }
    sessionProvider.init(config);
    this.sessionProvider = sessionProvider;
  }

  @Override
  public RequestDispatcher getDispatcher() {
    return dispatcher;
  }

  @Override
  public void setDispatcher(RequestDispatcher dispatcher) {
    if (this.dispatcher != null) {
      throw new IllegalStateException("cannot set dispatcher more than once.");
    }
    this.dispatcher = dispatcher;
  }
}
