/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

#include "nsIServiceManager.h"
#include "nsIComponentManager.h"
#include "nsIStreamConverterService.h"
#include "nsIStreamConverter.h"
#include "nsIRegistry.h"
#include "nsIFactory.h"
#include "nsIStringStream.h"
#include "nsIIOService.h"
#include "nsCOMPtr.h"
#include "nsNetUtil.h"

static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);

#include "nspr.h"


#define ASYNC_TEST // undefine this if you want to test sycnronous conversion.

/////////////////////////////////
// Event pump setup
/////////////////////////////////
#include "nsIEventQueueService.h"
#ifdef WIN32 
#include <windows.h>
#endif
static int gKeepRunning = 0;
static nsIEventQueue* gEventQ = nsnull;
/////////////////////////////////
// Event pump END
/////////////////////////////////


/////////////////////////////////
// Test converters include
/////////////////////////////////
#include "Converters.h"
#include "nsMultiMixedConv.h"

// CID setup
static NS_DEFINE_CID(kEventQueueServiceCID,      NS_EVENTQUEUESERVICE_CID);
static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
static NS_DEFINE_CID(kComponentManagerCID,       NS_COMPONENTMANAGER_CID);
static NS_DEFINE_CID(kRegistryCID,               NS_REGISTRY_CID);

////////////////////////////////////////////////////////////////////////
// EndListener - This listener is the final one in the chain. It
//   receives the fully converted data, although it doesn't do anything with
//   the data.
////////////////////////////////////////////////////////////////////////
class EndListener : public nsIStreamListener {
public:
    // nsISupports declaration
    NS_DECL_ISUPPORTS

    EndListener() {NS_INIT_ISUPPORTS();};

    // nsIStreamListener method
    NS_IMETHOD OnDataAvailable(nsIChannel *channel, nsISupports *ctxt, nsIInputStream *inStr, 
                               PRUint32 sourceOffset, PRUint32 count)
    {
        nsresult rv;
        PRUint32 read, len;
        rv = inStr->Available(&len);
        if (NS_FAILED(rv)) return rv;

        char *buffer = (char*)nsAllocator::Alloc(len + 1);
        if (!buffer) return NS_ERROR_OUT_OF_MEMORY;

        rv = inStr->Read(buffer, len, &read);
        buffer[len] = '\0';
        if (NS_SUCCEEDED(rv)) {
            printf("CONTEXT %p: Received %u bytes and the following data: \n %s\n\n", ctxt, read, buffer);
        }
        nsAllocator::Free(buffer);

        return NS_OK;
    }

    // nsIStreamObserver methods
    NS_IMETHOD OnStartRequest(nsIChannel *channel, nsISupports *ctxt) 
    {
        return NS_OK;
    }

    NS_IMETHOD OnStopRequest(nsIChannel *channel, nsISupports *ctxt, nsresult status, const PRUnichar *errorMsg)
    {
        return NS_OK;
    }
};

NS_IMPL_ISUPPORTS(EndListener, NS_GET_IID(nsIStreamListener));
////////////////////////////////////////////////////////////////////////
// EndListener END
////////////////////////////////////////////////////////////////////////


