/**
 *
 *  press.cc
 *  An Tao
 *
 *  Copyright 2018, An Tao.  All rights reserved.
 *  https://github.com/an-tao/drogon
 *  Use of this source code is governed by the MIT license
 *  that can be found in the License file.
 *
 *  Drogon
 *
 */

#include "press.h"
#include "cmd.h"
#include <drogon/DrClassMap.h>
#include <iostream>
#include <memory>
#include <iomanip>
#include <stdlib.h>
#ifndef _WIN32
#include <unistd.h>
#endif

using namespace drogon_ctl;

std::string press::detail()
{
    return "Use press command to do stress testing\n"
           "Usage:drogon_ctl press <options> <url>\n"
           "  -n num    number of requests(default : 1)\n"
           "  -t num    number of threads(default : 1)\n"
           "  -c num    concurrent connections(default : 1)\n"
           //  "  -k        keep alive(default: no)\n"
           "  -q        no progress indication(default: no)\n\n"
           "example: drogon_ctl press -n 10000 -c 100 -t 4 -q "
           "http://localhost:8080/index.html\n";
}

void outputErrorAndExit(const std::string_view &err)
{
    std::cout << err << std::endl;
    exit(1);
}

void press::handleCommand(std::vector<std::string> &parameters)
{
    for (auto iter = parameters.begin(); iter != parameters.end(); iter++)
    {
        auto &param = *iter;
        if (param.find("-n") == 0)
        {
            if (param == "-n")
            {
                ++iter;
                if (iter == parameters.end())
                {
                    outputErrorAndExit("No number of requests!");
                }
                auto &num = *iter;
                try
                {
                    numOfRequests_ = std::stoll(num);
                }
                catch (...)
                {
                    outputErrorAndExit("Invalid number of requests!");
                }
                continue;
            }
            else
            {
                auto num = param.substr(2);
                try
                {
                    numOfRequests_ = std::stoll(num);
                }
                catch (...)
                {
                    outputErrorAndExit("Invalid number of requests!");
                }
                continue;
            }
        }
        else if (param.find("-t") == 0)
        {
            if (param == "-t")
            {
                ++iter;
                if (iter == parameters.end())
                {
                    outputErrorAndExit("No number of threads!");
                }
                auto &num = *iter;
                try
                {
                    numOfThreads_ = std::stoll(num);
                }
                catch (...)
                {
                    outputErrorAndExit("Invalid number of threads!");
                }
                continue;
            }
            else
            {
                auto num = param.substr(2);
                try
                {
                    numOfThreads_ = std::stoll(num);
                }
                catch (...)
                {
                    outputErrorAndExit("Invalid number of threads!");
                }
                continue;
            }
        }
        else if (param.find("-c") == 0)
        {
            if (param == "-c")
            {
                ++iter;
                if (iter == parameters.end())
                {
                    outputErrorAndExit("No number of connections!");
                }
                auto &num = *iter;
                try
                {
                    numOfConnections_ = std::stoll(num);
                }
                catch (...)
                {
                    outputErrorAndExit("Invalid number of connections!");
                }
                continue;
            }
            else
            {
                auto num = param.substr(2);
                try
                {
                    numOfConnections_ = std::stoll(num);
                }
                catch (...)
                {
                    outputErrorAndExit("Invalid number of connections!");
                }
                continue;
            }
        }
        // else if (param == "-k")
        // {
        //     keepAlive_ = true;
        //     continue;
        // }
        else if (param == "-q")
        {
            processIndication_ = false;
        }
        else if (param[0] != '-')
        {
            url_ = param;
        }
    }
    // std::cout << "n=" << numOfRequests_ << std::endl;
    // std::cout << "t=" << numOfThreads_ << std::endl;
    // std::cout << "c=" << numOfConnections_ << std::endl;
    // std::cout << "q=" << processIndication_ << std::endl;
    // std::cout << "url=" << url_ << std::endl;
    if (url_.empty() || url_.compare(0, 4, "http") != 0 ||
        (url_.compare(4, 3, "://") != 0 && url_.compare(4, 4, "s://") != 0))
    {
        outputErrorAndExit("Invalid URL");
    }
    else
    {
        auto pos = url_.find("://");
        auto posOfPath = url_.find("/", pos + 3);
        if (posOfPath == std::string::npos)
        {
            host_ = url_;
            path_ = "/";
        }
        else
        {
            host_ = url_.substr(0, posOfPath);
            path_ = url_.substr(posOfPath);
        }
    }
    // std::cout << "host=" << host_ << std::endl;
    // std::cout << "path=" << path_ << std::endl;
    doTesting();
}

