/*
 * 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.tomcat.bayeux;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.Date;
import java.text.SimpleDateFormat;
import javax.servlet.ServletException;

import org.apache.catalina.comet.CometEvent;
import org.apache.tomcat.bayeux.HttpError;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.apache.cometd.bayeux.Bayeux;
import org.apache.cometd.bayeux.Message;

/**
 * Common functionality and member variables for all Bayeux requests.
 *
 * @author Guy A. Molinari
 * @version 0.9
 *
 */
public abstract class RequestBase implements BayeuxRequest {

    protected static final SimpleDateFormat timestampFmt =
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    static {
        timestampFmt.setTimeZone(TimeZone.getTimeZone("GMT"));
    }
    //message properties, combined for all messages
    protected TomcatBayeux tomcatBayeux;
    protected String channel;
    protected String id;
    protected String clientId;
    protected String version = null;
    protected String[] suppConnTypes = null;
    protected int suppConnTypesFlag = 0;
    protected int desiredConnTypeFlag = 0;
    protected String minVersion = null;
    protected String subscription = null;
    protected String data = null;
    protected String conType = null;
    protected LinkedHashMap<String, Object> ext = new LinkedHashMap<String, Object> ();


    protected CometEvent event;

    protected HashMap<String, Object> response = null;

    private static final Log log = LogFactory.getLog(RequestBase.class);

    protected int reconnectInterval = 1000;

    protected RequestBase(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
        this.tomcatBayeux = tb;
        this.event = event;
        channel = jsReq.optString(Bayeux.CHANNEL_FIELD);
        id = jsReq.optString(Bayeux.ID_FIELD);
        clientId = jsReq.optString(Bayeux.CLIENT_FIELD);
        version = jsReq.optString(Bayeux.VERSION_FIELD);
        minVersion = jsReq.optString(Bayeux.MIN_VERSION_FIELD);
        conType = jsReq.optString(Bayeux.CONNECTION_TYPE_FIELD);
        subscription = jsReq.optString(Bayeux.SUBSCRIPTION_FIELD);
        data = jsReq.optString(Bayeux.DATA_FIELD);
        reconnectInterval = tb.getReconnectInterval();
        if (jsReq.has(Bayeux.EXT_FIELD)) {
            JSONObject jext = jsReq.getJSONObject(Bayeux.EXT_FIELD);
            for (Iterator<String> i = jext.keys(); i.hasNext(); ) {
                String key = i.next();
                ext.put(key, jext.get(key));
            }//for
        }//end if

        if (jsReq.has(Bayeux.SUPP_CONNECTION_TYPE_FIELD)) {
            JSONArray types = jsReq.getJSONArray(Bayeux.SUPP_CONNECTION_TYPE_FIELD);
            suppConnTypes = new String[types.length()];
            for (int i = 0; i < types.length(); i++) {
                suppConnTypes[i] = types.getString(i);
                if (Bayeux.TRANSPORT_CALLBACK_POLL.equals(suppConnTypes[i]))
                    suppConnTypesFlag = suppConnTypesFlag|ClientImpl.SUPPORT_CALLBACK_POLL;
                else if (Bayeux.TRANSPORT_LONG_POLL.equals(suppConnTypes[i]))
                    suppConnTypesFlag = suppConnTypesFlag|ClientImpl.SUPPORT_LONG_POLL;
            }//for
        }//end if

        if (conType!=null) {
            if (Bayeux.TRANSPORT_CALLBACK_POLL.equals(conType))
                desiredConnTypeFlag = ClientImpl.SUPPORT_CALLBACK_POLL;
            else if (Bayeux.TRANSPORT_LONG_POLL.equals(conType))
                desiredConnTypeFlag = ClientImpl.SUPPORT_LONG_POLL;
        }//end if

        //due to the fact that the javascript doesn't send up a required field
        //we have to fake it
        suppConnTypesFlag = ClientImpl.SUPPORT_CALLBACK_POLL | ClientImpl.SUPPORT_LONG_POLL;

    }

    public HttpError validate() {
        HttpError result = null;
//        if (clientId == null) {
//            result = new HttpError(401,"No Client ID.", null);
//        }
        return result;
    }

    public TomcatBayeux getTomcatBayeux() {
        return tomcatBayeux;
    }

