#include <common.h>
#include <CGI/ServiceFCGI.h>
#include <FCGI/RecordReader.h>
#include <FCGI/MapOfSocketHandler.h>
#include <FCGI/limits.h>
#include <sys/epoll.h>

#define ON_ERRNO() recordError(__LINE__,errno)
#define ON_ERROR(S) recordError(__LINE__,(S))

static void recordError(int iLine,int iError) {
    TRACE_PRINT(("*" __FILE__ "(%d) - ERROR(%d) %s",iLine,iError,::strerror(iError)));
}

static void recordError(int iLine,const char* sError) {
    TRACE_PRINT(("*" __FILE__ "(%d) - ERROR %s",iLine,sError));
}

enum {
    MAX_EVENTS = 20, MAX_LISTEN_BACKLOG = 100
};

SOCKET CGI::ServiceFCGI::attachListener(char* sSpec) {
    if (!sSpec) {
        // A usual case when started by the web server as a FastCGI backend.
        SOCKET socket = STDIN_FILENO;
        if (!makeNonBlocking(socket)) {
            ON_ERRNO();
            return false;
        }
        if (0 != ::listen(socket,MAX_LISTEN_BACKLOG)) {
            ON_ERRNO();
            return false;
        }
        return true;
    }

    char* p = ::strchr(sSpec,':');
    const char* sAddress = "127.0.0.1";
    const char* sPort = "4012"; // 0x0FAC ~ FAstCgi
    if (!p) {
        sAddress = sSpec;
        sPort = "";
    } else if (sSpec < p) {
        sAddress = sSpec;
        *p++ = 0;
        sPort = p;
    } else {
        sPort = sSpec + 1;
    }

    // Address might be an INET address.
    struct addrinfo hints;
    memset(&hints,0,sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;
    struct addrinfo* pList = 0;
    int v = getaddrinfo(sAddress,sPort,&hints,&pList);
    if (0 != v) {
        ON_ERROR(gai_strerror(v));
        return false;
    }
    ON_ERROR("Cannot connect listener!");
    struct addrinfo* pInfo = pList;
    for (; pInfo; pInfo = pInfo->ai_next) {
        SOCKET socket = ::socket(pInfo->ai_family,pInfo->ai_socktype,pInfo->ai_protocol);
        if (socket < 0) {
            ON_ERRNO();
            continue;
        }
        if (!makeNonBlocking(socket)) {
            ON_ERRNO();
            continue;
        }

        // Set socket option so we can reuse the port on server crash.
        // This may fail for non-TCP/IP sockets, so ignore errors.
        int v = 1;
        setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&v,sizeof(v));

        if (0 != ::bind(socket,pInfo->ai_addr,pInfo->ai_addrlen)) {
            ON_ERRNO();
            close(socket);
            continue;
        }
        if (0 != ::listen(socket,MAX_LISTEN_BACKLOG)) {
            ON_ERRNO();
            close(socket);
            continue;
        }
        break;
    }
    freeaddrinfo(pList);
    return !!pInfo;
}

bool CGI::ServiceFCGI::initialize() {
    bPollContinue = true;
    dtPollTimeout = 3000;

    // Verify the wanted FastCGI role is something we can handle.
    const char* sRole = ::getenv("FCGI_ROLE");
    if (!sRole) {
        ON_ERROR("FCGI_ROLE value not specified.");
        return false;
    }
    if (0 != ::strcasecmp(sRole,"RESPONDER")) {
        // We do not handle the other roles as yet.
        ON_ERROR("FCGI_ROLE value not supported.");
        return false;
    }

    // Should do something with this.
    // const char* sWebServerAddresses = ::getenv("FCGI_WEB_SERVER_ADDRS");

    char* sListener = ::getenv("FCGI_LISTENER");
    SOCKET socket = attachListener(sListener);
    if (socket < 0) {
        return false;
    }

    // Allocate I/O event notification handle.
    hPoll = epoll_create1(EPOLL_CLOEXEC);
    if (hPoll < 0) {
        ON_ERRNO();
        return false;
    }

    // Watch for incoming connections on socketListener.
    {
        struct epoll_event ev;
        ev.events = EPOLLIN;
        AcceptHandler* handler = mapOfHandlers->makeAcceptHandler(socket,hPoll);

        ev.data.u64 = handler->iToken;
        // Direct mapping would be more efficient, but not safe until throughly debugged.
        // ev.data.ptr = mapOfHandlers[0] = new AcceptHandler(hPoll,socket);
        if (epoll_ctl(hPoll,EPOLL_CTL_ADD,socket,&ev)) {
            ON_ERRNO();
            return false;
        }
    }

    return true;
}

static void onTimeout() {
    TRACE_FN(CGI::ServiceFCGI::onTimeout);
    Trace::rolloverLog();
    // TODO
}

int CGI::ServiceFCGI::dispatch() {
    TRACE_FN(CGI::ServiceFCGI::dispatch);
    struct epoll_event arrayEvents[MAX_EVENTS];
    while (bPollContinue) {
        int nEvents = epoll_wait(hPoll,arrayEvents,MAX_EVENTS,dtPollTimeout);
        if (nEvents < 0) {
            ON_ERRNO();
            return 1;
        }
        if (0 == nEvents) {
            onTimeout();
            continue;
        }
        for (int i = 0; i < nEvents; ++i) {
            struct epoll_event* event = arrayEvents + i;
            int iToken = event->data.u32;
            SocketHandler* handler = mapOfHandlers[(FCGI::MAX_CONNECTIONS_DOMAIN - 1) & iToken];
            if (iToken == handler->iToken) {
                if (EPOLLIN & event->events) {
                    handler->onRead();
                }
                if (EPOLLOUT & event->events) {
                    handler->onWrite();
                }
            }
        }
    }
    return 0;
}

void CGI::ServiceFCGI::cleanup() {
    close(hPoll);
    // TODO
}