void press::doTesting()
{
    createRequestAndClients();
    if (clients_.empty())
    {
        outputErrorAndExit("No connection!");
    }
    statistics_.startDate_ = trantor::Date::now();
    for (auto &client : clients_)
    {
        sendRequest(client);
    }
    loopPool_->wait();
}

void press::createRequestAndClients()
{
    loopPool_ = std::make_unique<trantor::EventLoopThreadPool>(numOfThreads_);
    loopPool_->start();
    for (size_t i = 0; i < numOfConnections_; ++i)
    {
        auto client =
            HttpClient::newHttpClient(host_, loopPool_->getNextLoop());
        client->enableCookies();
        clients_.push_back(client);
    }
}

void press::sendRequest(const HttpClientPtr &client)
{
    auto numOfRequest = statistics_.numOfRequestsSent_++;
    if (numOfRequest >= numOfRequests_)
    {
        return;
    }
    auto request = HttpRequest::newHttpRequest();
    request->setPath(path_);
    request->setMethod(Get);
    // std::cout << "send!" << std::endl;
    client->sendRequest(
        request,
        [this, client, request](ReqResult r, const HttpResponsePtr &resp) {
            size_t goodNum, badNum;
            if (r == ReqResult::Ok)
            {
                // std::cout << "OK" << std::endl;
                goodNum = ++statistics_.numOfGoodResponse_;
                badNum = statistics_.numOfBadResponse_;
                statistics_.bytesRecieved_ += resp->body().length();
                auto delay = trantor::Date::now().microSecondsSinceEpoch() -
                             request->creationDate().microSecondsSinceEpoch();
                statistics_.totalDelay_ += delay;
            }
            else
            {
                goodNum = statistics_.numOfGoodResponse_;
                badNum = ++statistics_.numOfBadResponse_;
                if (badNum > numOfRequests_ / 10)
                {
                    outputErrorAndExit("Too many errors");
                }
            }
            if (goodNum + badNum >= numOfRequests_)
            {
                outputResults();
            }
            if (r == ReqResult::Ok)
                sendRequest(client);
            else
            {
                client->getLoop()->runAfter(1, [this, client]() {
                    sendRequest(client);
                });
            }

            if (processIndication_)
            {
                auto rec = goodNum + badNum;
                if (rec % 100000 == 0)
                {
                    std::cout << rec << " responses are received" << std::endl
                              << std::endl;
                }
            }
        });
}

void press::outputResults()
{
    static std::mutex mtx;
    size_t totalSent = 0;
    size_t totalRecv = 0;
    for (auto &client : clients_)
    {
        totalSent += client->bytesSent();
        totalRecv += client->bytesReceived();
    }
    auto now = trantor::Date::now();
    auto microSecs = now.microSecondsSinceEpoch() -
                     statistics_.startDate_.microSecondsSinceEpoch();
    double seconds = (double)microSecs / 1000000.0;
    size_t rps = static_cast<size_t>(statistics_.numOfGoodResponse_ / seconds);
    std::cout << std::endl;
    std::cout << "TOTALS:   " << numOfConnections_ << " connect, "
              << numOfRequests_ << " requests, "
              << statistics_.numOfGoodResponse_ << " success, "
              << statistics_.numOfBadResponse_ << " fail" << std::endl;

    std::cout << "TRAFFIC:  "
              << statistics_.bytesRecieved_ / statistics_.numOfGoodResponse_
              << " avg bytes, "
              << (totalRecv - statistics_.bytesRecieved_) /
                     statistics_.numOfGoodResponse_
              << " avg overhead, " << statistics_.bytesRecieved_ << " bytes, "
              << totalRecv - statistics_.bytesRecieved_ << " overhead"
              << std::endl;

    std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(3)
              << "TIMING:   " << seconds << " seconds, " << rps << " rps, "
              << (double)(statistics_.totalDelay_) /
                     statistics_.numOfGoodResponse_ / 1000
              << " ms avg req time" << std::endl;

    std::cout << "SPEED:    download " << totalRecv / seconds / 1000
              << " kBps, upload " << totalSent / seconds / 1000 << " kBps"
              << std::endl
              << std::endl;
    exit(0);
}