    public String getChannel() {
        return channel;
    }

    public String getId() {
        return id;
    }

    public String getClientId() {
        return clientId;
    }

    public LinkedHashMap getExt() {
        return ext;
    }

    public CometEvent getEvent() {
        return event;
    }

    protected static void deliver(CometEvent event, ClientImpl to) throws IOException, ServletException, BayeuxException {
        JSONArray jarray = getJSONArray(event,true);
        if ( jarray == null ) throw new BayeuxException("No message to send!");
        String jsonstring = jarray.toString();
        if (log.isDebugEnabled()) {
            log.debug("["+Thread.currentThread().getName()+"] Delivering message to[" + to + "] message:" + jsonstring);
        }

        if (to!=null) {
            if (to.useJsonFiltered()) {
                if (!event.getHttpServletResponse().isCommitted()) event.getHttpServletResponse().setContentType("text/json-comment-filtered");
            }else {	
                if (!event.getHttpServletResponse().isCommitted()) event.getHttpServletResponse().setContentType("text/json");
            }
        }

        PrintWriter out = event.getHttpServletResponse().getWriter();
        if (to==null) {
            //do nothing
        }else if ( (to.getDesirectConnType() == 0 && to.supportsLongPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_LONG_POLL) {
            if (to.useJsonFiltered())
                out.print("/*");
        } else if ( (to.getDesirectConnType() == 0 && to.supportsCallbackPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_CALLBACK_POLL) {
            String jsonp = event.getHttpServletRequest().getParameter(Bayeux.JSONP_PARAMETER);
            if (jsonp == null)
                jsonp = Bayeux.JSONP_DEFAULT_NAME;
            out.print(jsonp);
            out.print('(');
        } else {
            throw new BayeuxException("Client doesn't support any appropriate connection type.");
        }
        out.print(jsonstring);
        if ( to == null ) {
            //do nothing
        } else if ( (to.getDesirectConnType() == 0 && to.supportsLongPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_LONG_POLL) {
            if (to.useJsonFiltered())
                out.print("*/");
        } else if ( (to.getDesirectConnType() == 0 && to.supportsCallbackPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_CALLBACK_POLL) {
            out.print(");");
        }
        out.flush();
        event.getHttpServletResponse().flushBuffer();


    }

    protected static JSONArray getJSONArray(CometEvent event, boolean nullok) {
        synchronized(event) {
            JSONArray jarray = (JSONArray) event.getHttpServletRequest().getAttribute(JSON_MSG_ARRAY);
            if (jarray == null && (!nullok)) {
                jarray = new JSONArray();
                event.getHttpServletRequest().setAttribute(JSON_MSG_ARRAY, jarray);
            }
            return jarray;
        }
    }

    protected JSONArray getJSONArray() {
        return getJSONArray(event,false);
    }

    protected void addToDeliveryQueue(ClientImpl to, JSONObject msg) throws IOException, ServletException, BayeuxException {
        synchronized (event) {
            getJSONArray().put(msg);
        }
    }

    protected void flushMessages(ClientImpl client) throws BayeuxException {
        List<Message> msgs = client.takeMessages();
        synchronized (event) {
            try {
                for (Iterator<Message> it = msgs.iterator(); it.hasNext(); ){
                    MessageImpl msg = (MessageImpl)it.next();
                    Map map = new HashMap();
                    map.put(Bayeux.CHANNEL_FIELD,msg.getChannel().getId());
                    if (msg.getClient()!=null) map.put(Bayeux.CLIENT_FIELD,msg.getClient().getId());
                    map.put(Bayeux.DATA_FIELD,msg);
                    JSONObject obj = new JSONObject(map);
                    addToDeliveryQueue(client, obj);
                }
            } catch (ServletException x) {
                throw new BayeuxException(x);
            } catch (IOException x) {
                throw new BayeuxException(x);
            }
        }
    }

    public int process(int prevops) throws BayeuxException {
        event.getHttpServletRequest().setAttribute(CURRENT_REQ_ATTR,this);
        return prevops;
    }

    public int getReconnectInterval() {
        return reconnectInterval;
    }

    public String getTimeStamp() {
        return timestampFmt.format(new Date(System.currentTimeMillis()));
    }

}
