/*
 * Juick
 * Copyright (C) 2008-2013, Ugnich Anton
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.juick.api;

import com.juick.Tag;
import com.juick.json.MessageSerializer;
import com.juick.server.MessagesQueries;
import com.juick.server.SubscriptionsQueries;
import com.juick.server.TagQueries;
import com.juick.server.UserQueries;
import com.juick.xmpp.JID;
import com.juick.xmpp.Message;
import com.juick.xmpp.Stream;
import com.juick.xmpp.StreamComponent;
import com.juick.xmpp.extensions.JuickMessage;
import com.juick.xmpp.extensions.Nickname;
import com.juick.xmpp.extensions.XOOB;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.LogManager;

/**
 *
 * @author Ugnich Anton
 */
@WebServlet(name = "Main", urlPatterns = {"/"})
@MultipartConfig
public class Main extends HttpServlet implements Stream.StreamListener {

    JdbcTemplate jdbc;
    Stream xmpp;
    Messages messages;
    Users users;
    PM pm;
    Others others;
    TelegramBotHook tgb;
    SkypeEndpoint sep;

    String tmpDir, imgDir;

    @Override
    public void init() throws ServletException {
        super.init();
        try {
            LogManager.getLogManager().readConfiguration(getServletContext().getResourceAsStream("/WEB-INF/logging.properties"));
            Properties conf = new Properties();
            conf.load(getServletContext().getResourceAsStream("/WEB-INF/juick.conf"));
            BasicDataSource dataSource = new BasicDataSource();
            dataSource.setDriverClassName(conf.getProperty("datasource_driver", "com.mysql.jdbc.Driver"));
            dataSource.setUrl(conf.getProperty("datasource_url"));
            jdbc = new JdbcTemplate(dataSource);
            messages = new Messages(jdbc);
            users = new Users(jdbc);
            pm = new PM(jdbc);
            others = new Others(jdbc);
            tgb = new TelegramBotHook(jdbc, conf.getProperty("telegram_token", ""));
            sep = new SkypeEndpoint();
            setupXmppComponent(conf.getProperty("xmpp_host", "localhost"), Integer.parseInt(conf.getProperty("xmpp_port", "5347")),
                    conf.getProperty("xmpp_jid", "api.localhost"), conf.getProperty("xmpp_password"));
            tmpDir = conf.getProperty("upload_tmp_dir", "/var/www/juick.com/i/tmp/");
            imgDir = conf.getProperty("img_path", "/var/www/juick.com/i/");
        } catch (IOException e) {
            log("API initialization error", e);
        }
    }

