//
//  HTTPDaemon.cpp
//

#define DBG_LEVEL 0
#include "Raym/Log.h"
#include "Raym/Raym.h"
#include "net/HTTPDaemon.h"

using namespace Raym;

namespace NET
{

DEFINE_STATIC_MUTEX(mutex_);
static HTTPResponse *unnecessary_ = NULL;

HTTPDaemon::HTTPDaemon()
{
    DebugLog2("HTTPDaemon::HTTPDaemon()");

    _port = -1;
    _backlog = -1;
    _state = ST_IDLE;
    _rootPath = NULL;
    _delegate = NULL;
    _sockets = NULL;

}

HTTPDaemon::~HTTPDaemon()
{
    stop();

    RELEASE(_rootPath);
    RELEASE(_sockets);

    DebugLog2("HTTPDaemon::~HTTPDaemon()");
}

HTTPDaemon *HTTPDaemon::alloc()
{
    return new HTTPDaemon();
}

HTTPDaemon *HTTPDaemon::initWithPort(int port, int backlog)
{
    DebugLog2("HTTPDaemon::initWithPort()");

    _port = port;
    _backlog = backlog;
    _sockets = Array::alloc()->initWithCapacity(0);
    return this;
}

void HTTPDaemon::setDelegate(HTTPDaemonDelegate *delegate)
{
    DebugLog2("HTTPDaemon::setDelegate()");

    EnterCriticalSection(&_cs);
    _delegate = delegate;
    LeaveCriticalSection(&_cs);
}

void HTTPDaemon::setRootPath(String *path)
{
    DebugLog2("HTTPDaemon::setRootPath()");

    RELEASE(_rootPath);
    if (path != NULL)
    {
        _rootPath = path;
        _rootPath->retain();
    }
}

String *HTTPDaemon::rootPath()
{
    return _rootPath;
}

HTTPResponse *HTTPDaemon::responseWithReason(String *reason, int status, String *version)
{
    DebugLog2("HTTPDaemon::responseWithReason()");

    HTTPResponse *resp = HTTPResponse::alloc()->init();
    resp->setVersion(version);
    resp->setReason(reason);
    resp->setStatus(status);

    // header & body
    InternetTextMessageHeader * header = NULL;
    InternetTextMessageBody *   body   = NULL;
    switch (status)
    {
    case HTTP_STATUS_NO_CONTENT:
        {
            header = InternetTextMessageHeader::alloc()->init();
            header->setFieldBodyWithName(String::stringWithUTF8String("close"), String::stringWithUTF8String("Connection"));
        }
        break;

    case HTTP_STATUS_INTERNAL_SERVER_ERROR:
        {
            // header
            header = InternetTextMessageHeader::alloc()->init();
            header->setFieldBodyWithName(String::stringWithUTF8String("close"), String::stringWithUTF8String("Connection"));

            // body
            char html[1024];
            sprintf_s(html, 1024, "<html><head><title>%s</title></head><body>%s</body></html>", reason->cString(), reason->cString());
            body = InternetTextMessageBody::alloc()->initWithString(String::stringWithUTF8String(html));
        }
        break;

    default:
        break;
    }

    // Contet-Length
    header->setFieldBodyWithName(String::stringWithFormat("%I64u", body->body()->length()), "Content-Length");

    // message
    InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, body);
    RELEASE(header);
    RELEASE(body);

    resp->setMessage(message);
    resp->autorelease();
    RELEASE(message);

    return resp;
}

