//
// URLConnection.cpp
//

#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#endif

#define DBG_LEVEL 0
#include <Raym/Log.h>
#include <Raym/URLConnection.h>
#include <Raym/HTTPURLResponse.h>

namespace Raym
{

URLConnection::URLConnection()
{
}

URLConnection::~URLConnection()
{
}

//
// socketからterminatorで指定する終端文字列に遭遇するまで読み込む。
// 返却したバッファは必ずfreeすること。
// 失敗した場合はNULLを返却する。
//
#ifdef _WIN32
static char *readLine(SOCKET socket, const char *terminator)
#else
static char *readLine(int socket, const char *terminator)
#endif
{
    char *buf = NULL;
    if ((socket >= 0) && (terminator != NULL) && (strlen(terminator) > 0))
    {
        static const int BUF_UNIT = 256;
        long long unit_count = 0;
        long long term_len = strlen(terminator);
        long long offset = 0;
        bool done = false;
        while (true)
        {
            // バッファ確保
            if (offset == (unit_count * BUF_UNIT))
            {
                ++unit_count;
                char *newbuf = (char *)realloc(buf, (unit_count * BUF_UNIT));
                if (newbuf == NULL)
                {
                    if (buf != NULL)
                    {
                        free(buf);
                        buf = NULL;
                        printf("realloc ng.\n");
                    }
                    break;
                }
                buf = newbuf;
            }

            if (done)
            {
                buf[offset] = '\0';
                break;
            }

            // 1文字読み込み
            int len = recv(socket, &buf[offset], 1, 0);
            if (len <= 0)
            {
                if (offset == 0)
                {
                    free(buf);
                    buf = NULL;
                }
                if (len < 0)
                {
                    printf("recv error.\n");
                }
                break;
            }
            if (offset >= (term_len - 1))
            {
                if (memcmp(&buf[offset - term_len + 1], terminator, term_len) == 0)
                {
                    done = true;
                }
            }
            ++offset;
        }
    }
    return buf;
}

#ifdef _WIN32
static HTTPURLResponse *receiveHTTPURLResponse(SOCKET socket, URL *url)
#else
static HTTPURLResponse *receiveHTTPURLResponse(int socket, URL *url)
#endif
{
    HTTPURLResponse *result = NULL;

    if (socket >= 0)
    {
        // Status Line の読み込み
        char *statusLine;
        while (true)
        {
            statusLine = readLine(socket, "\r\n");
            if (statusLine == NULL)
            {
                DebugLog3("error: read status line.\n");
                return NULL;
            }
            if (strlen(statusLine) > 0)
            {
                break;
            }

            DebugLog3("error: read status line. (retry)\n");
            free(statusLine);
            statusLine = NULL;
        }

        char *p = strchr(statusLine, ' ');
        if ((p == NULL) || ((strncmp("HTTP/1.0 ", statusLine, 9) != 0) && (strncmp("HTTP/1.1 ", statusLine, 9) != 0)))
        {
            DebugLog3("error: invalid format. (HTTP-Version) \"%s\"\n", statusLine);
            free(statusLine);
            return NULL;
        }
        *p = '\0';

        String *http_version = String::stringWithUTF8String(statusLine);

        char *code = p + 1;
        p = strchr(code, ' ');
        if (p == NULL)
        {
            DebugLog3("error: invalid format. (Status-Code) \"%s\"\n", statusLine);
            free(statusLine);
            return NULL;
        }
        *p = '\0';

        int status_code = atoi(code);
        char tmp[256];
#ifdef _WIN32
        sprintf_s(tmp, sizeof(tmp), "%d", status_code);
#else
        sprintf(tmp, "%d", status_code);
#endif
        if (strcmp(code, tmp) != 0)
        {
            DebugLog3("error: invalid format. (Status-Code) \"%s\"\n", statusLine);
            free(statusLine);
            return NULL;
        }

        char *reason = p + 1;
        p = strstr(reason, "\r\n");
        if (p == NULL)
        {
            DebugLog3("error: invalid format. (Reason-Phrase) \"%s\"\n", statusLine);
            free(statusLine);
            return NULL;
        }
        *p = '\0';

        String *reason_phrase = String::stringWithUTF8String(reason);

        free(statusLine);

        DebugLog3("status line: %s %d %s\n", http_version->cString(), status_code, reason_phrase->cString());

        Dictionary *headers = Dictionary::dictionaryWithCapacity(0);

        if (http_version->isEqualToString("HTTP/1.1"))
        {
            // ヘッダ読み込み
            while (true)
            {
                char *header_line = readLine(socket, "\r\n");
                if (header_line == NULL)
                {
                    break;
                }
                if (strcmp("\r\n", header_line) == 0)
                {
                    free(header_line);
                    break;
                }

                char *p = strchr(header_line, ':');
                if (p != NULL)
                {
                    *p = '\0';
                    char *value = p + 1;
                    while (*value == ' ')
                    {
                        ++value;
                    }
                    p = strstr(value, "\r\n");
                    *p = '\0';
                    headers->setString(value, header_line);
                    DebugLog3("%s: %s\n", header_line, value);
                }

                free(header_line);
            }
        }
        else
        {
        }
        result = HTTPURLResponse::alloc()->initWithURL(url, status_code, http_version, headers);
        if (result != NULL)
        {
            result->autorelease();
        }
    }
    return result;
}

#ifdef _WIN32
static Data *receiveData(SOCKET socket, long long expectedContentLength)
#else
static Data *receiveData(int socket, long long expectedContentLength)
#endif
{
    DebugLog2("receiveData(%d, %lld)\n", socket, expectedContentLength);

    Data *result = NULL;

    char *buf = NULL;
    if (socket >= 0)
    {
        static const int BUF_UNIT = 1024;
        long long unit_count = 0;

        long long offset = 0;
        while (true)
        {
            // バッファ確保
            if (offset == (unit_count * BUF_UNIT))
            {
                ++unit_count;
                char *newbuf = (char *)realloc(buf, (unit_count * BUF_UNIT));
                if (newbuf == NULL)
                {
                    if (buf != NULL)
                    {
                        free(buf);
                        buf = NULL;
                    }
                    break;
                }
                buf = newbuf;
            }

            // 読み込み
            int rdlen;
            if (expectedContentLength == URLResponseUnknownLength)
            {
                rdlen = BUF_UNIT;
            }
            else if ((expectedContentLength - offset) > BUF_UNIT)
            {
                rdlen = BUF_UNIT;
            }
            else
            {
                rdlen = (expectedContentLength - offset);
            }
            size_t len = recv(socket, &buf[offset], rdlen, 0);
            if (len == 0)
            {
                // 終端 or エラー
                break;
            }

            offset += len;

            if (expectedContentLength != URLResponseUnknownLength)
            {
                if (len != rdlen)
                {
                    // 終端 or エラー
                    break;
                }
                if (offset >= expectedContentLength)
                {
                    break;
                }
            }
        }
        if (buf != NULL)
        {
            result = Data::alloc()->initWithBytesAndLength(buf, offset);
            free(buf);
            if (result != NULL)
            {
                result->autorelease();
            }
        }
    }
    return result;
}

Data *URLConnection::sendSynchronousRequest(URLRequest *request, URLResponse **response, Error **error)
{
    DebugLog2("URLConnection::sendSynchronousRequest()\n");

    //
    // 現状は、更なるクラス群を調査する気力が無いので
    // socketで直接実装する
    // とりあえず linux socket で実装。あとでwinsockに対応
    //

    Data *result = NULL;
    if (request != NULL)
    {
        URL *url = request->url();
        if (url->scheme()->isEqualToString("http"))
        {
            // http のみ実装
            struct sockaddr_in dst_addr;
            dst_addr.sin_family = AF_INET;
            dst_addr.sin_port = htons(url->port());
            struct hostent *hostent = gethostbyname(url->host()->cString());
            if (hostent == NULL)
            {
                DebugLog3("error: gethostbyname().\n");
                return NULL;
            }
#ifdef _WIN32
            dst_addr.sin_addr.s_addr = *(unsigned int *)hostent->h_addr;
#else
            dst_addr.sin_addr.s_addr = *(uint32_t *)hostent->h_addr;
#endif

#ifdef _WIN32
//            WSADATA wsaData;
//            WSAStartup(MAKEWORD(2,0), &wsaData);
            SOCKET sock = INVALID_SOCKET;
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock == INVALID_SOCKET)
#else
            int sock;
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock == -1)
#endif
            {
                DebugLog3("error: socket()\n");
                return NULL;
            }

            if (connect(sock, (const struct sockaddr *)&dst_addr, sizeof(dst_addr)) != 0)
            {
                DebugLog3("error: connect()\n");
#ifdef _WIN32
                closesocket(sock);
                WSACleanup();
#else
                close(sock);
#endif
                return NULL;
            }

            String *reqmsg = String::string();

            // Request Line
            String *reqline = String::stringWithFormat("%s %s HTTP/1.1\r\n", request->HTTPMethod()->cString(), url->path()->cString());
            reqmsg = reqmsg->stringByAppendingString(reqline);

            // headers
            Dictionary *headers = request->allHTTPHeaderFields();
            Array *keys = headers->allKeys();
            for (UInteger i = 0; i < keys->count(); ++i)
            {
                String *key = (String *)keys->objectAtIndex(i);
                String *val = headers->stringForKey(key);
                String *tmp = String::stringWithFormat("%s: %s\r\n", key->cString(), val->cString());
                reqmsg = reqmsg->stringByAppendingString(tmp);
            }

            reqmsg = reqmsg->stringByAppendingString("\r\n");
            DebugLog3("requst:\n%s", reqmsg->cString());

#ifdef _WIN32
            if (send(sock, reqmsg->cString(), reqmsg->length(), 0) != reqmsg->length())
#else
            if (write(sock, reqmsg->cString(), reqmsg->length()) != reqmsg->length())
#endif
            {
                DebugLog3("error: write()\n");
#ifdef _WIN32
                closesocket(sock);
//                WSACleanup();
#else
                close(sock);
#endif
                return NULL;
            }

            DebugLog3("resp wait...\n");

            //
            HTTPURLResponse *resp = receiveHTTPURLResponse(sock, url);
            if (resp == NULL)
            {
                DebugLog3("error: receiveHTTPURLResponse()\n");
#ifdef _WIN32
                closesocket(sock);
                WSACleanup();
#else
                close(sock);
#endif
                return NULL;
            }

            if (resp->statusCode() == 200)
            {
                DebugLog3("data wait...\n");
                result = receiveData(sock, resp->expectedContentLength());
                if (result != NULL)
                {
                    DebugLog3("recv data length: %llu\n", result->length());
                    if (response != NULL)
                    {
                        *response = resp;
                    }
                }
            }

#ifdef _WIN32
            closesocket(sock);
            WSACleanup();
#else
            close(sock);
#endif
        }
        else
        {
            DebugLog0("not implemented.\n");
        }
    }
    return result;
}

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

} // Raym
