package parser;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import logging.ForumGeLogger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import play.libs.Json;

import java.io.InputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.util.Scanner;

public class ForumGEParser
{
    private static String loginURL;
    private static String logoutURL;

    public ForumGEParser()
    {
        CookieManager cookieManager = new CookieManager();
        CookieHandler.setDefault(cookieManager);
        loginURL = "http://forum.ge/?act=Login&CODE=01&CookieDate=1";
        logoutURL = "http://forum.ge/?act=Login&CODE=03";
    }

    /**
     * @param username forum.ge username
     * @param password forum.ge password
     * @return Json object. If authorization is successful sessionId, username, message and code is returned, else only
     *                      message and code.
     */
    public ObjectNode login(String username, String password)
    {
        ObjectNode result = Json.newObject();
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.post(loginURL).field("UserName", username).field("PassWord", password).asBinary();
            result.put("message", httpResponse.getStatusText());
            result.put("code", httpResponse.getStatus());

            Scanner scanner = new Scanner(httpResponse.getBody());
            while (scanner.hasNext())
            {
                String temp = scanner.nextLine();
                if (temp.contains("you are now logged in"))
                {
                    result.put("username", username);
                    result.put("sessionId", httpResponse.getHeaders().getFirst("set-cookie"));
                    return result;
                }
            }
            return result;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            result.put("message", e.getMessage());
            result.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return result;
        }
    }

    /**
     * @param sessionID sessionId is returned in login function
     * @return Json object. If logout is successful message:logged out is returned else message:bad request
     */
    public ObjectNode logout(String sessionID)
    {
        ObjectNode result = Json.newObject();
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.post(logoutURL).field("Cookie", sessionID).field("rl", "Logout").asBinary();

            Scanner scan = new Scanner(httpResponse.getBody());
            while (scan.hasNext())
            {
                String temp = scan.nextLine();
                if (temp.contains("are now logged out"))
                {
                    result.put("message", "logged out");
                    result.put("code", httpResponse.getStatus());
                    return result;
                }
            }
            result.put("message", "bad request");
            result.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return result;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            result.put("message", e.getMessage());
            result.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return result;
        }
    }

    /**
     * @param text      post text
     * @param code      returned in getPosts metadata
     * @param f         returned in getPosts metadata
     * @param t         returned in getPosts metadata
     * @param authKey   returned in getPosts metadata
     * @param sessionId sessionId is returned in login function
     * @return Json object. if successfully posted message:ok is returned else message:BAD REQUEST
     */
    public ObjectNode addPost(String text, String code, String f, String t, String authKey, String sessionId)
    {
        ObjectNode root = Json.newObject();
        final String url = "http://forum.ge/?";
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.post(url).header("Cookie", sessionId).field("act", "Post").field("CODE", code).field("f", f)
                    .field("t", t).field("auth_key", authKey).field("Post", text).asBinary();

            Scanner scan = new Scanner(httpResponse.getBody());
            while (scan.hasNext())
            {
                String temp = scan.nextLine();
                if (temp.contains("Sorry"))
                {
                    root.put("message", "BAD REQUEST");
                    root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
                    return root;
                }
            }
            root.put("message", "OK");
            root.put("code", HttpURLConnection.HTTP_OK);
            return root;
        }

        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * @param userId    returned in getPosts function for each author
     * @param sessionId sessionId is returned in login function
     * @param text      post text
     * @param title     post title
     * @return Json object. if successfully sent message:ok is returned else message:BAD REQUEST
     */
    public ObjectNode sendPM(String userId, String sessionId, String text, String title)
    {
        final String url = "http://forum.ge/?";
        ObjectNode root = Json.newObject();
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.post(url).header("Cookie", sessionId).field("act", "Msg").field("CODE", "04").field("MODE", "01")
                    .field("Post", text).field("entered_name", userId).field("msg_title", title).asBinary();

            Scanner scan = new Scanner(httpResponse.getBody());
            while (scan.hasNext())
            {
                String temp = scan.nextLine();
                if (temp.contains("Thanks"))
                {
                    root.put("message", "OK");
                    root.put("code", HttpURLConnection.HTTP_OK);
                    return root;
                }
            }
            root.put("message", "BAD REQUEST");
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * @param sessionId sessionId is returned in login function
     * @return Json object. header and themes with inner themes are returned
     */
    public ObjectNode getMainContent(String sessionId)
    {
        String finalUrl = "http://forum.ge/?";
        ObjectNode root = Json.newObject();
        ArrayNode headerNodes = Json.newObject().arrayNode();
        root.put("root", headerNodes);
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.get(finalUrl).header("Cookie", sessionId).asBinary();
            Document document = Jsoup.parse(httpResponse.getBody(), Charset.defaultCharset().name(), "");
            Elements headerElements = document.select("div.maintitle > a");
            headerElements.forEach(s ->
            {
                ObjectNode headerNode = Json.newObject();
                ArrayNode themeNodes = Json.newObject().arrayNode();

                headerNode.put("header", s.text());
                headerNode.put("themes", themeNodes);
                headerNodes.add(headerNode);
                Element temp = s.parent().parent();
                Elements titles = temp.select("b > a");
                Elements descriptions = temp.select("span");
                for (Element e : titles)
                {
                    ObjectNode themeNode = Json.newObject();
                    themeNode.put("title", e.text());
                    themeNode.put("url", e.attr("href"));

                    Element dn = descriptions.get(titles.indexOf(e));
                    Elements as = dn.select("a");
                    if (as.size() > 0)
                    {
                        ArrayNode innerContent = Json.newObject().arrayNode();
                        for (Element k : as)
                        {
                            ObjectNode node = Json.newObject();
                            node.put("title", k.text());
                            node.put("url", k.attr("href"));
                            innerContent.add(node);
                        }
                        themeNode.put("innerContent", innerContent);
                    }
                    themeNodes.add(themeNode);
                }
            });
            return root;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * @param url       url of the Theme
     * @param sessionId sessionId is returned in login function
     * @return Json object. importantThemes, innerThemes, themes and pages are returned
     */
    public ObjectNode getContent(String url, String sessionId)
    {
        ObjectNode root = Json.newObject();
        ArrayNode innerThemes = Json.newObject().arrayNode();
        ArrayNode importantThemes = Json.newObject().arrayNode();
        ArrayNode themes = Json.newObject().arrayNode();
        ObjectNode pages = Json.newObject();

        root.put("innerThemes", innerThemes);
        root.put("importantThemes", importantThemes);
        root.put("themes", themes);
        root.put("pages", pages);

        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.get(url).header("Cookie", sessionId).asBinary();
            Document document = Jsoup.parse(httpResponse.getBody(), Charset.defaultCharset().name(), "");

            Elements innerElements = document.select("div.tableborder > table > tbody > tr > td.row4 > table > tbody > tr > td > b > a");
            innerElements.forEach(s ->
            {
                ObjectNode innerTheme = Json.newObject();
                innerTheme.put("title", s.text());
                innerTheme.put("url", s.attr("href"));
                innerTheme.put("description", s.parent().parent().select("span.desc").text());
                innerThemes.add(innerTheme);
            });

            Elements importantThemeElements = document.select("tbody > tr > td.row4 > b > a");
            importantThemeElements.forEach(s ->
            {
                ObjectNode importantTheme = Json.newObject();
                importantTheme.put("title", s.text());
                importantTheme.put("url", s.attr("href"));
                importantTheme.put("description", s.parent().parent().select("span.desc").text());
                Elements lastPageElement = s.parent().parent().select("span.small > a");
                if (lastPageElement.size() > 0)
                {
                    importantTheme.put("lastPage", lastPageElement.get(lastPageElement.size() - 1).attr("href"));
                }
                importantThemes.add(importantTheme);
            });

            Elements themeElements = document.select("form > div > table > tbody > tr > td.row4 > a[title]");
            themeElements.forEach(s ->
            {
                ObjectNode theme = Json.newObject();
                theme.put("title", s.text());
                theme.put("url", s.attr("href"));
                theme.put("description", s.parent().select("span.desc").text());
                Elements lastPageElement = s.parent().select("span.small > a");
                if (lastPageElement.size() > 0)
                {
                    theme.put("lastPage", lastPageElement.get(lastPageElement.size() - 1).attr("href"));
                }
                themes.add(theme);
            });

            Elements pageElements = document.select("div#ipbwrapper > table > tbody > tr > td[nowrap=nowrap] > a:not(title)");
            for (int i = 0; i < pageElements.size() / 2; i++)
            {
                Element temp = pageElements.get(i);
                if (!temp.text().startsWith("P"))
                {
                    pages.put(temp.text(), temp.attr("href"));
                }
            }
            return root;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * @param url       url of the Theme
     * @param sessionId sessionId is returned in login function
     * @return Json object. author, avatar, postDate, profileURL, userId, postData,
     * pages and metadata are returned.
     * in metadata there is some information for posting.
     */
    public ObjectNode getPosts(String url, String sessionId)
    {
        ObjectNode root = Json.newObject();
        ArrayNode posts = Json.newObject().arrayNode();
        ObjectNode pages = Json.newObject();
        ObjectNode metadata = Json.newObject();
        root.put("posts", posts);
        root.put("pages", pages);
        root.put("metadata", metadata);

        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.get(url).header("Cookie", sessionId).asBinary();
            Document document = Jsoup.parse(httpResponse.getBody(), Charset.defaultCharset().name(), "");
            Elements postElements = document.select("div.tableborder > form[name=MDEL] > table[cellpadding=3] > tbody");
            postElements.forEach(s ->
            {
                ObjectNode post = Json.newObject();
                post.put("author", s.select("tr > td[valign=top] > span.normalname > a").text());
                post.put("avatar", s.select("tr > td[valign=top] > span.postdetails > img[width]").attr("src"));
                post.put("postDate", s.select("tr > td[width=100%]").first().ownText().split("#")[0].trim());
                Elements profileAttrs = s.select("tr > td[width=100%] > a");
                if (profileAttrs.get(1).text().equals("ბარათი"))
                {
                    post.put("profileURL", profileAttrs.get(2).attr("href"));
                    String[] userID = profileAttrs.get(3).attr("href").split("=");
                    post.put("userId", userID[userID.length - 1]);
                } else
                {
                    post.put("profileURL", profileAttrs.get(1).attr("href"));
                    String[] userID = profileAttrs.get(2).attr("href").split("=");
                    post.put("userId", userID[userID.length - 1]);
                }

                ArrayNode jsonNodes = Json.newObject().arrayNode();
                Element postTagElements = s.select("tr > td[width=100%]").last();
                postTagElements.children().forEach(pte ->
                {
                    switch (pte.tagName())
                    {
                        case "div":
                            if (pte.hasClass("postcolor"))
                            {
                                pte.childNodes().forEach(tp -> parsePosts(tp, jsonNodes, false));
                            }
                            break;
                        case "table":
                            Element quotedElement = pte.select("tbody > tr > td#QUOTE").first();
                            quotedElement.childNodes().forEach(chn -> parsePosts(chn, jsonNodes, true));
                            break;
                        default:
                            break;
                    }
                });
                post.put("postData", jsonNodes);
                posts.add(post);
            });

            Elements pageElements = document.select("div#ipbwrapper > table > tbody > tr > td[nowrap=nowrap] > a:not(title)");
            for (int i = 0; i < pageElements.size() / 2; i++)
            {
                Element temp = pageElements.get(i);
                if (!temp.text().startsWith("P") && (!temp.text().startsWith("გ")))
                {
                    pages.put(temp.text(), temp.attr("href"));
                }
            }
            Elements postDataElements = document.select("body > table > tbody > tr > td > div#ipbwrapper > div.tableborder > form[name=REPLIER]");
            if (postDataElements != null)
            {
                metadata.put("code", postDataElements.select("input[name=CODE]").attr("value"));
                metadata.put("f", postDataElements.select("input[name=f]").attr("value"));
                metadata.put("t", postDataElements.select("input[name=t]").attr("value"));
                metadata.put("auth_key", postDataElements.select("input[name=auth_key]").attr("value"));
            }
            return root;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * @param sessionId sessionId is returned in login function
     * @return Json object. title, url, sender and date are returned
     */
    public ObjectNode getPMList(String sessionId)
    {
        final String url = "http://forum.ge/?act=Msg&CODE=01";
        ObjectNode root = Json.newObject();
        ArrayNode pms = Json.newObject().arrayNode();
        root.put("PMList", pms);
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.get(url).header("Cookie", sessionId).asBinary();
            Document document = Jsoup.parse(httpResponse.getBody(), Charset.defaultCharset().name(), "");

            Elements messageList = document.body().select("tr.dlight");
            messageList.forEach(ml ->
            {
                ObjectNode pm = Json.newObject();
                Elements attributes = ml.select("td");
                if (attributes.size() > 3)
                {
                    pm.put("title", attributes.get(1).text());
                    pm.put("url", attributes.get(1).select("a").first().attr("href"));
                    pm.put("sender", attributes.get(2).text().split("\\[")[0].trim());
                    pm.put("date", attributes.get(3).text());
                }
                pms.add(pm);
            });
            return root;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * @param url       url of the message
     * @param sessionId sessionId is returned in login function
     * @return Json object. message content
     */
    public ObjectNode getPM(String url, String sessionId)
    {
        ObjectNode root = Json.newObject();
        ArrayNode jsonNodes = Json.newObject().arrayNode();
        root.put("PM", jsonNodes);
        try
        {
            HttpResponse<InputStream> httpResponse = Unirest.get(url).header("Cookie", sessionId).asBinary();
            Document document = Jsoup.parse(httpResponse.getBody(), Charset.defaultCharset().name(), "");

            Element pmElement = document.body().select("span.postcolor").first();
            pmElement.childNodes().forEach(a -> parsePosts(a, jsonNodes, false));
            pmElement.children().forEach(chn ->
            {
                switch (chn.tagName())
                {
                    case "div":
                        chn.childNodes().forEach(tp -> parsePosts(tp, jsonNodes, false));
                        break;
                    case "table":
                        Element quotedElement = chn.select("tbody > tr > td#QUOTE").first();
                        quotedElement.childNodes().forEach(tp -> parsePosts(tp, jsonNodes, true));
                        break;
                    default:
                        break;
                }
            });
            return root;
        }
        catch (Exception e)
        {
            ForumGeLogger.log(e);
            root.put("message", e.getMessage());
            root.put("code", HttpURLConnection.HTTP_BAD_REQUEST);
            return root;
        }
    }

    /**
     * helper function
     *
     * @param node      current node
     * @param jsonNodes node where parsed info is added
     * @param isQuoted  is this post original or quoted
     */
    private void parsePosts(Node node, ArrayNode jsonNodes, boolean isQuoted)
    {
        switch (node.nodeName())
        {
            case "#text":
                String temp = ((TextNode) node).text().trim();
                if (temp.length() > 0)
                {
                    jsonNodes.add(Json.newObject().put(!isQuoted ? "text" : "quotedText", temp));
                }
                break;
            case "img":
                jsonNodes.add(Json.newObject().put(!isQuoted ? "img" : "quotedImg", node.attr("src")));
                break;
            case "a":
                if (node.childNodeSize() > 0 && node.childNode(0).nodeName().equals("img"))
                {
                    ObjectNode imglinkNode = Json.newObject().put("src", node.attr("href")).put("imgsrc", node.childNode(0).attr("src"));
                    ObjectNode tempNode = Json.newObject();
                    tempNode.put(!isQuoted ? "link" : "quotedLink", imglinkNode);
                    jsonNodes.add(tempNode);
                } else
                {
                    jsonNodes.add(Json.newObject().put(!isQuoted ? "link" : "quotedLink", node.attr("href")));
                }
                break;
            case "b":
                temp = node.toString().replace("<b>", "").replace("</b>", "").trim();
                if (temp.length() > 0)
                {
                    jsonNodes.add(Json.newObject().put("quotedMember", temp));
                }
                break;
            case "object":
                String embedSource = node.childNodes().stream().filter(embed -> embed.nodeName().equals("embed")).findFirst().get().attr("src");
                jsonNodes.add(Json.newObject().put(!isQuoted ? "embed" : "quotedEmbed", embedSource));
                break;

            case "div":
                if (node.attr("class").equals("spoilermain"))
                {
                    parseSpoiler(node, jsonNodes);
                }
                break;
            default:
                break;
        }
    }

    /**
     * helper function
     *
     * @param node      current node
     * @param jsonNodes node where parsed info is added
     */
    private void parseSpoiler(Node node, ArrayNode jsonNodes)
    {
        ObjectNode spoilerNode = Json.newObject();
        ArrayNode spoilerAray = Json.newObject().arrayNode();
        spoilerNode.put("spoiler", spoilerAray);
        jsonNodes.add(spoilerNode);
        node.childNodes().forEach(chn ->
        {
            switch (chn.nodeName())
            {
                case "#text":
                    String temp = ((TextNode) chn).text().trim();
                    if (temp.length() > 0)
                    {
                        spoilerAray.add(Json.newObject().put("spoilerText", temp));
                    }
                    break;
                case "img":
                    spoilerAray.add(Json.newObject().put("spoilerImg", chn.attr("src")));
                    break;
                case "a":
                    if (chn.childNodeSize() > 0 && chn.childNode(0).nodeName().equals("img"))
                    {
                        ObjectNode imglinkNode = Json.newObject().put("src", chn.attr("href")).put("imgsrc", chn.childNode(0).attr("src"));
                        ObjectNode tempNode = Json.newObject();
                        tempNode.put("spoilerLink", imglinkNode);
                        spoilerAray.add(tempNode);
                    } else
                    {
                        spoilerAray.add(Json.newObject().put("spoilerLink", chn.attr("href")));
                    }
                    break;
                case "b":
                    temp = chn.toString().replace("<b>", "").replace("</b>", "").trim();
                    if (temp.length() > 0)
                    {
                        spoilerAray.add(Json.newObject().put("spoilerMember", temp));
                    }
                    break;
                case "object":
                    String embedSource = chn.childNodes().stream().filter(embed -> embed.nodeName().equals("embed")).findFirst().get().attr("src");
                    spoilerAray.add(Json.newObject().put("spoilerEmbed", embedSource));
                    break;
                default:
                    break;
            }
        });
    }
}