HTTPResponse *HTTPDaemon::responseWithPath(String *path, HTTPRequest *request)
{
    DebugLog2("HTTPDaemon::responseWithPath()");

    HTTPResponse *result = NULL;
    if ((path != NULL) && (request != NULL))
    {
        Data *bodyData = Data::alloc()->initWithContentsOfFile(path);
        if (bodyData != NULL)
        {
            // header
            InternetTextMessageHeader *header = InternetTextMessageHeader::alloc()->init();
            // Date
            // Server
            // Content-Encoding
            // Last-Modified
            // Content-Type
            String *ext = path->pathExtension()->lowercaseString();
            if (ext->isEqualToString("htm") || ext->isEqualToString("html"))
            {
                header->setFieldBodyWithName("text/html", "Content-Type");
            }
            else if (ext->isEqualToString("jpg"))
            {
                header->setFieldBodyWithName("image/jpeg", "Content-Type");
            }
            else if (ext->isEqualToString("png"))
            {
                header->setFieldBodyWithName("image/png", "Content-Type");
            }
            else if (ext->isEqualToString("gif"))
            {
                header->setFieldBodyWithName("image/gif", "Content-Type");
            }
            else if (ext->isEqualToString("js"))
            {
                header->setFieldBodyWithName("text/javascript", "Content-Type");
            }
            else if (ext->isEqualToString("css"))
            {
                header->setFieldBodyWithName("text/css", "Content-Type");
            }
            else if (ext->isEqualToString("log"))
            {
                header->setFieldBodyWithName("text/plane", "Content-Type");
            }
            else if (ext->isEqualToString("m3u8"))
            {
                header->setFieldBodyWithName("application/x-mpegURL", "Content-Type");
            }
            else if (ext->isEqualToString("ts"))
            {
                header->setFieldBodyWithName("video/mp2t", "Content-Type");
            }
            else
            {
                header->setFieldBodyWithName("application/octet-stream", "Content-Type");
            }
            // Connection
            // Transfer-Encoding
            // Content-Length
            header->setFieldBodyWithName(String::stringWithFormat("%I64u", bodyData->length()), "Content-Length");

            // body
            InternetTextMessageBody *body = InternetTextMessageBody::alloc()->initWithData(bodyData);
            RELEASE(bodyData);

            // message
            InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, body);
            RELEASE(header);
            RELEASE(body);
            if (message != NULL)
            {
//                result = HTTPResponse::response();
                result = HTTPResponse::alloc()->init();
                result->setVersion(request->version());
                result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_OK));
                result->setStatus(HTTP_STATUS_OK);
                result->setMessage(message);
                result->autorelease();
                RELEASE(message);
            }
        }
        else
        {
            DebugLog3("HTTPDaemon::responseWithPath(): date read error.");
        }
    }
    return result;
}

HTTPResponse *HTTPDaemon::responseOfUnnecessary()
{
    mutex_.lock();
    if (unnecessary_ == NULL)
    {
        unnecessary_ = HTTPResponse::alloc();
        ((Object *)unnecessary_)->init();
        ((Object *)unnecessary_)->autorelease(true);
    }
    mutex_.unlock();

    return unnecessary_;
}

String *HTTPDaemon::reasonForStatus(int status)
{
    DebugLog2("HTTPDaemon::reasonForStatus()");

    const char *result = NULL;
    switch (status)
    {
    // Informational 1xx
    case 100:   result = "Continue";                            break;
    case 101:   result = "Swithing Protocols";                  break;
    // Successful 2xx
    case 200:   result = "OK";                                  break;
    case 201:   result = "Created";                             break;
    case 202:   result = "Accepted";                            break;
    case 203:   result = "Non-Authoritative Information";       break;
    case 204:   result = "No Content";                          break;
    case 205:   result = "Reset Content";                       break;
    case 206:   result = "Partial Content";                     break;
    // Redirection 3xx
    case 300:   result = "Multiple Choices";                    break;
    case 301:   result = "Moved Permanently";                   break;
    case 302:   result = "Found";                               break;
    case 303:   result = "See Other";                           break;
    case 304:   result = "Not Modified";                        break;
    case 305:   result = "Use Proxy";                           break;
    case 306:   result = "(Unused)";                            break;
    case 307:   result = "Temporary Redirect";                  break;
    // Client Error 4xx
    case 400:   result = "Bad Request";                         break;
    case 401:   result = "Unauthorized";                        break;
    case 402:   result = "Payment Required";                    break;
    case 403:   result = "Forbidden";                           break;
    case 404:   result = "Not Found";                           break;
    case 405:   result = "Method Not Allowed";                  break;
    case 406:   result = "Not Acceptable";                      break;
    case 407:   result = "Proxy Authentication Required";       break;
    case 408:   result = "Request Timeout";                     break;
    case 409:   result = "Conflict";                            break;
    case 410:   result = "Gone";                                break;
    case 411:   result = "Length Required";                     break;
    case 412:   result = "Precondition Failed";                 break;
    case 413:   result = "Request Entity Too Large";            break;
    case 414:   result = "Request-URI Too Long";                break;
    case 415:   result = "Unsupported Media Type";              break;
    case 416:   result = "Requested Range Not Satisfiable";     break;
    case 417:   result = "Expectation Failed";                  break;
    // Server Error 5xx
    case 500:   result = "Internal Server Error";               break;
    case 501:   result = "Not Implemented";                     break;
    case 502:   result = "Bad Gateway";                         break;
    case 503:   result = "Service Unavailable";                 break;
    case 504:   result = "Gateway Timeout";                     break;
    case 505:   result = "HTTP Version Not Supported";          break;
    default:                                                    break;
    }
    return String::stringWithUTF8String(result);
}

