/**
 * @file lliohttpserver_tut.cpp
 * @date   May 2006
 * @brief HTTP server unit tests
 *
 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * 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;
 * version 2.1 of the License only.
 *
 * 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
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "lltut.h"
#include "llbufferstream.h"
#include "lliohttpserver.h"
#include "llsdhttpserver.h"
#include "llsdserialize.h"
#include "llcommon.h"

#include "llpipeutil.h"


namespace tut
{
    class HTTPServiceTestData
    {
    public:
        class DelayedEcho : public LLHTTPNode
        {
            HTTPServiceTestData* mTester;

        public:
            DelayedEcho(HTTPServiceTestData* tester) : mTester(tester) { }

            void post(ResponsePtr response, const LLSD& context, const LLSD& input) const
            {
                ensure("response already set", mTester->mResponse == ResponsePtr(NULL));
                mTester->mResponse = response;
                mTester->mResult = input;
            }
        };

        class WireHello : public LLIOPipe
        {
        protected:
            virtual EStatus process_impl(
                const LLChannelDescriptors& channels,
                buffer_ptr_t& buffer,
                bool& eos,
                LLSD& context,
                LLPumpIO* pump)
            {
                if(!eos) return STATUS_BREAK;
                LLSD sd = "yo!";
                LLBufferStream ostr(channels, buffer.get());
                ostr << LLSDXMLStreamer(sd);
                return STATUS_DONE;
            }
        };

        HTTPServiceTestData()
            : mResponse(NULL)
        {
            LLHTTPStandardServices::useServices();
            LLHTTPRegistrar::buildAllServices(mRoot);
            mRoot.addNode("/delayed/echo", new DelayedEcho(this));
            mRoot.addNode("/wire/hello", new LLHTTPNodeForPipe<WireHello>);
        }

        ~HTTPServiceTestData()
        {
        }

        LLHTTPNode mRoot;
        LLHTTPNode::ResponsePtr mResponse;
        LLSD mResult;

        void pumpPipe(LLPumpIO* pump, S32 iterations)
        {
            while(iterations > 0)
            {
                pump->pump();
                pump->callback();
                --iterations;
            }
        }

        std::string makeRequest(
            const std::string& name,
            const std::string& httpRequest,
            bool timeout = false)
        {
            LLPipeStringInjector* injector = new LLPipeStringInjector(httpRequest);
            LLPipeStringExtractor* extractor = new LLPipeStringExtractor();

            apr_pool_t* pool;
            apr_pool_create(&pool, NULL);

            LLPumpIO* pump;
            pump = new LLPumpIO(pool);

            LLPumpIO::chain_t chain;
            LLSD context;

            chain.push_back(LLIOPipe::ptr_t(injector));
            LLIOHTTPServer::createPipe(chain, mRoot, LLSD());
            chain.push_back(LLIOPipe::ptr_t(extractor));

            pump->addChain(chain, DEFAULT_CHAIN_EXPIRY_SECS);

            pumpPipe(pump, 10);
            if(mResponse.notNull() && (! timeout))
            {
                mResponse->result(mResult);
                mResponse = NULL;
            }
            pumpPipe(pump, 10);

            std::string httpResult = extractor->string();

            chain.clear();
            delete pump;
            apr_pool_destroy(pool);

            if(mResponse.notNull() && timeout)
            {
                mResponse->result(mResult);
                mResponse = NULL;
            }

            return httpResult;
        }

        std::string httpGET(const std::string& uri,
                            bool timeout = false)
        {
            std::string httpRequest = "GET " + uri + " HTTP/1.0\r\n\r\n";
            return makeRequest(uri, httpRequest, timeout);
        }

        std::string httpPOST(const std::string& uri,
            const std::string& body,
            bool timeout,
            const std::string& evilExtra = "")
        {
            std::ostringstream httpRequest;
            httpRequest << "POST " + uri + " HTTP/1.0\r\n";
            httpRequest << "Content-Length: " << body.size() << "\r\n";
            httpRequest << "\r\n";
            httpRequest << body;
            httpRequest << evilExtra;

            return makeRequest(uri, httpRequest.str(), timeout);
        }

        std::string httpPOST(const std::string& uri,
            const std::string& body,
            const std::string& evilExtra = "")
        {
            bool timeout = false;
            return httpPOST(uri, body, timeout, evilExtra);
        }
    };

    typedef test_group<HTTPServiceTestData>     HTTPServiceTestGroup;
    typedef HTTPServiceTestGroup::object        HTTPServiceTestObject;
    HTTPServiceTestGroup httpServiceTestGroup("http service");

    template<> template<>
    void HTTPServiceTestObject::test<1>()
    {
        std::string result = httpGET("web/hello");

        ensure_starts_with("web/hello status", result,
            "HTTP/1.0 200 OK\r\n");

        ensure_contains("web/hello content type", result,
            "Content-Type: application/llsd+xml\r\n");

        ensure_contains("web/hello content length", result,
            "Content-Length: 36\r\n");

        ensure_contains("web/hello content", result,
            "\r\n"
            "<llsd><string>hello</string></llsd>"
            );
    }

    template<> template<>
    void HTTPServiceTestObject::test<2>()
    {
        // test various HTTP errors

        std::string actual;

        actual = httpGET("web/missing");
        ensure_starts_with("web/missing 404", actual,
            "HTTP/1.0 404 Not Found\r\n");

        actual = httpGET("web/echo");
        ensure_starts_with("web/echo 405", actual,
            "HTTP/1.0 405 Method Not Allowed\r\n");
    }

    template<> template<>
    void HTTPServiceTestObject::test<3>()
    {
        // test POST & content-length handling

        std::string result;

        result = httpPOST("web/echo",
            "<llsd><integer>42</integer></llsd>");

        ensure_starts_with("web/echo status", result,
            "HTTP/1.0 200 OK\r\n");

        ensure_contains("web/echo content type", result,
            "Content-Type: application/llsd+xml\r\n");

        ensure_contains("web/echo content length", result,
            "Content-Length: 35\r\n");

        ensure_contains("web/hello content", result,
            "\r\n"
            "<llsd><integer>42</integer></llsd>"
            );

/* TO DO: this test doesn't pass!!

        result = httpPOST("web/echo",
            "<llsd><string>evil</string></llsd>",
            "really!  evil!!!");

        ensure_equals("web/echo evil result", result,
            "HTTP/1.0 200 OK\r\n"
            "Content-Length: 34\r\n"
            "\r\n"
            "<llsd><string>evil</string></llsd>"
            );
*/
    }

    template<> template<>
    void HTTPServiceTestObject::test<4>()
    {
        // test calling things based on pipes

        std::string result;

        result = httpGET("wire/hello");

        ensure_contains("wire/hello", result, "yo!");
    }

    template<> template<>
    void HTTPServiceTestObject::test<5>()
    {
        // test timeout before async response
        std::string result;

        bool timeout = true;
        result = httpPOST("delayed/echo",
            "<llsd><string>agent99</string></llsd>", timeout);

        ensure_equals("timeout delayed/echo status", result, std::string(""));
    }

    template<> template<>
    void HTTPServiceTestObject::test<6>()
    {
        // test delayed service
        std::string result;

        result = httpPOST("delayed/echo",
            "<llsd><string>agent99</string></llsd>");

        ensure_starts_with("delayed/echo status", result,
            "HTTP/1.0 200 OK\r\n");

        ensure_contains("delayed/echo content", result,
            "\r\n"
            "<llsd><string>agent99</string></llsd>"
            );
    }

    template<> template<>
    void HTTPServiceTestObject::test<7>()
    {
        // test large request
        std::stringstream stream;

        //U32 size = 36 * 1024 * 1024;
        //U32 size = 36 * 1024;
        //std::vector<char> data(size);
        //memset(&(data[0]), '1', size);
        //data[size - 1] = '\0';


        //std::string result = httpPOST("web/echo", &(data[0]));

        stream << "<llsd><array>";
        for(U32 i = 0; i < 1000000; ++i)
        {
            stream << "<integer>42</integer>";
        }
        stream << "</array></llsd>";
        LL_INFOS() << "HTTPServiceTestObject::test<7>"
                << stream.str().length() << LL_ENDL;
        std::string result = httpPOST("web/echo", stream.str());
        ensure_starts_with("large echo status", result, "HTTP/1.0 200 OK\r\n");
    }

    template<> template<>
    void HTTPServiceTestObject::test<8>()
    {
        // test the OPTIONS http method -- the default implementation
        // should return the X-Documentation-URL
        std::ostringstream http_request;
        http_request << "OPTIONS /  HTTP/1.0\r\nHost: localhost\r\n\r\n";
        bool timeout = false;
        std::string result = makeRequest("/", http_request.str(), timeout);
        ensure_starts_with("OPTIONS verb ok", result, "HTTP/1.0 200 OK\r\n");
        ensure_contains(
            "Doc url header exists",
            result,
            "X-Documentation-URL: http://localhost");
    }


    /* TO DO:
        test generation of not found and method not allowed errors
    */
}