package org.limewire.lws.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
import org.limewire.http.httpclient.SimpleLimeHttpClient;
import org.limewire.net.SocketsManager;
import org.limewire.service.ErrorService;

/**
 * This class defines instances of {@link LWSSenderOfMessagesToServer} that
 * simply communicate to a another server running on a given host and port. For
 * the purpose of testing it's used to talk to another server running on
 * <code>localhost</code>.
 */
public final class LocalServerDelegate {

    private final int port;
    private final SocketsManager socketsManager;
    private final String host;

    /**
     * @param host host name to which we communicate
     * @param port port to which we communicate
     * @param openner openner that will create a socket from <code>host</code>
     *        and <code>port</code>
     */
    public LocalServerDelegate(SocketsManager openner, String host, int port) {
        this.socketsManager = openner;
        this.host = host;
        this.port = port;
    }
    
    /**
     * This defines an interface to construct a URL based on a message and
     * arguments. This is used becaused when we're sending a URL to a server on
     * the client we encode the arguments as normal CGI parameters; when sending
     * to a mock remote server, we use a Wicket-style encoding. See
     * {@link #constructURL(String msg, Map<String, String> args)} for examples.
     */
    public interface URLConstructor {
        
        /**
         * Returns a URL based on the base message, <code>msg</code> and
         * arguments. Examples, for a message <code>"Foo"</code> and arguments
         * <code>{ "a" => "A", "b" => "B" }</code> would be:
         * 
         * <p>
         * 
         * <table>
         *  <tr>
         *   <th>Type</th>
         *   <th>URL</th>
         *  </tr>
         *  <tr>
         *   <td>Normal</td>
         *   <td><code>Foo?a=A&b=B</code></td>
         *  </tr>
         *  <tr>
         *   <td>Wicket</td>
         *   <td><code>store/app/pages/client/ClientCom/command/Foo/a/A/b/B</code></td>
         *  </tr>
         * </table>
         * 
         * @param msg base message
         * @param args arguments
         * @return a URL based on the base message, <code>msg</code> and
         *         arguments
         */
        String constructURL(String msg, Map<String, String> args);
    }
    
    /**
     * An implementation of {@link URLConstructor} using Wicket-style URLs. An
     * example, for a message <code>"Foo"</code> and arguments
     * <code>{ "a" => "A", "b" => "B" }</code> would be:
     * <code>store/app/pages/client/ClientCom/command/Foo/a/A/b/B</code>.
     */
    public static class WicketStyleURLConstructor implements URLConstructor {
        
        public final static WicketStyleURLConstructor INSTANCE = new WicketStyleURLConstructor();
        
        private WicketStyleURLConstructor() {}

        public String constructURL(String msg, Map<String, String> args) {
            StringBuffer sb = new StringBuffer("store/app/pages/client/ClientCom/command");
            sb.append("/").append(msg);
            for (String key : args.keySet()) {
                sb.append("/")
                  .append(key)
                  .append("/")
                  .append(args.get(key));
            }
            return sb.toString();
        } 
    }
    
    /**
     * An implementation of {@link URLConstructor} using normal CGI
     * parameter-encoding. An example, for a message <code>"Foo"</code> and
     * arguments <code>{ "a" => "A", "b" => "B" }</code> would be
     * <code>Foo?a=A&b=B</code>.
     */
    public static class NormalStyleURLConstructor implements URLConstructor {
        
        public final static NormalStyleURLConstructor INSTANCE = new NormalStyleURLConstructor();
        
        private NormalStyleURLConstructor() {}

        public String constructURL(String msg, Map<String, String> args) {
            StringBuffer sb = new StringBuffer(msg);
            boolean first = true;
            for (String key : args.keySet()) {
                sb.append(first ? "?" : "&")
                  .append(key)
                  .append("=")
                  .append(args.get(key));
                first = false;
            }
            return sb.toString();
        } 
    }

    public void sendMessageToServer(String msg, Map<String, String> args, StringCallback cb, URLConstructor ctor) {
        try {
            int tmpPort = port;
            for (; tmpPort < port + 10; tmpPort++) {
                Socket tmpSock;
                try {
                    tmpSock = socketsManager.connect(new InetSocketAddress(host, port), 5000);
                    tmpSock.close();
                    break;
                } catch (IOException e) { /* skip */ }
            }
            //
            // We'll always be adding this from the web page
            // to ensure a new script is loaded
            //
            Map<String, String> newArgs = new HashMap<String, String>(args);
            newArgs.put("_f", String.valueOf(System.currentTimeMillis()));
            String request = ctor.constructURL(msg, newArgs);

            HttpClient client = new SimpleLimeHttpClient();
            String requestLine = "http://" + host + ":" + port + "/" + request;
            HttpGet get = new HttpGet(requestLine);
            HttpProtocolParams.setVersion(client.getParams(), HttpVersion.HTTP_1_1);

            HttpResponse response = client.execute(get);

            String res = "";
            if (response.getEntity() != null) {
                String result;
                if (response.getEntity() != null) {
                    result = EntityUtils.toString(response.getEntity());
                } else {
                    result = null;
                }
                res = result;
            }
            cb.process(res);
        } catch (IOException e) {
            fail(e);
        } catch (HttpException e) {
            fail(e);
        } catch (URISyntaxException e) {
            fail(e);
        }
    }

    private void fail(Exception e) {
        ErrorService.error(e, "trying to connect on " + host + ":" + port);
    }

}