unsigned __stdcall HTTPDaemon_session(void *arg)
{
    HTTPDaemonSessionArgs *session = (HTTPDaemonSessionArgs *)arg;
    session->_daemon->session(session->_sock, &session->_client);
    closesocket(session->_sock);
    delete session;
    return 0;
}

void HTTPDaemon::session(SOCKET sock, struct sockaddr_in *client)
{
    DebugLog0("session start. sock = %d", sock);

    DebugLog2("HTTPDaemon::session()");

    Number *num_sock = Number::alloc()->initWithInt((int)sock);

    EnterCriticalSection(&_cs);
    _sockets->addObject(num_sock);
    LeaveCriticalSection(&_cs);

    while (true)
    {
        AutoreleasePool *pool = AutoreleasePool::alloc()->init();
        bool done;

        HTTPResponse *response = NULL;
        DebugLog3("before request()");
        HTTPRequest *request;
        if (_delegate != NULL)
        {
            request = _delegate->readRequest(sock);
        }
        else
        {
            request = HTTPRequest::requestWithSocket(sock);
        }
        DebugLog3("after request()");
        if (request != NULL)
        {
            if (_delegate != NULL)
            {
                response = _delegate->request(request, sock, client);
            }

            if ((response == NULL) && (request->method()->isEqualToString("GET") || request->method()->isEqualToString("HEAD")))
            {
                if (_rootPath != NULL)
                {
                    String *path = _rootPath->stringByAppendingPathComponent(request->URI());
                    //path = path->stringByAbbreviatingWithTildeInPath();
                    //path = path->stringByStandardizingPath();
                    if (path->hasPrefix(_rootPath))
                    {
                        FileManager *fm = FileManager::defaultManager();
                        bool isDir = false;
                        if (fm->fileExistsAtPath(path, &isDir))
                        {
                            if ((!isDir) && !(request->URI()->hasSuffix("/")))
                            {
                                String *ext = path->pathExtension()->lowercaseString();
                                if (ext->isEqualToString("htm") ||
                                    ext->isEqualToString("html") ||
                                    ext->isEqualToString("jpg") ||
                                    ext->isEqualToString("png") ||
                                    ext->isEqualToString("gif") ||
                                    ext->isEqualToString("js") ||
                                    ext->isEqualToString("manifest") ||
                                    ext->isEqualToString("gtpl") ||
                                    ext->isEqualToString("css"))
                                {
                                    response = responseWithPath(path, request);
                                    if (response == NULL)
                                    {
                                        DebugLog2("error: %s\n", path->cString());
                                    }
                                }
                                else
                                {
                                    DebugLog2("unsupported type: %s\n", ext->cString());
                                }
                            }
                        }
                        else
                        {
                            DebugLog2("not exists: %s\n", path->cString());
                        }
                    }
                }
            }

            if (response == NULL)
            {
                DebugLog3("response == NULL");
                String *ver;
                if (request == NULL)
                {
                    ver = String::stringWithUTF8String("HTTP/1.1");
                }
                else
                {
                    ver = request->version();
                }

                int status;
                if (request == NULL)
                {
                    DebugLog3("request == NULL");
                }
                else if (request->method() == NULL)
                {
                    DebugLog3("method == NULL");
                }
                if (request->method()->isEqualToString("GET") || request->method()->isEqualToString("HEAD"))
                {
                    status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
                }
                else if (request->method()->isEqualToString("POST"))
                {
                    status = HTTP_STATUS_NO_CONTENT;
                }
                else
                {
                    status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
                }
                response = responseWithReason(reasonForStatus(status), status, ver);
            }

            if (response != responseOfUnnecessary())
            {
                // status line
                char statusLine[256];
                sprintf_s(statusLine, sizeof(statusLine), "%s %03d %s\r\n", response->version()->cString(), response->status(), response->reason()->cString());
                send(sock, statusLine, (int)strlen(statusLine), 0);

                InternetTextMessage *message = response->message();
                if (message != NULL)
                {
                    // response header
                    InternetTextMessageHeader *header = message->header();
                    if (header != NULL)
                    {
                        Array *fieldNames = header->fieldNames();
                        for (uint i = 0; i < fieldNames->count(); ++i)
                        {
                            String *name = (String *)fieldNames->objectAtIndex(i);
                            char field[16384];
                            sprintf_s(field, sizeof(field), "%s: %s\r\n", name->cString(), header->fieldBodyForName(name)->cString());

                            DebugLog2("send response header");
                            send(sock, field, (int)strlen(field), 0);
                        }
                    }

                    send(sock, "\r\n", 2, 0);

                    if (!request->method()->isEqualToString("HEAD"))
                    {
                        // response entity
                        InternetTextMessageBody *body = message->body();
                        if (body != NULL)
                        {
                            const char *ptr = NULL;
                            UInteger length;
                            Data *data = body->data();
                            if (data != NULL)
                            {
                                ptr = (const char *)data->bytes();
                                length = data->length();
                            }
                            else if (body->body() != NULL)
                            {
                                String *str = body->body();
                                ptr = str->cString();
                                length = str->length();
                            }
                            if (ptr != NULL)
                            {
                                UInteger offset = 0;
                                while (offset < length)
                                {
                                    int len = ((length - offset) > 16384) ? 16384 : (int)(length - offset);
                                    DebugLog2("send response entity");
                                    send(sock, &ptr[offset], len, 0);
                                    offset += len;
                                }
                            }
                        }
                    }
                }

                if ((response->status() / 100) != 2)
                {
                    DebugLog2("done. response is not OK.");
                    done = true;
                }
                else if (request != NULL)
                {
                    if (message != NULL)
                    {
                        InternetTextMessageHeader *header = message->header();
                        if (header != NULL)
                        {
                            String *fieldBody = header->fieldBodyForName(String::stringWithUTF8String("Connection"));
                            if (fieldBody != NULL)
                            {
                                if (strstr(fieldBody->cString(), "close") != NULL)
                                {
                                    DebugLog2("done. request connection is close.");
                                    done = true;
                                }
                            }
                        }
                    }
                }
            }
        }
        else
        {
            DebugLog3("request is null");
            done = true;
        }

        pool->release();
        if (done)
        {
            break;
        }
    }

    DebugLog0("session end. sock = %d", sock);

    EnterCriticalSection(&_cs);
    _sockets->removeObject(num_sock);
    LeaveCriticalSection(&_cs);
    num_sock->release();
}