int
main(int argc, char* argv[])
{
    nsresult rv;

    // Create the Event Queue for this thread...
    NS_WITH_SERVICE(nsIEventQueueService, eventQService, kEventQueueServiceCID, &rv);
    if (NS_FAILED(rv)) return rv;

    rv = eventQService->CreateThreadEventQueue();
    if (NS_FAILED(rv)) return rv;

    eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, &gEventQ);

    rv = nsComponentManager::AutoRegister(nsIComponentManager::NS_Startup, NULL /* default */);
    if (NS_FAILED(rv)) return rv;

    ///////////////////////////////////////////
    // BEGIN - Stream converter registration
    //   All stream converters must register with the ComponentManager, _and_ make
    //   a registry entry.
    ///////////////////////////////////////////
    TestConverterFactory *convFactory = new TestConverterFactory(kTestConverterCID, "TestConverter", NS_ISTREAMCONVERTER_KEY);
    nsIFactory *convFactSup = nsnull;
    rv = convFactory->QueryInterface(NS_GET_IID(nsIFactory), (void**)&convFactSup);
    if (NS_FAILED(rv)) return rv;

    TestConverterFactory *convFactory1 = new TestConverterFactory(kTestConverter1CID, "TestConverter1", NS_ISTREAMCONVERTER_KEY);
    nsIFactory *convFactSup1 = nsnull;
    rv = convFactory1->QueryInterface(NS_GET_IID(nsIFactory), (void**)&convFactSup1);
    if (NS_FAILED(rv)) return rv;

    // multipart mixed converter registration
    nsIFactory *multiMixedFactSup = nsnull;
    rv = nsComponentManager::FindFactory(kMultiMixedConverterCID, &multiMixedFactSup);
    if (NS_FAILED(rv)) return rv;

    // register the TestConverter with the component manager. One progid registration
    // per conversion pair (from - to pair).
    rv = nsComponentManager::RegisterFactory(kTestConverterCID,
                                             "TestConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=a/foo?to=b/foo",
                                             convFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kTestConverter1CID,
                                             "TestConverter1",
                                             NS_ISTREAMCONVERTER_KEY "?from=b/foo?to=c/foo",
                                             convFactSup1,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kMultiMixedConverterCID,
                                             "MultiMixedConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=multipart/x-mixed-replace?to=text/html",
                                             multiMixedFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kTestConverterCID,
                                             "TestConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=b/foo?to=d/foo",
                                             convFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kTestConverterCID,
                                             "TestConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=c/foo?to=d/foo",
                                             convFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kTestConverterCID,
                                             "TestConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=d/foo?to=e/foo",
                                             convFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kTestConverterCID,
                                             "TestConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=d/foo?to=f/foo",
                                             convFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;

    rv = nsComponentManager::RegisterFactory(kTestConverterCID,
                                             "TestConverter",
                                             NS_ISTREAMCONVERTER_KEY "?from=t/foo?to=k/foo",
                                             convFactSup,
                                             PR_TRUE);
    if (NS_FAILED(rv)) return rv;
    
    // register the converters with the registry. One progid registration
    // per conversion pair.
    NS_WITH_SERVICE(nsIRegistry, registry, kRegistryCID, &rv);
    if (NS_FAILED(rv)) return rv;

    // open the registry
    rv = registry->OpenWellKnownRegistry(nsIRegistry::ApplicationComponentRegistry);
    if (NS_FAILED(rv)) return rv;

    // set the key
    nsRegistryKey key, key1;

    rv = registry->AddSubtree(nsIRegistry::Common, NS_ISTREAMCONVERTER_KEY, &key);
    if (NS_FAILED(rv)) return rv;
    rv = registry->AddSubtreeRaw(key, "?from=a/foo?to=b/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=b/foo?to=c/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=multipart/x-mixed-replace?to=text/html", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=b/foo?to=d/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=c/foo?to=d/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=d/foo?to=e/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=d/foo?to=f/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    rv = registry->AddSubtreeRaw(key, "?from=t/foo?to=k/foo", &key1);
    if (NS_FAILED(rv)) return rv;

    registry->Close();

    NS_WITH_SERVICE(nsIStreamConverterService, StreamConvService, kStreamConverterServiceCID, &rv);
    if (NS_FAILED(rv)) return rv;

    // use a dummy string as a stream to convert. This is the original from data.
    nsString2 dummyData("--thisrandomstring\nContent-type: text/html\n\n<p>Please stand by ... <p>\n");
    nsCOMPtr<nsISupports> inputDataSup;

    rv = NS_NewStringInputStream(getter_AddRefs(inputDataSup), dummyData);
    if (NS_FAILED(rv)) return rv;

    nsString2 fromStr("multipart/x-mixed-replace");
    nsString2 toStr  ("text/html");

    nsCOMPtr<nsIInputStream> inputData = do_QueryInterface(inputDataSup);

#ifdef ASYNC_TEST
    // ASYNCRONOUS conversion

    NS_WITH_SERVICE(nsIIOService, serv, kIOServiceCID, &rv);
    if (NS_FAILED(rv)) return rv;

    // we need a dummy channel for the async calls.
    nsCOMPtr<nsIChannel> dummyChannel;
    nsCOMPtr<nsIURI> dummyURI;
    rv = serv->NewURI("http://neverneverland.com", nsnull, getter_AddRefs(dummyURI));
    if (NS_FAILED(rv)) return rv;
    rv = NS_NewInputStreamChannel(dummyURI, "multipart/x-mixed-replacE;boundary=thisrandomstring",
                                  -1,        // XXX fix contentLength
                                  nsnull,    // inStr
                                  nsnull,    // loadGroup
                                  nsnull,    // notificationCallbacks
                                  nsIChannel::LOAD_NORMAL,
                                  nsnull,    // originalURI
                                  0, 0,
                                  getter_AddRefs(dummyChannel));
    if (NS_FAILED(rv)) return rv;


    // setup a listener to receive the converted data. This guy is the end
    // listener in the chain, he wants the fully converted (toType) data.
    // An example of this listener in mozilla would be the DocLoader.
    nsCOMPtr<nsIStreamListener> dataReceiver = new EndListener();

    // setup a listener to push the data into. This listener sits inbetween the
    // unconverted data of fromType, and the final listener in the chain (in this case
    // the dataReceiver.
    nsCOMPtr<nsIStreamListener> converterListener;
    rv = StreamConvService->AsyncConvertData(fromStr.GetUnicode(), toStr.GetUnicode(), dataReceiver, nsnull, getter_AddRefs(converterListener));
    if (NS_FAILED(rv)) return rv;

    // at this point we have a stream listener to push data to, and the one
    // that will receive the converted data. Let's mimic On*() calls and get the conversion
    // going. Typically these On*() calls would be made inside their respective wrappers On*()
    // methods.
    rv = converterListener->OnStartRequest(dummyChannel, nsnull);
    if (NS_FAILED(rv)) return rv;

    // For the OnDataAvailable() call, we'll simulate multiple (two) calls.
    rv = converterListener->OnDataAvailable(dummyChannel, nsnull, inputData, 0, -1);
    if (NS_FAILED(rv)) return rv;

    // And the second...
    nsString2 dummyData2("\n--thisrandomstring\nContent-type: text/html\n");
    nsCOMPtr<nsISupports> inputDataSup2;

    rv = NS_NewStringInputStream(getter_AddRefs(inputDataSup2), dummyData2);
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIInputStream> inputData2 = do_QueryInterface(inputDataSup2);

    rv = converterListener->OnDataAvailable(dummyChannel, nsnull, inputData2, 0, -1);
    if (NS_FAILED(rv)) return rv;

    // And the third...
    nsString2 dummyData3("Set-Cookie: BUGLIST=12012:1750:2023:11112:11948:13288:14960:17164:1485:1582:1596:4528:848:1043:2267:3692:9446:12122:3371:1219:1277:1896:4233:1044:1119:1177:1238:1378:1758:2079:2478:3457:5387:12451:17567:845:1036:1039:1040:1041:1045:1047:1048:1049:1050:1051:1052:1053:1055:1057:1058:1059:1060:1120:1122:1184:1206:1237:1274:1275:1276:1278:1279:1281:1300:1360:1580:1595:1605:1606:1759:1770:1781:1787:1807:1808:1812:1820:1834:1851:1863:1864:1985:2006:2007:2010:2011:2012:2013:2014:2015:2018:2019:2022:2025:2032:2033:2035:2037:2038:2044:2052:2056:2058:2059:2062:2064:2072:2109:2261:2285:2353:2354:2436:2441:2442:2452:2479:2501:2502:2525:2592:2765:2771:2842:2844:2867:2868:2925:2926:2942:2947:2948:2949:2950:2951:2952:2987:2990:2992:2993:3000:3027:3089:3116:3143:3152:3153:3160:3195:3221:3222:3240:3366:3454:3458:3460:3474:3486:4445:4515:4519:5849:6052:6403:8905:9740:9777:9778:9779:9781:9850:12272:12401:12906:2031:3088:850:1042:1046:1141:1414:3013:8044:15992:16934:17418:17519:3950:4580:5850:6518:8032:8088:9024:9236:9593:10176:10273:10296:14310:16586:16848:17645:4387:4426:6357:6519:8045:8071:8565:9013:9474:9738:10268:10269:10274:11960:12217:12398:13140:15315:16490:16585:16624:16636:16936:1038:1413:4042:4050:4092:4234:4510:4529:4572:4615:4831:4833:");
    dummyData3.Append("4834:5154:5194:5195:5272:5277:5406:5407:5469:5688:5693:5818:5886:6033:6046:6069:6071:6073:6125:6152:6166:6282:6404:6546:6732:6789:6901:6903:7588:7774:7833:7834:7835:8051:8468:8586:8595:8886:8893:8912:8914:9075:9076:9127:9136:9189:9191:9250:9271:9280:9285:9300:9301:9421:9439:9442:9479:9611:9690:9691:9696:9730:9741:9747:9885:9927:10013:10064:10085:10107:10109:10140:10169:10207:10209:10216:10292:10330:10403:10455:10485:10497:10509:10577:10580:10649:10712:10763:10831:11559:11565:11932:11936:12152:12154:12155:12193:12194:12231:12232:12233:12302:12304:12341:12348:12350:12385:12386:12403:12449:12450:12452:12453:12461:12462:12542:12750:12764:12765:12766:12945:12962:12997:12998:13034:13035:13131:13214:13219:13245:13281:12324:14290:13626:14169:14957:14959:14961:15439:15440:15441");
    dummyData3.Append(":15459:15649:15891:15904:15909:15931:15932:16200:16252:16256:16258:15945:16362:16377:16548:16555:16569:16584:16590:16591:16603:16605:16606:16609:16610:16625:16640:16651:16808:16813:16852:16876:16879:16881:16882:16888:16938:17010:17159:17161:17517:17522:17524:17586:17595:17598:17640:17648:17726:17861:17827:1301:2024:4832:5080:6882:10395:13625:15558:12339:12686:17594:3975:6782:9302:16376:16379:17612:16647:1054:10593:17799:1583:9689:1749:2055:8042\n\n test data --thisrandomstring--");
    nsCOMPtr<nsISupports> inputDataSup3;

    rv = NS_NewStringInputStream(getter_AddRefs(inputDataSup3), dummyData3);
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIInputStream> inputData3 = do_QueryInterface(inputDataSup3);

    rv = converterListener->OnDataAvailable(dummyChannel, nsnull, inputData3, 0, -1);
    if (NS_FAILED(rv)) return rv;

    rv = converterListener->OnStopRequest(dummyChannel, nsnull, rv, nsnull);
    if (NS_FAILED(rv)) return rv;


#else
    // SYNCRONOUS conversion
    nsIInputStream *convertedData = nsnull;
    rv = StreamConvService->Convert(inputData, fromStr.GetUnicode(), toStr.GetUnicode(), nsnull, &convertedData);
#endif

    NS_RELEASE(convFactSup);
    if (NS_FAILED(rv)) return rv;

    // Enter the message pump to allow the URL load to proceed.
    while ( gKeepRunning ) {
#ifdef WIN32
        MSG msg;

        if (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        } else {
            gKeepRunning = 0;
    }
#else
#ifdef XP_MAC
    /* Mac stuff is missing here! */
#else
    PLEvent *gEvent;
    rv = gEventQ->GetEvent(&gEvent);
    rv = gEventQ->HandleEvent(gEvent);
#endif /* XP_UNIX */
#endif /* !WIN32 */
    }

    return NS_ShutdownXPCOM(NULL);
}
