/*
 * Robot Testing Framework
 *
 * Copyright (C) 2015-2019 Istituto Italiano di Tecnologia (IIT)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include <robottestingframework/Asserter.h>
#include <robottestingframework/WebProgressListener.h>
#include <robottestingframework/impl/WebProgressListener_impl.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>

using namespace std;
using namespace robottestingframework;


#define BLUE "<font color=\"blue\" style=\"font-weight: bold;\">"
#define GREEN "<font color=\"green\" style=\"font-weight: bold;\">"
#define RED "<font color=\"red\" style=\"font-weight: bold;\">"
#define GRAY "<font color=\"gray\" style=\"font-weight: bold;\">"
#define ENDC "</font>"

#define MSG_ERROR RED "[ERROR]&nbsp;" ENDC
#define MSG_FAIL RED "[FAIL]&nbsp;&nbsp;" ENDC
#define MSG_REPORT GREEN "[INFO]&nbsp;&nbsp;" ENDC

static std::string html_page = "<!DOCTYPE html>\n"
                               "<html>\n"
                               "<head>\n"
                               "<style>\n"
                               ".font_h4 {\n"
                               "font: normal normal normal 36px/1.3em Arial,'ms pgothic',dotum,helvetica,sans-serif;\n"
                               "color: #1C1C1C; font-weight: bold; }\n"
                               "\n"
                               ".font_1 {\n"
                               "font: normal normal bold 12px/1.3em Arial,'ms pgothic',dotum,helvetica,sans-serif;\n"
                               "color: #808080; }\n"
                               "\n"
                               ".my_window {\n"
                               "position:absolute;\n"
                               "left:20px; right:20px;\n"
                               "background: #efefef; background-color: #efefef;\n"
                               "border: solid 1px #0088CB;\n"
                               "-webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px;\n"
                               "font-family: 'Spinnaker' !important; list-style: none;\n"
                               "margin: 0 0 10px; padding: 5px; position: absolute;\n"
                               "box-shadow: 0 5px 15px 0 rgba(0,0,0,.4); }\n"
                               "</style>\n"
                               "\n"
                               "<script>\n"
                               "function update() {\n"
                               "    var xmlUpdate;\n"
                               "    if (window.XMLHttpRequest) {\n"
                               "      xmlUpdate=new XMLHttpRequest();\n"
                               "    }\n"
                               "    else {\n"
                               "        xmlUpdate=new ActiveXObject(\"Microsoft.XMLHTTP\");\n"
                               "    }\n"
                               "    xmlUpdate.onreadystatechange=function() {\n"
                               "        if (xmlUpdate.readyState==4 && xmlUpdate.status==200) {\n"
                               "            document.getElementById(\"result\").innerHTML=xmlUpdate.responseText;\n"
                               "             if(document.getElementById(\"autoscroll\").checked == true) {\n"
                               "               document.getElementById(\"result\").scrollTop = document.getElementById(\"result\").scrollHeight;\n"
                               "             }\n"
                               "        }\n"
                               "    }\n"
                               "    xmlUpdate.open(\"GET\",document.URL+\"update\",true);\n"
                               "    xmlUpdate.send();\n"
                               "\n"
                               "    var xmlStatus;\n"
                               "    if (window.XMLHttpRequest) {\n"
                               "        xmlStatus=new XMLHttpRequest();\n"
                               "    }\n"
                               "    else {\n"
                               "        xmlStatus=new ActiveXObject(\"Microsoft.XMLHTTP\");\n"
                               "    }\n"
                               "    xmlStatus.onreadystatechange=function() {\n"
                               "        if (xmlStatus.readyState==4 && xmlStatus.status==200) {\n"
                               "            var status = JSON.parse(xmlStatus.responseText);\n"
                               "            document.getElementById(\"suite_name\").innerHTML=status.name;\n"
                               "            var c = document.getElementById(\"progress\");\n"
                               "            var ctx = c.getContext(\"2d\");\n"
                               "            ctx.clearRect(0, 0, c.width, c.height);\n"
                               "            var xpos = 20;\n"
                               "            for (i = 0; i < status.testStatus.length; i++) {\n"
                               "                ctx.beginPath();\n"
                               "                ctx.arc(xpos, c.height/2, 7, 0, 2 * Math.PI);\n"
                               "                ctx.lineWidth = 2;\n"
                               "                switch (status.testStatus[i]) {\n"
                               "                case 0:\n"
                               "                    ctx.strokeStyle=\"#B0C0D0\";\n"
                               "                    ctx.fillStyle=\"#BBCCDD\";\n"
                               "                    break;\n"
                               "                case 1:\n"
                               "                    ctx.strokeStyle=\"#708090\";\n"
                               "                    ctx.fillStyle=\"#778899\";\n"
                               "                    break;\n"
                               "                case 2:\n"
                               "                    ctx.strokeStyle=\"#FF0000\";\n"
                               "                    ctx.fillStyle=\"#DC143C\";\n"
                               "                    break;\n"
                               "                case 3:\n"
                               "                    ctx.strokeStyle=\"#008000\";\n"
                               "                    ctx.fillStyle=\"#228B22\";\n"
                               "                    break;\n"
                               "                default:\n"
                               "                    break;\n"
                               "                }\n"
                               "                ctx.stroke();\n"
                               "                ctx.fill();\n"
                               "                xpos = xpos + 20;\n"
                               "            }\n"
                               "        }\n"
                               "    }\n"
                               "    xmlStatus.open(\"GET\",document.URL+\"status\",true);\n"
                               "    xmlStatus.send();\n"
                               "\n"
                               "    setTimeout(update, 1000);\n"
                               "}\n"
                               "setTimeout(update, 1000);\n"
                               "</script>\n"
                               "</head>\n"
                               "<body bgcolor=\"#efefef\">\n"
                               "<div class=\"my_window\" style=\"height:52px\">\n"
                               "<div style=\"position:relative; padding:5px; float:left;\"> <img src=\"data:image/png;base64, \"/> </div>\n"
                               "<div style=\"position:relative; padding:5px; float:center;\"> <h4 id=\"suite_name\" style=\"text-align: center; margin:0;\" class=\"font_h4\">Robot Testing Framework</h4></div>\n"
                               "</div>\n"
                               "<div class=\"my_window\" style=\"top:75px; height:90%; padding:5px\"><div id=\"result\" style=\"top:0px; height:96%; overflow-y:scroll; background:#ffffff;\"></div>\n"
                               "<table style=\"width:100%\"><tr><td width=\"90%\"><canvas id=\"progress\" width=\"1000px\" height=\"20px\" style=\"width:1000px; height:20px\" ></canvas></td><td align=\"right\"><input align=\"right\" type=\"checkbox\" id=\"autoscroll\" checked/> auto scroll</div></td></tr></table>\n"
                               //"<div style=\"top:94%; position:absolute; padding-left:20px;\">\n"
                               //"<p class=\"font_1\">Copyright (C) 2015-2019 Istituto Italiano di Tecnologia (IIT)</p>\n"
                               //"</div>\n"
                               "</body>\n"
                               "</html>\n";

WebProgressListenerImpl& WebProgressListenerImpl::create(unsigned int port,
                                                         bool verbose)
{
    static WebProgressListenerImpl instance(port, verbose);
    return instance;
}

WebProgressListenerImpl::WebProgressListenerImpl(unsigned int port,
                                                 bool verbose)
{
    suite_size = 0;
    this->verbose = verbose;
    this->port = port;
    server = mg_create_server(this, WebProgressListenerImpl::handler);
    if (server != nullptr) {
        std::string port_str = Asserter::format("%d", port);
        mg_set_option(server,
                      "listening_port",
                      port_str.c_str());
        shouldStop = false;
        updater = new std::thread(update, this);
    }
}

WebProgressListenerImpl::~WebProgressListenerImpl()
{
    shouldStop = true;
    // stop the pooling thread
    if (updater != nullptr) {
        updater->join();
        delete updater;
        updater = nullptr;
    }
    // delete the web server
    if (server != nullptr) {
        // ensure the last message delivery (?)
        mg_poll_server(server, 1000);
        mg_destroy_server(&server);
        server = nullptr;
    }
}

void WebProgressListenerImpl::update(void* param)
{
    auto* web = (WebProgressListenerImpl*)param;
    while (!web->shouldStop) {
        mg_poll_server(web->server, 1000);
    }
}

int WebProgressListenerImpl::handler(struct mg_connection* conn,
                                     enum mg_event ev)
{
    auto* web = (WebProgressListenerImpl*)conn->server_param;
    if (ev == MG_REQUEST) {
        if (strcmp(conn->uri, "/update") == 0) {
            web->critical.lock();
            std::string data = web->result;
            web->critical.unlock();
            mg_send_header(conn, "Content-Type", "text/turtle");
            mg_send_header(conn, "Access-Control-Allow-Origin", "*");
            //mg_send_header(conn, "Content-Location", "mydata.ttl");
            mg_printf_data(conn, "%s", data.c_str());
        } else if (strcmp(conn->uri, "/status") == 0) {
            web->critical.lock();
            std::stringstream ss;
            ss << R"({"name":")" << web->suite_name;
            ss << R"(","testStatus":[)";
            for (auto it = web->testStatus.begin(); it != web->testStatus.end(); ++it) {
                ss << (int)(*it);
                if (std::next(it) != web->testStatus.end()) {
                    ss << ",";
                }
            }
            for (int i = web->testStatus.size(); i < web->suite_size; ++i) {
                ss << ",0";
            }
            ss << "]}";

            web->critical.unlock();
            mg_send_header(conn, "Content-Type", "text/turtle");
            mg_send_header(conn, "Access-Control-Allow-Origin", "*");
            mg_printf_data(conn, "%s", ss.str().c_str());
        } else {
            mg_send_data(conn, html_page.c_str(), strlen(html_page.c_str()));
        }
        return MG_TRUE;
    }
    if (ev == MG_AUTH) {
        return MG_TRUE;
    }
    return MG_FALSE;
}

std::string WebProgressListenerImpl::encode(const std::string& data)
{
    std::string buffer;
    buffer.reserve(data.size());
    for (size_t pos = 0; pos != data.size(); ++pos) {
        switch (data[pos]) {
        case '&':
            buffer.append("&amp;");
            break;
        case '\"':
            buffer.append("&quot;");
            break;
        case '\'':
            buffer.append("&apos;");
            break;
        case '<':
            buffer.append("&lt;");
            break;
        case '>':
            buffer.append("&gt;");
            break;
        default:
            buffer.append(&data[pos], 1);
            break;
        }
    }
    return buffer;
}

void WebProgressListenerImpl::addReport(const Test* test,
                                        TestMessage msg)
{
    string text = Asserter::format("<br> %s (%s) %s : %s.",
                                   MSG_REPORT,
                                   encode(test->getName()).c_str(),
                                   encode(msg.getMessage()).c_str(),
                                   encode(msg.getDetail()).c_str());
    critical.lock();
    result += text;
    critical.unlock();
    //if(verbose && msg.getSourceLineNumber() != 0)
    //    cout<<GRAY<<msg.getSourceFileName()<<" at "<<msg.getSourceLineNumber()<<"."<<ENDC<<endl<<endl;
}

void WebProgressListenerImpl::addError(const Test* test,
                                       TestMessage msg)
{
    string text = Asserter::format("<br> %s (%s) %s : %s.",
                                   MSG_ERROR,
                                   encode(test->getName()).c_str(),
                                   encode(msg.getMessage()).c_str(),
                                   encode(msg.getDetail()).c_str());
    critical.lock();
    result += text;
    critical.unlock();
}

void WebProgressListenerImpl::addFailure(const Test* test,
                                         TestMessage msg)
{
    string text = Asserter::format("<br> %s (%s) %s : %s.",
                                   MSG_FAIL,
                                   encode(test->getName()).c_str(),
                                   encode(msg.getMessage()).c_str(),
                                   encode(msg.getDetail()).c_str());
    critical.lock();
    result += text;
    critical.unlock();
}

void WebProgressListenerImpl::startTest(const Test* test)
{
    string text = Asserter::format("<br> %s Test case %s started... %s",
                                   BLUE,
                                   encode(test->getName()).c_str(),
                                   ENDC);
    critical.lock();
    // if there is no test suite, use the test case's name
    if (suite_name.empty()) {
        suite_name = test->getName();
    }
    testStatus.push_back(TestStatus::Running);
    result += text;
    critical.unlock();
}

void WebProgressListenerImpl::endTest(const Test* test)
{
    string text = Asserter::format("<br> %s Test case %s %s %s",
                                   BLUE,
                                   encode(test->getName()).c_str(),
                                   (test->succeeded()) ? "passed!" : "failed!",
                                   ENDC);
    critical.lock();
    testStatus.back() = (test->succeeded()) ? TestStatus::Success : TestStatus::Failed;
    result += text;
    critical.unlock();
}

void WebProgressListenerImpl::startTestSuite(const Test* test)
{
    string text = Asserter::format("<br> %s Test suite %s started... %s",
                                   BLUE,
                                   encode(test->getName()).c_str(),
                                   ENDC);
    critical.lock();
    result += text;
    suite_name = test->getName();
    auto suite = dynamic_cast<const TestSuite*>(test);
    if (suite != nullptr) {
        suite_size = suite->size();
    }
    critical.unlock();
}

void WebProgressListenerImpl::endTestSuite(const Test* test)
{
    string text = Asserter::format("<br> %s Test suite %s %s %s",
                                   BLUE,
                                   encode(test->getName()).c_str(),
                                   (test->succeeded()) ? "passed!" : "failed!",
                                   ENDC);
    critical.lock();
    result += text;
    critical.unlock();
}

void WebProgressListenerImpl::startTestRunner()
{
    string text = Asserter::format("<br> %s Starting test runner. %s",
                                   BLUE,
                                   ENDC);
    critical.lock();
    result += text;
    critical.unlock();
}

void WebProgressListenerImpl::endTestRunner()
{
    string text = Asserter::format("<br> %s Ending test runner. %s",
                                   BLUE,
                                   ENDC);
    critical.lock();
    result += text;
    critical.unlock();
}


/**
 * WebProgressListener
 */