unsigned __stdcall HTTPDaemon_run(void *arg)
{
    ((HTTPDaemon *)arg)->run();
    return 0;
}

void HTTPDaemon::run()
{
    DebugLog2("%s()\n", __FUNCTION__);

    AutoreleasePool *pool = AutoreleasePool::alloc()->init();

    EnterCriticalSection(&_cs);

    while (_state == ST_READY)
    {
/*
使用側でコールしておくこと
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2,0), &wsaData);
*/
        SOCKET httpd = INVALID_SOCKET;

        // ready socket
        httpd = socket(AF_INET, SOCK_STREAM, 0);
        if (httpd == INVALID_SOCKET)
        {
            DebugLog3("error: socket() %d\n", WSAGetLastError());
            break;
        }

        //
        BOOL yes = 1;
        setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));

        // bind
        struct sockaddr_in own_addr;
        own_addr.sin_family = AF_INET;
        own_addr.sin_port = htons(_port);
        own_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(httpd, (struct sockaddr *)&own_addr, sizeof(own_addr)) != 0)
        {
            DebugLog3("error: bind() %d\n", WSAGetLastError());
            closesocket(httpd);
            break;
        }

        // listen
        if (listen(httpd, _backlog) != 0)
        {
            DebugLog3("error: listen() %d\n", WSAGetLastError());
            closesocket(httpd);
            break;
        }

        // state change
        _state = ST_RUN;

        LeaveCriticalSection(&_cs);

        bool done = false;
        while (!done)
        {
            fd_set fdset;
            FD_ZERO(&fdset);
            FD_SET(httpd, &fdset);
            struct timeval timeout = {1, 0};
            
            if (select(0, &fdset, NULL, NULL, &timeout) == SOCKET_ERROR)
            {
                int err = WSAGetLastError();
                if (err != WSAEINTR)
                {
                    DebugLog3("error: select() %d\n", err);
                    break;
                }
            }

            if (FD_ISSET(httpd, &fdset))
            {
                // accept
                struct sockaddr_in acc_addr;
                int sock_len = sizeof(acc_addr);
                SOCKET newsock;
                if ((newsock = accept(httpd, (struct sockaddr *)&acc_addr, &sock_len)) != INVALID_SOCKET)
                {
                    DebugLog0("accepted. newsock = %d", newsock);
                    HTTPDaemonSessionArgs *args = new HTTPDaemonSessionArgs();
                    args->_daemon = this;
                    args->_sock = newsock;
                    memcpy(&args->_client, &acc_addr, sizeof(acc_addr));

                    HANDLE h;
                    unsigned int uiThreadId;
                    h = (HANDLE)_beginthreadex(NULL,
                                               0,
                                               HTTPDaemon_session,
                                               args,
                                               0,
                                               &uiThreadId);
                    if (h == NULL)
                    {
                        DebugLog3("error: _beginthreades()\n");
                        closesocket(newsock);
                        delete args;
                    }
                }
                else
                {
                    DebugLog3("error: accept() %d\n", WSAGetLastError());
                }
            }

            // state check
            EnterCriticalSection(&_cs);
            done = (_state == ST_DONE);
            LeaveCriticalSection(&_cs);
        }

        // socket close
        closesocket(httpd);

