/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.vysper.xmpp.extension.xep0124;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.vysper.xmpp.server.ServerRuntimeContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

/**
 * Receives BOSH requests from HTTP clients.
 *
 * @author The Apache MINA Project (dev@mina.apache.org)
 */
public class BoshServlet extends HttpServlet {

    public static final String TXT_CONTENT_TYPE = "text/plain";

    public static final String HTML_CONTENT_TYPE = "text/html; charset=UTF-8";

    public static final String XML_CONTENT_TYPE = "text/xml; charset=UTF-8";

    private static final long serialVersionUID = 1979722775762481476L;

    private static final String FLASH_CROSS_DOMAIN_POLICY_URI = "/crossdomain.xml";

    private static final String INFO_GET = "This is an XMPP BOSH connection manager, only POST is allowed";

    private static final String SERVER_IDENTIFICATION = "Vysper/0.5";

    private final Logger logger = LoggerFactory.getLogger(BoshServlet.class);

    private final BoshHandler boshHandler = new BoshHandler();

    private List<String> accessControlAllowOrigin;

    private String accessControlMaxAge = "86400"; // one day in seconds

    private String accessControlAllowMethods = "GET, POST, OPTIONS";

    /**
     * Setter for the {@link ServerRuntimeContext}
     * @param serverRuntimeContext
     */
    public void setServerRuntimeContext(ServerRuntimeContext serverRuntimeContext) {
        boshHandler.setServerRuntimeContext(serverRuntimeContext);
    }
    
    
    
    public List<String> getAccessControlAllowOrigin() {
        return accessControlAllowOrigin;
    }



    public void setAccessControlAllowOrigin(List<String> accessControlAllowOrigin) {
        this.accessControlAllowOrigin = accessControlAllowOrigin;
    }



    private byte[] createFlashCrossDomainPolicy() {
        StringBuffer crossDomain = new StringBuffer();
        crossDomain.append("<?xml version='1.0'?>"); 
        crossDomain.append("<!DOCTYPE cross-domain-policy SYSTEM 'http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd'>");
        crossDomain.append("<cross-domain-policy>");
            for(String domain : accessControlAllowOrigin) {
                crossDomain.append("<allow-access-from domain='");
                crossDomain.append(domain);
                crossDomain.append("' />");
            }
            crossDomain.append("</cross-domain-policy>"); 
        try {
            return crossDomain.toString().getBytes("UTF-8");
        } catch (UnsupportedEncodingException shouldNotHappen) {
            throw new RuntimeException(shouldNotHappen);
        }
    }

    private String createAccessControlAllowOrigin() {
        StringBuffer crossDomain = new StringBuffer();
        boolean first = true;
        for(String domain : accessControlAllowOrigin) {
            if(!first) {
                crossDomain.append(',');
            }
            crossDomain.append(domain);
            first = false;
        }
        return crossDomain.toString();
    }

    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.addDateHeader("Date", System.currentTimeMillis());
        resp.addHeader("Server", SERVER_IDENTIFICATION);
        if (FLASH_CROSS_DOMAIN_POLICY_URI.equals(req.getRequestURI())){
            if(accessControlAllowOrigin != null) {
                resp.setContentType(XML_CONTENT_TYPE);
                byte[] flashCrossDomainPolicy = createFlashCrossDomainPolicy();
                resp.setContentLength(flashCrossDomainPolicy.length);
                resp.getOutputStream().write(flashCrossDomainPolicy);
            } else {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        } else {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, INFO_GET);
        }
        resp.flushBuffer();
    }

    @Override
    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // used for preflighted requests
        resp.addDateHeader("Date", System.currentTimeMillis());
        resp.addHeader("Server", SERVER_IDENTIFICATION);
        resp.setContentType(TXT_CONTENT_TYPE);
        resp.setContentLength(0);
        if(accessControlAllowOrigin != null) {
            resp.addHeader("Access-Control-Allow-Origin", createAccessControlAllowOrigin());
            resp.addHeader("Access-Control-Allow-Methods", accessControlAllowMethods);
            resp.addHeader("Access-Control-Max-Age", accessControlMaxAge);
            resp.addHeader("Access-Control-Allow-Headers", "Content-Type, User-Agent, If-Modified-Since, Cache-Control");
        }
        resp.flushBuffer();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BoshResponse boshResponse = (BoshResponse) req.getAttribute("response");
        if (boshResponse != null) {
            // if the continuation is resumed or expired
            writeResponse(resp, boshResponse);
            return;
        }

        BoshDecoder decoder = new BoshDecoder(boshHandler, req);
        try {
            decoder.decode();
        } catch (SAXException e) {
            logger.error("Exception thrown while decoding XML", e);
        }
    }
    
    

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, INFO_GET);
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, INFO_GET);
    }

    private void writeResponse(HttpServletResponse resp, BoshResponse respData) throws IOException {
        resp.addDateHeader("Date", System.currentTimeMillis());
        resp.addHeader("Server", SERVER_IDENTIFICATION);
        resp.setContentType(respData.getContentType());
        resp.setContentLength(respData.getContent().length);
        if(accessControlAllowOrigin != null) {
            resp.addHeader("Access-Control-Allow-Origin", createAccessControlAllowOrigin());
        }
        resp.getOutputStream().write(respData.getContent());
        resp.flushBuffer();
    }

}
