/*
 * Copyright (C) 2008 Kevin Ollivier <kevino@theolliviers.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "DumpRenderTree.h"

#include "LayoutTestController.h"
#include "WorkQueue.h"
#include "WorkQueueItem.h"

#include <JavaScriptCore/JavaScript.h>

#include <wx/wx.h>
#include "WebView.h"
#include "WebFrame.h"
#include "WebBrowserShell.h"

#include <wtf/Assertions.h>

#include <cassert>
#include <stdlib.h>
#include <string.h>
#include <time.h>

volatile bool done = true;
volatile bool notified = false;
static bool printSeparators = true;
static int dumpPixels;
static int dumpTree = 1;
time_t startTime; // to detect timeouts / failed tests

using namespace std;

FILE* logOutput;

RefPtr<LayoutTestController> gLayoutTestController;
static wxWebView* webView;
static wxTimer* idleTimer;

const unsigned timeOut = 10;
const unsigned maxViewHeight = 600;
const unsigned maxViewWidth = 800;

class LayoutWebViewEventHandler : public wxEvtHandler {

public:
    LayoutWebViewEventHandler(wxWebView* webView)
        : m_webView(webView)
    {
    }
    
    void bindEvents() 
    {
        m_webView->Connect(wxEVT_WEBVIEW_LOAD, wxWebViewLoadEventHandler(LayoutWebViewEventHandler::OnLoadEvent), NULL, this);
        m_webView->Connect(wxEVT_WEBVIEW_JS_ALERT, wxWebViewAlertEventHandler(LayoutWebViewEventHandler::OnAlertEvent), NULL, this);
        m_webView->Connect(wxEVT_WEBVIEW_JS_CONFIRM, wxWebViewConfirmEventHandler(LayoutWebViewEventHandler::OnConfirmEvent), NULL, this);
        m_webView->Connect(wxEVT_WEBVIEW_JS_PROMPT, wxWebViewPromptEventHandler(LayoutWebViewEventHandler::OnPromptEvent), NULL, this);
        m_webView->Connect(wxEVT_WEBVIEW_CONSOLE_MESSAGE, wxWebViewConsoleMessageEventHandler(LayoutWebViewEventHandler::OnConsoleMessageEvent), NULL, this);
        m_webView->Connect(wxEVT_WEBVIEW_RECEIVED_TITLE, wxWebViewReceivedTitleEventHandler(LayoutWebViewEventHandler::OnReceivedTitleEvent), NULL, this);
        m_webView->Connect(wxEVT_WEBVIEW_WINDOW_OBJECT_CLEARED, wxWebViewWindowObjectClearedEventHandler(LayoutWebViewEventHandler::OnWindowObjectClearedEvent), NULL, this);
    }
    
    void OnLoadEvent(wxWebViewLoadEvent& event) 
    {

        if (event.GetState() == wxWEBVIEW_LOAD_FAILED || event.GetState() == wxWEBVIEW_LOAD_STOPPED)
            done = true; 
        
        if (event.GetState() == wxWEBVIEW_LOAD_ONLOAD_HANDLED) {
            done = true;
            
            if (!gLayoutTestController->waitToDump() || notified) {
                dump();
            }
        }
    }
    
    void OnAlertEvent(wxWebViewAlertEvent& event)
    {
        fprintf(stdout, "ALERT: %S\n", event.GetMessage().c_str());
    }
    
    void OnConfirmEvent(wxWebViewConfirmEvent& event)
    {
        fprintf(stdout, "CONFIRM: %S\n", event.GetMessage().c_str());
        event.SetReturnCode(1);
    }
    
    void OnPromptEvent(wxWebViewPromptEvent& event)
    {
        fprintf(stdout, "PROMPT: %S, default text: %S\n", event.GetMessage().c_str(), event.GetResponse().c_str());
        event.SetReturnCode(1);
    }
    
    void OnConsoleMessageEvent(wxWebViewConsoleMessageEvent& event)
    {
        fprintf(stdout, "CONSOLE MESSAGE: line %d: %S\n", event.GetLineNumber(), event.GetMessage().c_str());
    }
    
    void OnReceivedTitleEvent(wxWebViewReceivedTitleEvent& event)
    {
        if (gLayoutTestController->dumpTitleChanges() && !done) {
            const char* title = event.GetTitle().mb_str(wxConvUTF8);
            printf("TITLE CHANGED: %S\n", title ? title : "");
        }
    }
    
    void OnWindowObjectClearedEvent(wxWebViewWindowObjectClearedEvent& event)
    {
        JSValueRef exception = 0;
        gLayoutTestController->makeWindowObject(event.GetJSContext(), event.GetWindowObject(), &exception);
    }
    
private:
    wxWebView* m_webView;

};

void notifyDoneFired() 
{
    notified = true;
    if (done)
        dump();
}

LayoutWebViewEventHandler* eventHandler = NULL;

static wxString dumpFramesAsText(wxWebFrame* frame)
{
    // TODO: implement this. leaving this here so we don't forget this case.
    if (gLayoutTestController->dumpChildFramesAsText()) {
    }
    
    return frame->GetInnerText();
}

void dump()
{    
    if (!done)
        return;
    
    if (gLayoutTestController->waitToDump() && !notified)
        return;
        
    if (dumpTree) {
        const char* result = 0;

        bool dumpAsText = gLayoutTestController->dumpAsText();
        wxString str;
        if (gLayoutTestController->dumpAsText())
            str = dumpFramesAsText(webView->GetMainFrame());
        else 
            str = webView->GetMainFrame()->GetExternalRepresentation();

        result = str.ToUTF8();
        if (!result) {
            const char* errorMessage;
            if (gLayoutTestController->dumpAsText())
                errorMessage = "WebFrame::GetInnerText";
            else
                errorMessage = "WebFrame::GetExternalRepresentation";
            printf("ERROR: NULL result from %s", errorMessage);
        } else {
            printf("%s\n", result);
        }

        if (gLayoutTestController->dumpBackForwardList()) {
            // FIXME: not implemented
        }

        if (printSeparators) {
            puts("#EOF");
            fputs("#EOF\n", stderr);
            fflush(stdout);
            fflush(stderr);
        }
    }

    if (dumpPixels
        && gLayoutTestController->generatePixelResults()
        && !gLayoutTestController->dumpDOMAsWebArchive()
        && !gLayoutTestController->dumpSourceAsWebArchive()) {
        // FIXME: Add support for dumping pixels
        fflush(stdout);
    }

    puts("#EOF");
    fflush(stdout);
    fflush(stderr);

    gLayoutTestController.clear();
}

static void runTest(const wxString testPathOrURL)
{
    done = false;
    time(&startTime);
    string pathOrURLString(testPathOrURL.char_str());
    string pathOrURL(pathOrURLString);
    string expectedPixelHash;

    size_t separatorPos = pathOrURL.find("'");
    if (separatorPos != string::npos) {
        pathOrURL = string(pathOrURLString, 0, separatorPos);
        expectedPixelHash = string(pathOrURLString, separatorPos + 1);
    }
    
    // CURL isn't happy if we don't have a protocol.
    size_t http = pathOrURL.find("http://");
    if (http == string::npos)
        pathOrURL.insert(0, "file://");
    
    gLayoutTestController = LayoutTestController::create(pathOrURL, expectedPixelHash);
    if (!gLayoutTestController) {
        wxTheApp->ExitMainLoop();
    }

    WorkQueue::shared()->clear();
    WorkQueue::shared()->setFrozen(false);

    webView->LoadURL(wxString(pathOrURL.c_str(), wxConvUTF8));
    
    // wait until load completes and the results are dumped
    while (!done)
        wxSafeYield();
}

class MyApp : public wxApp
{
public:

    virtual bool OnInit();
    
private:
    wxLog* logger;
};


IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
    logOutput = fopen("output.txt", "ab");
    if (logOutput) {
        logger = new wxLogStderr(logOutput);
        wxLog::SetActiveTarget(logger);
    }

    wxLogMessage(wxT("Starting DumpRenderTool, %d args.\n"), argc);

    for (int i = 1; i < argc; ++i) {
        wxString option = wxString(argv[i]);
        if (!option.CmpNoCase(_T("--notree"))) {
            dumpTree = false;
            continue;
        }
        
        if (!option.CmpNoCase(_T("--pixel-tests"))) {
            dumpPixels = true;
            continue;
        }
        
        if (!option.CmpNoCase(_T("--tree"))) {
            dumpTree = true;
            continue;
        }
    }
    wxInitAllImageHandlers();
        
    // create the main application window
    wxWebBrowserShell* webFrame = new wxWebBrowserShell(_T("wxWebKit DumpRenderTree App"));
    SetTopWindow(webFrame);
    webView = webFrame->webview;
    webView->SetSize(wxSize(maxViewWidth, maxViewHeight));
    
    if (!eventHandler) {
        eventHandler = new LayoutWebViewEventHandler(webView);
        eventHandler->bindEvents();
    }

    int optind = 1;
    time(&startTime);
    wxString option_str = wxString(argv[optind]);
    if (argc == optind+1 && option_str.Find(_T("-")) == 0) {
        char filenameBuffer[2048];
        while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
            wxString filename = wxString::FromUTF8(filenameBuffer);
            char* newLineCharacter = strchr(filenameBuffer, '\n');
            if (newLineCharacter)
                *newLineCharacter = '\0';

            if (strlen(filenameBuffer) == 0)
                return 0;
            wxLogMessage(wxT("Running test %S.\n"), filenameBuffer);
            runTest(filename);
        }
    
    } else {
        printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
        for (int i = optind; i != argc; ++i) {
            runTest(wxTheApp->argv[1]);
        }
    }
    
    webFrame->Close();
    delete eventHandler;

    wxLog::SetActiveTarget(NULL);
    delete logger;
    fclose(logOutput);
    
    // returning false shuts the app down
    return false;
}