//        WSACleanup();

        // state change
        EnterCriticalSection(&_cs);
        break;
    }

    DebugLog2("HTTPDaemon::run() loop done.");

    // セッションの終了待ち
    if (_sockets->count() > 0)
    {
        DebugLog2("wait for session close 00");
        // 残存セッションを終了させる為、ソケットを閉じる
        for (uint i = 0; i < _sockets->count(); ++i)
        {
            SOCKET s = (SOCKET)((Number *)_sockets->objectAtIndex(i))->intValue();
            closesocket(s);
        }
        DebugLog2("wait for session close 01");
        // セッションが終了して_socketからオブジェクトを削除するのを待つ
        while (_sockets->count() > 0)
        {
            LeaveCriticalSection(&_cs);
            ::Sleep(200);
            EnterCriticalSection(&_cs);
        }
        DebugLog2("wait for session close 02");
    }

    DebugLog2("HTTPDaemon::run() session close done.");

    _state = ST_IDLE;

    LeaveCriticalSection(&_cs);

    pool->release();

    DebugLog2("%s() done.\n", __FUNCTION__);
}

bool HTTPDaemon::start()
{
    DebugLog2("%s()\n", __FUNCTION__);
    
    bool result = false;
    
    EnterCriticalSection(&_cs);
    
    if (_state == ST_IDLE)
    {
        HANDLE h;
        unsigned int uiThreadId;
        
        h = (HANDLE)_beginthreadex(NULL,
                                   0,
                                   HTTPDaemon_run,
                                   this,
                                   0,
                                   &uiThreadId);
        if (h != NULL)
        {
            _state = ST_READY;

            LeaveCriticalSection(&_cs);

            bool done = false;
            while (!done)
            {
                bool needSleep = false;

                EnterCriticalSection(&_cs);

                if (_state == ST_IDLE)
                {
                    done = true;
                }
                else if (_state == ST_RUN)
                {
                    done = true;
                    result = true;
                }
                else if (_state == ST_READY)
                {
                    needSleep = true;
                }
                LeaveCriticalSection(&_cs);

                if (needSleep)
                {
                    ::Sleep(100); // 100 ms
                }
            }

            EnterCriticalSection(&_cs);
        }
    }

    LeaveCriticalSection(&_cs);

    return result;
}

void HTTPDaemon::stop()
{
    DebugLog2("HTTPDaemon::stop()");

    EnterCriticalSection(&_cs);
    if (_state == ST_RUN)
    {
        _state = ST_DONE;
    }
    LeaveCriticalSection(&_cs);

    wait();

    DebugLog2("HTTPDaemon::stop() done.");
}

void HTTPDaemon::wait()
{
    DebugLog2("HTTPDaemon::wait()");
    bool done = false;
    while (!done)
    {
        EnterCriticalSection(&_cs);
        done = (_state == ST_IDLE);
        LeaveCriticalSection(&_cs);
        if (!done)
        {
            ::Sleep(100);
        }
    }
    DebugLog2("HTTPDaemon::wait() done.");
}

const char *HTTPDaemon::className()
{
    return "NET::HTTPDaemon";
}

} // NET