WebProgressListener::WebProgressListener(unsigned int port,
                                         bool verbose)
{
    implement = &WebProgressListenerImpl::create(port, verbose);
}

WebProgressListener::~WebProgressListener() = default;

void WebProgressListener::addReport(const Test* test,
                                    TestMessage msg)
{
    ((WebProgressListener*)implement)->addReport(test, msg);
}

void WebProgressListener::addError(const Test* test,
                                   TestMessage msg)
{
    ((WebProgressListener*)implement)->addError(test, msg);
}

void WebProgressListener::addFailure(const Test* test,
                                     TestMessage msg)
{
    ((WebProgressListener*)implement)->addFailure(test, msg);
}

void WebProgressListener::startTest(const Test* test)
{
    ((WebProgressListener*)implement)->startTest(test);
}

void WebProgressListener::endTest(const Test* test)
{
    ((WebProgressListener*)implement)->endTest(test);
}

void WebProgressListener::startTestSuite(const Test* test)
{
    ((WebProgressListener*)implement)->startTestSuite(test);
}

void WebProgressListener::endTestSuite(const Test* test)
{
    ((WebProgressListener*)implement)->endTestSuite(test);
}

void WebProgressListener::startTestRunner()
{
    ((WebProgressListener*)implement)->startTestRunner();
}

void WebProgressListener::endTestRunner()
{
    ((WebProgressListener*)implement)->endTestRunner();
}
