/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010 Red Hat Inc. and/or its affiliates and other
 * contributors as indicated by the @author tags. All rights reserved.
 * See the copyright.txt 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.infinispan.server.websocket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.infinispan.Cache;
import org.infinispan.manager.CacheContainer;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.server.core.AbstractProtocolServer;
import org.infinispan.server.websocket.handlers.GetHandler;
import org.infinispan.server.websocket.handlers.NotifyHandler;
import org.infinispan.server.websocket.handlers.PutHandler;
import org.infinispan.server.websocket.handlers.RemoveHandler;
import org.infinispan.util.TypedProperties;
import org.infinispan.util.concurrent.ConcurrentMapFactory;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;

/**
 * An HTTP server which serves Web Socket requests on an Infinispan cacheManager.
 * <p/>
 * Websocket specific code lifted from Netty WebSocket Server example.
 */
public class WebSocketServer extends AbstractProtocolServer {

   public static final String INFINISPAN_WS_JS_FILENAME = "infinispan-ws.js";

   private static String javascript;
   private Channel channel;

   public WebSocketServer() {
      super("WebSocketServerThread");
   }

   public OneToOneEncoder getEncoder() {
      return null;
   }

   public ChannelHandler getDecoder() {
      return null;
   }

//   @Override
   public void start(Properties p, EmbeddedCacheManager cacheManager) {
      Properties properties = p == null ? new Properties() : p;
      super.start(properties, cacheManager, 8181);
   }

   @Override
   public void startTransport(int idleTimeout, boolean tcpNoDelay, int sendBufSize, int recvBufSize, TypedProperties typedProps) {
      InetSocketAddress address = new InetSocketAddress(getHost(), getPort());
      Executor masterExecutor = Executors.newCachedThreadPool();
      Executor workerExecutor = Executors.newCachedThreadPool();

      NioServerSocketChannelFactory factory = new NioServerSocketChannelFactory(masterExecutor, workerExecutor, workerThreads());

      // Configure the server.
      ServerBootstrap bootstrap = new ServerBootstrap(factory);

      // Set up the event pipeline factory.
      bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory(cacheManager()));

      // Bind and start to accept incoming connections.
      bootstrap.setOption("child.tcpNoDelay", tcpNoDelay);
      if (sendBufSize > 0) bootstrap.setOption("child.sendBufferSize", sendBufSize);
      if (recvBufSize > 0) bootstrap.setOption("child.receiveBufferSize", recvBufSize);

      bootstrap.bind(address);
   }

   @Override
   public void stop() {
      if (channel != null) channel.close();
   }

   private static class WebSocketServerPipelineFactory implements ChannelPipelineFactory {

      private CacheContainer cacheContainer;
      private Map<String, OpHandler> operationHandlers;
      private Map<String, Cache> startedCaches = ConcurrentMapFactory.makeConcurrentMap();

      public WebSocketServerPipelineFactory(CacheContainer cacheContainer) {
         this.cacheContainer = cacheContainer;

         operationHandlers = new HashMap<String, OpHandler>();
         operationHandlers.put("put", new PutHandler());
         operationHandlers.put("get", new GetHandler());
         operationHandlers.put("remove", new RemoveHandler());
         NotifyHandler notifyHandler = new NotifyHandler();
         operationHandlers.put("notify", notifyHandler);
         operationHandlers.put("unnotify", notifyHandler);
      }

      @Override
      public ChannelPipeline getPipeline() throws Exception {
         // Create a default pipeline implementation.
         ChannelPipeline pipeline = Channels.pipeline();

         pipeline.addLast("decoder", new HttpRequestDecoder());
         pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
         pipeline.addLast("encoder", new HttpResponseEncoder());
         pipeline.addLast("handler", new WebSocketServerHandler(cacheContainer, operationHandlers, startedCaches));

         return pipeline;
      }
   }

   public static String getJavascript() {
      if (javascript != null) {
         return javascript;
      }

      BufferedReader scriptReader = new BufferedReader(new InputStreamReader(WebSocketServer.class.getResourceAsStream(INFINISPAN_WS_JS_FILENAME)));

      try {
         StringWriter writer = new StringWriter();

         String line = scriptReader.readLine();
         while (line != null) {
            writer.write(line);
            writer.write('\n');
            line = scriptReader.readLine();
         }

         javascript = writer.toString();

         return javascript;
      } catch (IOException e) {
         throw new IllegalStateException("Unexpected exception while sending Websockets script to client.", e);
      } finally {
         try {
            scriptReader.close();
         } catch (IOException e) {
            throw new IllegalStateException("Unexpected exception while closing Websockets script to client.", e);
         }
      }
   }
}
