/**
 * Test program for PhysicsFS, using wxWidgets. May only work on Unix.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Ryan C. Gordon.
 */

#if ( (defined(__MACH__)) && (defined(__APPLE__)) )
#define PLATFORM_MACOSX 1
#include <Carbon/Carbon.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#include <wx/wx.h>
#include <wx/treectrl.h>

#include "physfs.h"

#define TEST_VER_MAJOR  2
#define TEST_VER_MINOR  0
#define TEST_VER_PATCH  3

//static PHYSFS_uint32 do_buffer_size = 0;

enum WxTestPhysfsMenuCommands
{
    // start with standard menu items, since using the wxIDs will map them
    //  to sane things in the platform's UI (gnome icons in GTK+, moves the
    //  about and quit items to the Apple menu on Mac OS X, etc).
    MENUCMD_About = wxID_ABOUT,
    MENUCMD_Quit = wxID_EXIT,

    // non-standard menu items go here.
    MENUCMD_Init = wxID_HIGHEST,
    MENUCMD_Deinit,
    MENUCMD_AddArchive,
    MENUCMD_Mount,
    MENUCMD_Remove,
    MENUCMD_GetCDs,
    MENUCMD_SetWriteDir,
    MENUCMD_PermitSymLinks,
    MENUCMD_SetSaneConfig,
    MENUCMD_MkDir,
    MENUCMD_Delete,
    MENUCMD_Cat,
    MENUCMD_SetBuffer,
    MENUCMD_StressBuffer,
    MENUCMD_Append,
    MENUCMD_Write,
    MENUCMD_GetLastError,

/*
    { "getdirsep",      cmd_getdirsep,      0, NULL                         },
    { "getsearchpath",  cmd_getsearchpath,  0, NULL                         },
    { "getbasedir",     cmd_getbasedir,     0, NULL                         },
    { "getuserdir",     cmd_getuserdir,     0, NULL                         },
    { "getwritedir",    cmd_getwritedir,    0, NULL                         },
    { "getrealdir",     cmd_getrealdir,     1, "<fileToFind>"               },
    { "exists",         cmd_exists,         1, "<fileToCheck>"              },
    { "isdir",          cmd_isdir,          1, "<fileToCheck>"              },
    { "issymlink",      cmd_issymlink,      1, "<fileToCheck>"              },
    { "filelength",     cmd_filelength,     1, "<fileToCheck>"              },
    { "getlastmodtime", cmd_getlastmodtime, 1, "<fileToExamine>"            },
*/
};


class WxTestPhysfsFrame : public wxFrame
{
public:
    WxTestPhysfsFrame(const wxChar *argv0);

    void rebuildTree();

    void onMenuInit(wxCommandEvent &evt);
    void onMenuDeinit(wxCommandEvent &evt);
    void onMenuAddArchive(wxCommandEvent &evt);
    void onMenuGetCDs(wxCommandEvent &evt);
    void onMenuPermitSymLinks(wxCommandEvent &evt);

private:
    wxTreeCtrl *fileTree;
    wxTreeItemId stateItem;
    wxTreeItemId fsItem;

    int err(int success);
    void fillFileSystemTree(const char *path, const wxTreeItemId &item);
    void doInit(const char *argv0);
    void doDeinit();

    DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(WxTestPhysfsFrame, wxFrame)
    EVT_MENU(MENUCMD_Init, WxTestPhysfsFrame::onMenuInit)
    EVT_MENU(MENUCMD_Deinit, WxTestPhysfsFrame::onMenuDeinit)
    EVT_MENU(MENUCMD_AddArchive, WxTestPhysfsFrame::onMenuAddArchive)
    EVT_MENU(MENUCMD_GetCDs, WxTestPhysfsFrame::onMenuGetCDs)
    EVT_MENU(MENUCMD_PermitSymLinks, WxTestPhysfsFrame::onMenuPermitSymLinks)
END_EVENT_TABLE()



// This is the the Application itself.
class WxTestPhysfsApp : public wxApp
{
public:
    WxTestPhysfsApp() : mainWindow(NULL) { /* no-op. */ }
    virtual bool OnInit();

private:
    WxTestPhysfsFrame *mainWindow;
};

DECLARE_APP(WxTestPhysfsApp)


static inline char *newstr(const char *str)
{
    char *retval = NULL;
    if (str != NULL)
    {
        retval = new char[strlen(str) + 1];
        strcpy(retval, str);
    } // if
    return retval;
} // newstr

static char *newutf8(const wxString &wxstr)
{
    #if wxUSE_UNICODE
    size_t len = wxstr.Len() + 1;
    char *utf8text = new char[len * 6];
    wxConvUTF8.WC2MB(utf8text, wxstr, len);
    return utf8text;
    #else
    return newstr(wxstr);
    #endif
} // newutf8


WxTestPhysfsFrame::WxTestPhysfsFrame(const wxChar *argv0)
    : wxFrame(NULL, -1, wxT("WxTestPhysfs"))
{
    this->CreateStatusBar();

    wxMenuBar *menuBar = new wxMenuBar;

    wxMenu *stuffMenu = new wxMenu;
    stuffMenu->Append(MENUCMD_Init, wxT("&Init"));
    stuffMenu->Append(MENUCMD_Deinit, wxT("&Deinit"));
    stuffMenu->Append(MENUCMD_AddArchive, wxT("&Add Archive"));
    stuffMenu->Append(MENUCMD_Mount, wxT("&Mount Archive"));
    stuffMenu->Append(MENUCMD_Remove, wxT("&Remove Archive"));
    stuffMenu->Append(MENUCMD_GetCDs, wxT("&Get CD-ROM drives"));
    stuffMenu->Append(MENUCMD_SetWriteDir, wxT("&Set Write Dir"));
    stuffMenu->Append(MENUCMD_SetSaneConfig, wxT("Set Sane &Config"));
    stuffMenu->Append(MENUCMD_MkDir, wxT("M&kDir"));
    stuffMenu->Append(MENUCMD_Delete, wxT("D&elete"));
    stuffMenu->Append(MENUCMD_Cat, wxT("&Cat"));
    stuffMenu->Append(MENUCMD_SetBuffer, wxT("Set &Buffer"));
    stuffMenu->Append(MENUCMD_StressBuffer, wxT("Stress &Test Buffer"));
    stuffMenu->Append(MENUCMD_Append, wxT("&Append"));
    stuffMenu->Append(MENUCMD_Write, wxT("&Write"));
    stuffMenu->Append(MENUCMD_Write, wxT("&Update getLastError"));
    stuffMenu->AppendCheckItem(MENUCMD_PermitSymLinks, wxT("&Permit symlinks"));
    menuBar->Append(stuffMenu, wxT("&Stuff"));

    //wxMenu *helpMenu = new wxMenu;
    //helpMenu->Append(MENUCMD_About, wxT("&About\tF1"));
    //menuBar->Append(helpMenu, wxT("&Help"));

    this->SetMenuBar(menuBar);

    this->fileTree = new wxTreeCtrl(this, -1);

    // The sizer just makes sure that fileTree owns whole client area.
    wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
    sizer->Add(this->fileTree, 1, wxALL | wxEXPAND | wxALIGN_CENTRE);
    sizer->SetItemMinSize(this->fileTree, 1, 1);
    this->SetSizer(sizer);

    char *utf8argv0 = newutf8(wxString(argv0));
    this->doInit(utf8argv0);
    delete[] utf8argv0;
} // WxTestPhysfsFrame::WxTestPhysfsFrame


int WxTestPhysfsFrame::err(int success)
{
    if (success)
        this->SetStatusText(wxT(""));
    else
        this->SetStatusText(wxString(PHYSFS_getLastError(), wxConvUTF8));
    return success;
} // WxTestPhysfsFrame::err


void WxTestPhysfsFrame::fillFileSystemTree(const char *path,
                                           const wxTreeItemId &item)
{
    char **rc = PHYSFS_enumerateFiles(path);
    char **i;
    wxTreeItemId id;

    if (rc == NULL)
    {
        const wxString quote(wxT("'"));
        wxString str(wxT("Enumeration error: "));
        str << quote << wxString(PHYSFS_getLastError(), wxConvUTF8) << quote;
        id = this->fileTree->AppendItem(item, str);
        this->fileTree->SetItemTextColour(id, wxColour(255, 0, 0));
    } // if
    else
    {
        for (i = rc; *i != NULL; i++)
        {
            id = this->fileTree->AppendItem(item, wxString(*i, wxConvUTF8));
            const int len = strlen(path) + strlen(*i) + 2;
            char *fname = new char[len];
            const char *origdir = path;
            if (strcmp(origdir, "/") == 0)
                origdir = "";
            snprintf(fname, len, "%s/%s", origdir, *i);

            if (PHYSFS_isDirectory(fname))
            {
                this->fileTree->SetItemTextColour(id, wxColour(0, 0, 255));
                this->fillFileSystemTree(fname, id);
            } // if

            else if (PHYSFS_isSymbolicLink(fname))
            {
                this->fileTree->SetItemTextColour(id, wxColour(0, 255, 0));
            } // else if

            else  // ...file.
            {
            } // else

            delete[] fname;
        } // for

        PHYSFS_freeList(rc);
    } // else
} // fillFileSystemTree


void WxTestPhysfsFrame::rebuildTree()
{
    const wxString dot(wxT("."));
    const wxString quote(wxT("'"));
    wxTreeItemId item;
    wxString str;
    const char *cstr = NULL;
    const bool wasInit = PHYSFS_isInit() ? true : false;

    this->fileTree->DeleteAllItems();
    wxTreeItemId root = this->fileTree->AddRoot(wxT("PhysicsFS"));
    this->stateItem = this->fileTree->AppendItem(root, wxT("Library state"));

    str = wxT("Initialized: ");
    str << ((wasInit) ? wxT("true") : wxT("false"));
    this->fileTree->AppendItem(this->stateItem, str);

    this->fileTree->Expand(this->stateItem);
    this->fileTree->Expand(root);

    // Fill in version information...

    PHYSFS_Version ver;
    item = this->stateItem;
    str = wxT("wxtest_physfs version: ");
    str << TEST_VER_MAJOR << dot << TEST_VER_MINOR << dot << TEST_VER_PATCH;
    this->fileTree->AppendItem(item, str);
    PHYSFS_VERSION(&ver);
    str = wxT("Compiled against PhysicsFS version: ");
    str << (int) ver.major << dot << (int) ver.minor << dot << ver.patch;
    this->fileTree->AppendItem(item, str);
    PHYSFS_getLinkedVersion(&ver);
    str = wxT("Linked against PhysicsFS version: ");
    str << (int) ver.major << dot << (int) ver.minor << dot << ver.patch;
    this->fileTree->AppendItem(item, str);

    if (!wasInit)
        return;   // nothing else to do before initialization...

    str = wxT("Symbolic links permitted: ");
    str << ((PHYSFS_symbolicLinksPermitted()) ? wxT("true") : wxT("false"));
    this->fileTree->AppendItem(this->stateItem, str);

    str = wxT("Native directory separator: ");
    str << quote << wxString(PHYSFS_getDirSeparator(), wxConvUTF8) << quote;
    this->fileTree->AppendItem(this->stateItem, str);

    // Fill in supported archives...

    item = this->fileTree->AppendItem(this->stateItem, wxT("Archivers"));
    const PHYSFS_ArchiveInfo **arcs = PHYSFS_supportedArchiveTypes();
    if (*arcs == NULL)
        this->fileTree->AppendItem(item, wxT("(none)"));
    else
    {
        const PHYSFS_ArchiveInfo **i;
        for (i = arcs; *i != NULL; i++)
        {
            const wxString ext((*i)->extension, wxConvUTF8);
            const wxString desc((*i)->description, wxConvUTF8);
            const wxString auth((*i)->author, wxConvUTF8);
            const wxString url((*i)->url, wxConvUTF8);
            wxTreeItemId arcitem = this->fileTree->AppendItem(item, ext);
            this->fileTree->AppendItem(arcitem, desc);
            this->fileTree->AppendItem(arcitem, auth);
            this->fileTree->AppendItem(arcitem, url);
        } // for
    } // else


    // Fill in the standard paths...

    item = this->fileTree->AppendItem(this->stateItem, wxT("Paths"));
    str = wxT("Base directory: ");
    str << quote << wxString(PHYSFS_getBaseDir(), wxConvUTF8) << quote;
    this->fileTree->AppendItem(item, str);
    str = wxT("User directory: ");
    str << quote << wxString(PHYSFS_getUserDir(), wxConvUTF8) << quote;
    this->fileTree->AppendItem(item, str);
    str = wxT("Write directory: ");
    if ((cstr = PHYSFS_getWriteDir()) == NULL)
        str << wxT("(NULL)");
    else
        str << quote << wxString(cstr ? cstr : "(NULL)", wxConvUTF8) << quote;
    this->fileTree->AppendItem(item, str);
    //str = wxT("Preference directory: ");
    //str << wxString(PHYSFS_getUserDir(), wxConvUTF8);
    //this->fileTree->AppendItem(item, str);

    // Fill in the CD-ROMs...

    item = this->fileTree->AppendItem(this->stateItem, wxT("CD-ROMs"));
    char **cds = PHYSFS_getCdRomDirs();
    if (cds == NULL)
    {
        str = wxT("Error: ");
        str << quote << wxString(PHYSFS_getLastError(), wxConvUTF8) << quote;
        wxTreeItemId id = this->fileTree->AppendItem(item, str);
        this->fileTree->SetItemTextColour(id, wxColour(255, 0, 0));
    } // if
    else
    {
        if (*cds == NULL)
            this->fileTree->AppendItem(item, wxT("(none)"));
        else
        {
            char **i;
            for (i = cds; *i != NULL; i++)
                this->fileTree->AppendItem(item, wxString(*i, wxConvUTF8));
        } // else
        PHYSFS_freeList(cds);
    } // else

    // Fill in search path...

    item = this->fileTree->AppendItem(this->stateItem, wxT("Search path"));
    char **sp = PHYSFS_getSearchPath();
    if (sp == NULL)
    {
        str = wxT("Error: ");
        str << quote << wxString(PHYSFS_getLastError(), wxConvUTF8) << quote;
        wxTreeItemId id = this->fileTree->AppendItem(item, str);
        this->fileTree->SetItemTextColour(id, wxColour(255, 0, 0));
    } // if
    else
    {
        if (*sp == NULL)
            this->fileTree->AppendItem(item, wxT("(none)"));
        else
        {
            char **i;
            for (i = sp; *i != NULL; i++)
                this->fileTree->AppendItem(item, wxString(*i, wxConvUTF8));
        } // else
        PHYSFS_freeList(sp);
    } // else

    // Now fill in the filesystem...

    this->fsItem = this->fileTree->AppendItem(root, wxT("Filesystem"));
    this->fillFileSystemTree("/", this->fsItem);
    this->fileTree->Expand(this->fsItem);
} // WxTestPhysfsFrame::rebuildTree


void WxTestPhysfsFrame::doInit(const char *argv0)
{
    if (!this->err(PHYSFS_init(argv0)))
        ::wxMessageBox(wxT("PHYSFS_init() failed!"), wxT("wxTestPhysfs"));
    this->rebuildTree();
} // WxTestPhysfsFrame::doInit


void WxTestPhysfsFrame::doDeinit()
{
    if (!this->err(PHYSFS_deinit()))
        ::wxMessageBox(wxT("PHYSFS_deinit() failed!"), wxT("wxTestPhysfs"));
    this->rebuildTree();
} // WxTestPhysfsFrame::doDeinit


void WxTestPhysfsFrame::onMenuInit(wxCommandEvent &evt)
{
    wxString argv0(wxGetApp().argv[0] == NULL ? wxT("") : wxGetApp().argv[0]);
    wxString str(wxGetTextFromUser(wxT("PHYSFS_init"),
                 wxT("argv[0]? (cancel for NULL)"), argv0));
    char *cstr = str.IsEmpty() ? NULL : newutf8(str);
    this->doInit(cstr);
    delete[] cstr;
} // WxTestPhysfsFrame::onMenuInit


void WxTestPhysfsFrame::onMenuDeinit(wxCommandEvent &evt)
{
    this->doDeinit();
} // WxTestPhysfsFrame::onMenuDeinit


void WxTestPhysfsFrame::onMenuAddArchive(wxCommandEvent &evt)
{
    wxString arc = wxFileSelector(wxT("Choose archive to add"));
    if (!arc.IsEmpty())
    {
        char *cstr = newutf8(arc);
        // !!! FIXME: add to start/end?
        if (!this->err(PHYSFS_addToSearchPath(cstr, 1)))
            ::wxMessageBox(wxT("PHYSFS_addToSearchPath() failed!"), wxT("wxTestPhysfs"));
        delete[] cstr;
        this->rebuildTree();
    } // if
} // WxTestPhysfsFrame::onMenuAddArchive


void WxTestPhysfsFrame::onMenuGetCDs(wxCommandEvent &evt)
{
    this->rebuildTree();  // This will call PHYSFS_getCdRomDirs()...
} // WxTestPhysfsFrame::onMenuGetCDs


void WxTestPhysfsFrame::onMenuPermitSymLinks(wxCommandEvent &evt)
{
    PHYSFS_permitSymbolicLinks(evt.IsChecked() ? 1 : 0);
    this->rebuildTree();
} // WxTestPhysfsFrame::onMenuPermitSymLinks



IMPLEMENT_APP(WxTestPhysfsApp)

bool WxTestPhysfsApp::OnInit()
{
    #if PLATFORM_MACOSX
    // This lets a stdio app become a GUI app. Otherwise, you won't get
    //  GUI events from the system and other things will fail to work.
    // Putting the app in an application bundle does the same thing.
    //  TransformProcessType() is a 10.3+ API. SetFrontProcess() is 10.0+.
    if (TransformProcessType != NULL)  // check it as a weak symbol.
    {
        ProcessSerialNumber psn = { 0, kCurrentProcess };
        TransformProcessType(&psn, kProcessTransformToForegroundApplication);
        SetFrontProcess(&psn);
    } // if
    #endif

    this->mainWindow = new WxTestPhysfsFrame(this->argv[0]);
    this->mainWindow->Show(true);
    SetTopWindow(this->mainWindow);
    return true;
} // WxTestPhysfsApp::OnInit

// end of wxtest_physfs.cpp ...

