/*
 * Copyright 2004-2014 ICEsoft Technologies Canada Corp.
 *
 * 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.icepush;

import java.io.IOException;
import java.io.Writer;
import java.util.logging.Logger;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.icepush.http.Request;
import org.icepush.http.Server;
import org.icepush.http.standard.FixedXMLContentHandler;
import org.icepush.util.Slot;

public class ConfigurationServer implements Server {
    private static final Logger log = Logger.getLogger(ConfigurationServer.class.getName());
    private static final String defaultServerErrorRetries = "1000 2000 4000";
    private static final String defaultNetworkErrorRetries = "1 1 1 2 2 3";
    private static final int defaultEmptyResponseRetries = 3;
    public static final int DefaultHeartbeatTimeout = 15000;
    private static final String BrowserIDCookieName = "ice.push.browser";
    private final Slot heartbeatInterval;
    private final String serverErrorRetries;
    private final String networkErrorRetries;
    private final int emptyResponseRetries;
    private final String contextPath;

    private Configuration configuration;
    private Server server;

    private FixedXMLContentHandler setBrowserID;
    private boolean redirect;

    public ConfigurationServer(final Slot heartbeatInterval, final PushContext context, final ServletContext servletContext, final Configuration configuration, final Server server) {
        this.heartbeatInterval = heartbeatInterval;
        this.server = server;
        this.configuration = configuration;
        contextPath = normalizeContextPath(this.configuration.getAttribute("contextPath", (String) servletContext.getAttribute("contextPath")));
        //PUSH-218: temporarily disabling modification of the context parameter
        serverErrorRetries = this.configuration.getAttribute("serverErrorRetryTimeouts", defaultServerErrorRetries);
        networkErrorRetries = this.configuration.getAttribute("networkErrorRetryTimeouts", defaultNetworkErrorRetries);
        emptyResponseRetries = this.configuration.getAttributeAsInteger("emptyResponseRetries", defaultEmptyResponseRetries);

        //always redirect if the request comes to this context path
        redirect = contextPath != null && !servletContext.getContextPath().equals(contextPath);
        setBrowserID = new SetBrowserID(context);
    }

    private static String normalizeContextPath(String path) {
        if (path == null) {
            return null;
        } else {
            return path.startsWith("/") ? path : "/" + path;
        }
    }

    public void service(Request request) throws Exception {
        String browserIDParameter;
        try {
            browserIDParameter = request.getParameter(BrowserIDCookieName);
        } catch (RuntimeException e) {
            browserIDParameter = "";
        }
        boolean browserIDNotSet = browserIDParameter == null || "".equals(browserIDParameter);
        if (redirect || request.containsParameter("ice.sendConfiguration") || browserIDNotSet) {
            String configurationMessage = "<configuration" +
                    (heartbeatInterval.getLongValue() != DefaultHeartbeatTimeout ?
                            " heartbeatTimeout=\"" + heartbeatInterval.getLongValue() + "\"" : "") +
                    (emptyResponseRetries != defaultEmptyResponseRetries ?
                            " emptyResponseRetries=\"" + emptyResponseRetries + "\"" : "") +
                    (!serverErrorRetries.equals(defaultServerErrorRetries) ?
                            " serverErrorRetryTimeouts=\"" + serverErrorRetries + "\"" : "") +
                    (!networkErrorRetries.equals(defaultNetworkErrorRetries) ?
                            " networkErrorRetryTimeouts=\"" + networkErrorRetries + "\"" : "") +
                    (contextPath != null ?
                            " blockingConnectionURI=\"" + contextPath + "/listen.icepush\"" : "") +
                    (contextPath != null ?
                            " contextPath=\"" + contextPath + "\"" : "") +
                    "/>";
            boolean nonDefaultConfiguration = configurationMessage.length() != "<configuration/>".length();

            if (nonDefaultConfiguration && browserIDNotSet) {
                request.respondWith(new SetBrowserIDAndConfigureBridgeMacro(new ConfigureBridge(configurationMessage)));
            } else if (nonDefaultConfiguration) {
                request.respondWith(new ConfigureBridge(configurationMessage));
            } else if (browserIDNotSet) {
                request.respondWith(setBrowserID);
            } else {
                server.service(request);
            }
        } else {
            server.service(request);
        }
    }

    public void shutdown() {
        server.shutdown();
    }

    private static class ConfigureBridge extends FixedXMLContentHandler {
        private String configurationMessage;

        private ConfigureBridge(String configurationMessage) {
            this.configurationMessage = configurationMessage;
        }

        public void writeTo(Writer writer) throws IOException {
            writer.write(configurationMessage);
            log.fine("Re-configured bridge.");
        }
    }

    private static class SetBrowserID extends FixedXMLContentHandler {
        private PushContext context;

        public SetBrowserID(PushContext context) {
            this.context = context;
        }

        public void writeTo(Writer writer) throws IOException {
            writer.write("<browser id=\"" + context.generateBrowserID() + "\"/>");
            log.fine("BrowserID set through blocking connection.");
        }
    }

    private class SetBrowserIDAndConfigureBridgeMacro extends FixedXMLContentHandler {
        private FixedXMLContentHandler configureBridge;

        public SetBrowserIDAndConfigureBridgeMacro(FixedXMLContentHandler configureBridge) {
            this.configureBridge = configureBridge;
        }

        public void writeTo(Writer writer) throws IOException {
            writer.write("<macro>");
            configureBridge.writeTo(writer);
            setBrowserID.writeTo(writer);
            writer.write("</macro>");
        }
    }
}