    public void setupXmppComponent(final String host, final int port, final String jid, final String password) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(() -> {
            try {
                Socket socket = new Socket(host, port);
                xmpp = new StreamComponent(new JID(jid), socket.getInputStream(), socket.getOutputStream(), password);
                xmpp.addListener(Main.this);
                xmpp.startParsing();
            } catch (IOException e) {
                log("XMPP exception", e);
            }
        });
    }

    @Override
    public void onStreamFail(Exception e) {
        log("XMPP failed", e);
    }

    @Override
    public void onStreamReady() {
        log("XMPP STREAM READY");
    }

    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (request.getCharacterEncoding() == null) {
            request.setCharacterEncoding("UTF-8");
        }

        String uri = request.getRequestURI();

        int vuid = Utils.getHttpAuthUID(jdbc, request);
        if (vuid == 0) {
            vuid = Utils.getVisitorQueryStringUID(jdbc, request);
        }

        if (uri.equals("/home")) {
            if (vuid > 0) {
                messages.doGetHome(request, response, vuid);
            } else {
                response.sendError(401);
            }
        } else if (uri.equals("/messages")) {
            messages.doGet(request, response, vuid);
        } else if (uri.equals("/thread")) {
            messages.doThreadGet(request, response, vuid);
        } else if (uri.equals("/users")) {
            users.doGetUsers(request, response, vuid);
        } else if (uri.equals("/users/read")) {
            users.doGetUserRead(request, response, vuid);
        } else if (uri.equals("/users/readers")) {
            users.doGetUserReaders(request, response, vuid);
        } else if (uri.equals("/pm")) {
            if (vuid > 0) {
                pm.doGetPM(request, response, vuid);
            } else {
                response.sendError(401);
            }
        } else if (uri.equals("/groups_pms")) {
            if (vuid > 0) {
                others.doGetGroupsPMs(request, response, vuid);
            } else {
                response.sendError(401);
            }
        } else if (uri.equals("/messages/recommended")) {
            if (vuid > 0) {
                messages.doGetRecommended(request, response, vuid);
            } else {
                response.sendError(401);
            }
        } else if (uri.equals("/messages/set_popular") && vuid == 3694) {
            messages.doSetPopular(request, response, xmpp);
        } else if (uri.equals("/messages/set_privacy") && vuid > 0) {
            messages.doSetPrivacy(request, response, xmpp, vuid);
        } else {
            response.sendError(404);
        }
    }

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (request.getCharacterEncoding() == null) {
            request.setCharacterEncoding("UTF-8");
        }
        String uri = request.getRequestURI();
        if (uri.equals("/tlgmbtwbhk")) {
            try {
                tgb.doPost(request);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                log("telegram error", e);
            }
            return;
        }
        if (uri.equals("/skypebotendpoint")) {
            sep.doPost(request);
            return;
        }

        int vuid = Utils.getHttpAuthUID(jdbc, request);
        if (vuid == 0) {
            vuid = Utils.getVisitorQueryStringUID(jdbc, request);
        }
        if (vuid == 0) {
            response.sendError(401);
            return;
        }
        switch (uri) {
            case "/post":
                int mid = Utils.parseInt(request.getParameter("mid"), 0);
                if (mid == 0) {
                    doPostMessage(jdbc, request, response, xmpp, vuid);
                } else {
                    doPostComment(jdbc, request, response, xmpp, vuid);
                }
                break;
            case "/pm":
                pm.doPostPM(request, response, xmpp, vuid);
                break;
            default:
                response.sendError(405);
                break;
        }
    }

    public void doPostMessage(JdbcTemplate sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, int vuid)
            throws ServletException, IOException {
        String body = request.getParameter("body");
        if (body == null || body.length() < 1 || body.length() > 4096) {
            response.sendError(400);
            return;
        }
        body = body.replace("\r", "");

        String tagsStr = request.getParameter("tags");
        List<Tag> tags = new ArrayList<>();
        String tagsArr[] = new String[1];
        if (tagsStr != null && !tagsStr.isEmpty()) {
            tagsArr = tagsStr.split("[ \\,]");
            for (int i = 0; i < tagsArr.length; i++) {
                if (tagsArr[i].startsWith("*")) {
                    tagsArr[i] = tagsArr[i].substring(1);
                }
                if (tagsArr[i].length() > 64) {
                    tagsArr[i] = tagsArr[i].substring(0, 64);
                }
            }
            tags = TagQueries.getTags(sql, tagsArr, true);
            while (tags.size() > 5) {
                tags.remove(5);
            }
        }

        String attachmentFName = null;
        try {
            attachmentFName = Utils.receiveMultiPartFile(request, "attach");
        } catch (Exception e) {
            log("MULTIPART ERROR", e);
            response.sendError(400);
            return;
        }

        String paramImg = request.getParameter("img");
        if (attachmentFName == null && paramImg != null && paramImg.length() > 10 ) {
            try {
                URL imgUrl = new URL(paramImg);
                attachmentFName = Utils.downloadImage(imgUrl);
            } catch (Exception e) {
                log("DOWNLOAD ERROR", e);
                response.sendError(500);
                return;
            }
        }

        String attachmentType = attachmentFName != null ? attachmentFName.substring(attachmentFName.length() - 3) : null;
        int mid = MessagesQueries.createMessage(sql, vuid, body, attachmentType, tags);
        SubscriptionsQueries.subscribeMessage(sql, mid, vuid);
        JuickMessage jmsg = new JuickMessage(MessagesQueries.getMessage(sql, mid));
        if (xmpp != null) {
            Message xmsg = new Message();
            xmsg.from = new JID("juick", "juick.com", null);
            xmsg.type = Message.Type.chat;
            xmsg.thread = "juick-" + mid;

            xmsg.addChild(jmsg);

            Nickname nick = new Nickname();
            nick.Nickname = "@" + jmsg.getUser().getUName();
            xmsg.addChild(nick);

            if (attachmentFName != null) {
                String fname = mid + "." + attachmentType;
                String attachmentURL = "http://i.juick.com/photos-1024/" + fname;

                Path origName = Paths.get(imgDir, "p", fname);
                Files.move(Paths.get(tmpDir, attachmentFName), origName);
                Thumbnails.of(origName.toFile()).size(1024, 1024).outputQuality(0.9)
                        .toFile(Paths.get(imgDir, "photos-1024", fname).toFile());
                Thumbnails.of(origName.toFile()).size(512, 512).outputQuality(0.9)
                        .toFile(Paths.get(imgDir, "photos-512", fname).toFile());
                Thumbnails.of(origName.toFile()).size(160, 120).outputQuality(0.9)
                        .toFile(Paths.get(imgDir, "ps", fname).toFile());

                body = attachmentURL + "\n" + body;
                XOOB xoob = new XOOB();
                xoob.URL = attachmentURL;
                xmsg.addChild(xoob);
            }

            String tagsStr2 = "";
            for (String tag : tagsArr) {
                tagsStr2 += " *" + tag;
            }
            xmsg.body = "@" + jmsg.getUser().getUName() + ":" + tagsStr2 + "\n" + body + "\n\n#" + mid + " http://juick.com/" + mid;

            xmsg.to = new JID("juick", "s2s.juick.com", null);
            xmpp.send(xmsg);

            xmsg.to.Host = "ws.juick.com";
            xmpp.send(xmsg);

            xmsg.to.Host = "push.juick.com";
            xmpp.send(xmsg);

            xmsg.to.Host = "crosspost.juick.com";
            xmsg.to.Username = "twitter";
            xmpp.send(xmsg);
            xmsg.to.Username = "fb";
            xmpp.send(xmsg);

            xmsg.to.Host = "nologin.ru";
            xmsg.to.Username = "jubo";
            xmpp.send(xmsg);
        } else {
            log("XMPP unavailable");
        }
        MessageSerializer serializer = new MessageSerializer();
        Main.replyJSON(request, response, serializer.serialize(jmsg).toString());
    }

    public void doPostComment(JdbcTemplate sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, int vuid)
            throws ServletException, IOException {
        int mid = Utils.parseInt(request.getParameter("mid"), 0);
        if (mid == 0) {
            response.sendError(400);
            return;
        }
        com.juick.Message msg = MessagesQueries.getMessage(sql, mid);
        if (msg == null) {
            response.sendError(404);
            return;
        }

        int rid = Utils.parseInt(request.getParameter("rid"), 0);
        com.juick.Message reply = null;
        if (rid > 0) {
            reply = MessagesQueries.getReply(sql, mid, rid);
            if (reply == null) {
                response.sendError(404);
                return;
            }
        }

        String body = request.getParameter("body");
        if (body == null || body.length() < 1 || body.length() > 4096) {
            response.sendError(400);
            return;
        }
        body = body.replace("\r", "");

        if ((msg.ReadOnly && msg.getUser().getUID() != vuid) || UserQueries.isInBLAny(sql, msg.getUser().getUID(), vuid)
                || (reply != null && UserQueries.isInBLAny(sql, reply.getUser().getUID(), vuid))) {
            response.sendError(403);
            return;
        }

        String attachmentFName = null;
        try {
            attachmentFName = Utils.receiveMultiPartFile(request, "attach");
        } catch (Exception e) {
            log("MULTIPART ERROR", e);
            response.sendError(400);
            return;
        }

        String paramImg = request.getParameter("img");
        if (attachmentFName == null && paramImg != null && paramImg.length() > 10) {
            try {
                attachmentFName = Utils.downloadImage(new URL(paramImg));
            } catch (Exception e) {
                log("DOWNLOAD ERROR", e);
                response.sendError(500);
                return;
            }
        }

        String attachmentType = attachmentFName != null ? attachmentFName.substring(attachmentFName.length() - 3) : null;
        int ridnew = MessagesQueries.createReply(sql, mid, rid, vuid, body, attachmentType);
        SubscriptionsQueries.subscribeMessage(sql, mid, vuid);

        JuickMessage jmsg = new JuickMessage(MessagesQueries.getReply(sql, mid, ridnew));

        if (xmpp != null) {
            Message xmsg = new Message();
            xmsg.from = new JID("juick", "juick.com", null);
            xmsg.type = Message.Type.chat;
            xmsg.thread = "juick-" + mid;
            xmsg.addChild(jmsg);

            String quote = reply != null ? reply.getText() : msg.getText();
            if (quote.length() >= 50) {
                quote = quote.substring(0, 47) + "...";
            }

            Nickname nick = new Nickname();
            nick.Nickname = "@" + jmsg.getUser().getUName();
            xmsg.addChild(nick);

            if (attachmentFName != null) {
                String fname = mid + "-" + ridnew + "." + attachmentType;
                String attachmentURL = "http://i.juick.com/photos-1024/" + fname;

                Path origName = Paths.get(imgDir, "p", fname);
                Files.move(Paths.get(tmpDir, attachmentFName), origName);
                Thumbnails.of(origName.toFile()).size(1024, 1024).outputQuality(0.9)
                        .toFile(Paths.get(imgDir, "photos-1024", fname).toFile());
                Thumbnails.of(origName.toFile()).size(512, 512).outputQuality(0.9)
                        .toFile(Paths.get(imgDir, "photos-512", fname).toFile());
                Thumbnails.of(origName.toFile()).size(160, 120).outputQuality(0.9)
                        .toFile(Paths.get(imgDir, "ps", fname).toFile());

                body = attachmentURL + "\n" + body;
                XOOB xoob = new XOOB();
                xoob.URL = attachmentURL;
                xmsg.addChild(xoob);
            }

            xmsg.body = "Reply by @" + jmsg.getUser().getUName() + ":\n>" + quote + "\n" + body + "\n\n#" + mid + "/" + ridnew + " http://juick.com/" + mid + "#" + ridnew;

            xmsg.to = new JID("juick", "s2s.juick.com", null);
            xmpp.send(xmsg);

            xmsg.to.Host = "ws.juick.com";
            xmpp.send(xmsg);

            xmsg.to.Host = "push.juick.com";
            xmpp.send(xmsg);
        } else {
            log("XMPP unavailable");
        }
        MessageSerializer serializer = new MessageSerializer();
        Main.replyJSON(request, response, serializer.serialize(jmsg).toString());
    }

    public static void replyJSON(HttpServletRequest request, HttpServletResponse response, String json) throws IOException {
        response.setContentType("application/json; charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin", "*");

        String callback = request.getParameter("callback");
        if (callback != null && (callback.length() > 64 || !callback.matches("[a-zA-Z0-9\\-\\_]+"))) {
            callback = null;
        }

        try (PrintWriter out = response.getWriter()) {
            if (callback != null) {
                out.print(callback + "(");
                out.print(json);
                out.print(")");
            } else {
                out.print(json);
            }
        }
    }
}