package org.dynmap.servlet;

import static org.dynmap.JSONUtils.s;

import org.dynmap.DynmapCore;
import org.dynmap.Event;
import org.dynmap.Log;
import org.dynmap.web.HttpField;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

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

import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import org.dynmap.utils.IpAddressMatcher;;

@SuppressWarnings("serial")
public class SendMessageServlet extends HttpServlet {
    protected static final Logger log = Logger.getLogger("Minecraft");

    private static final JSONParser parser = new JSONParser();
    public Event<Message> onMessageReceived = new Event<Message>();
    private Charset cs_utf8 = Charset.forName("UTF-8");
    public int maximumMessageInterval = 1000;
    public boolean hideip = false;
    public boolean trustclientname = false;
    
    public String spamMessage = "\"You may only chat once every %interval% seconds.\"";
    private HashMap<String, WebUser> disallowedUsers = new HashMap<String, WebUser>();
    private LinkedList<WebUser> disallowedUserQueue = new LinkedList<WebUser>();
    private Object disallowedUsersLock = new Object();
    private HashMap<String,String> useralias = new HashMap<String,String>();
    private int aliasindex = 1;
    public boolean use_player_login_ip = false;
    public boolean require_player_login_ip = false;
    public boolean check_user_ban = false;
    public boolean require_login = false;
    public boolean chat_perms = false;
    public int lengthlimit = 256;
    public DynmapCore core;
    public ArrayList<IpAddressMatcher> proxyaddress = new ArrayList<IpAddressMatcher>();

    private boolean trustedProxy(String ip) {
    	for (IpAddressMatcher m : proxyaddress) {
    		if (m.matches(ip)) {
    			return true;
    		}
    	}
    	return false;
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        byte[] bytes;
        String error = "none";
        HttpSession sess = request.getSession(true);
        String userID = (String) sess.getAttribute(LoginServlet.USERID_ATTRIB);
        if(userID == null) userID = LoginServlet.USERID_GUEST;
        boolean chat_requires_login = core.getLoginRequired() || require_login;
        if(chat_requires_login && userID.equals(LoginServlet.USERID_GUEST)) {
            error = "login-required";
        }
        else if(chat_requires_login && (!userID.equals(LoginServlet.USERID_GUEST)) && chat_perms && 
                (!core.checkPermission(userID, "webchat"))) {
            Log.info("Rejected web chat by " + userID + ": not permitted");
            error = "not-permitted";
        }
        else {
            boolean ok = true;
            
            InputStreamReader reader = new InputStreamReader(request.getInputStream(), cs_utf8);

            JSONObject o = null;
            try {
                o = (JSONObject)parser.parse(reader);
            } catch (ParseException e) {
                error = "bad-format";
                ok = false;
            }

            final Message message = new Message();

            if (userID.equals(LoginServlet.USERID_GUEST)) {
                message.name = "";
                if (trustclientname) {
                    message.name = String.valueOf(o.get("name"));
                }
                boolean isip = true;
                if ((message.name == null) || message.name.equals("")) {
                    /* If from trusted proxy, check for client */
                    String rmtaddr = request.getRemoteAddr(); 
                    if (this.trustedProxy(rmtaddr)) {	// If remote address is valid trusted proxy
                        /* If proxied client address, get original IP */
                        if (request.getHeader("X-Forwarded-For") != null) {
                        	// Split list, since addresses after first are proxy chain
                            String[] proxyAddrs = request.getHeader("X-Forwarded-For").split(",");
                            // We only want first - any others are proxies that our local proxy was willing to pass to us
                            message.name = proxyAddrs[0].trim();
                        } else {
                            message.name = String.valueOf(o.get("name"));
                        }
                    }
                    else
                        message.name = request.getRemoteAddr();
                }
                if (use_player_login_ip) {
                    List<String> ids = core.getIDsForIP(message.name);
                    if (ids != null) {
                        String id = ids.get(0);
                        if (check_user_ban) {
                            if (core.getServer().isPlayerBanned(id)) {
                                Log.info("Ignore message from '" + message.name + "' - banned player (" + id + ")");
                                error = "not-allowed";
                                ok = false;
                            }
                        }
                        if (chat_perms && !core.getServer().checkPlayerPermission(id, "webchat")) {
                            Log.info("Rejected web chat from '" + message.name + "': not permitted (" + id + ")");
                            error = "not-allowed";
                            ok = false;
                        }
                        message.name = id;
                        isip = false;
                    } else if (require_player_login_ip) {
                        Log.info("Ignore message from '" + message.name + "' - no matching player login recorded");
                        error = "not-allowed";
                        ok = false;
                    }
                }
                if (hideip && isip) { /* If hiding IP, find or assign alias */
                    synchronized (disallowedUsersLock) {
                        String n = useralias.get(message.name);
                        if (n == null) { /* Make ID */
                            n = String.format("web-%03d", aliasindex);
                            aliasindex++;
                            useralias.put(message.name, n);
                        }
                        message.name = n;
                    }
                }
            }
            else {
                message.name = userID;
            }
            message.message = String.valueOf(o.get("message"));
            if((lengthlimit > 0) && (message.message.length() > lengthlimit)) {
                message.message = message.message.substring(0, lengthlimit);
            }

            final long now = System.currentTimeMillis();

            synchronized (disallowedUsersLock) {
                // Allow users that  user that are now allowed to send messages.
                while (!disallowedUserQueue.isEmpty()) {
                    WebUser wu = disallowedUserQueue.getFirst();
                    if (now >= wu.nextMessageTime) {
                        disallowedUserQueue.remove();
                        disallowedUsers.remove(wu.name);
                    } else {
                        break;
                    }
                }

                WebUser user = disallowedUsers.get(message.name);
                if (user == null) {
                    user = new WebUser() {
                        {
                            name = message.name;
                            nextMessageTime = now + maximumMessageInterval;
                        }
                    };
                    disallowedUsers.put(user.name, user);
                    disallowedUserQueue.add(user);
                } else {
                    error = "not-allowed";
                    ok = false;
                }
            }
            if(ok)
                onMessageReceived.trigger(message);
        }
        JSONObject json = new JSONObject();
        s(json, "error", error);
        bytes = json.toJSONString().getBytes(cs_utf8);
        
        String dateStr = new Date().toString();
        response.addHeader(HttpField.Date, dateStr);
        response.addHeader(HttpField.ContentType, "text/plain; charset=utf-8");
        response.addHeader(HttpField.Expires, "Thu, 01 Dec 1994 16:00:00 GMT");
        response.addHeader(HttpField.LastModified, dateStr);
        response.addHeader(HttpField.ContentLength, Integer.toString(bytes.length));
        response.getOutputStream().write(bytes);
    }

    public static class Message {
        public String name;
        public String message;
    }
    public static class WebUser {
        public long nextMessageTime;
        public String name;
    }
}
