/**********
 * Copyright (c) 2003-2004 Greg Parker.  All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY GREG PARKER ``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 THE AUTHOR 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 "includes.h"
#include "ssh/ssh.h"
#include "vt100/vt100.h"
#include "rsrc/rsrc.h"
#include "crypto/rand.h"
#include "armstubs.h"
#include "SysZLib.h"

#include "data/connectionlist.h"
#include "data/hostkeys.h"
#include "data/memolist.h"
#include "data/prefs.h"
#include "data/publickeys.h"

#include "forms/about.h"
#include "forms/connectionprefsform.h"
#include "forms/detailsform.h"
#include "forms/displayprefsform.h"
#include "forms/hostkeysform.h"
#include "forms/hostkeydetailsform.h"
#include "forms/keyboardprefsform.h"
#include "forms/loginform.h"
#include "forms/mainform.h"
#include "forms/memoform.h"
#include "forms/passwordform.h"
#include "forms/passphraseform.h"
#include "forms/publickeysform.h"
#include "forms/publickeychoiceform.h"
#include "forms/publickeydetailsform.h"
#include "forms/terminalform.h"
#include "forms/CollapseUtils.h"

#include "crypto/openssl/bn/bn.h"
#include "crypto/openssl/md5/md5.h"

Err errno;



UInt16 NetLibCount = 0;
struct ssh_session_t *ss = NULL;


// fixme move to recordlist
DmOpenRef OpenDB(UInt32 type, char *name, Boolean resDB, Boolean create)
{
    DmOpenRef result;
    Err err;

    result = DmOpenDatabaseByTypeCreator(type, PSSH_CREATOR, dmModeReadWrite);
    if (!result  &&  create) {
        UInt16 card;
        LocalID id;
        UInt16 attr;

        err = DmCreateDatabase(0, name, PSSH_CREATOR, type, resDB);
        if (err) { complain_int("db 1", err); return 0; }
        result = DmOpenDatabaseByTypeCreator(type, PSSH_CREATOR, dmModeReadWrite);
        if (!result) { complain_int("db 2", DmGetLastErr()); return 0; }

        // Set backup bit
        DmOpenDatabaseInfo(result, &id, NULL, NULL, &card, NULL);
        DmDatabaseInfo(card, id, NULL, &attr, NULL, NULL, NULL, 
                       NULL, NULL, NULL, NULL, NULL, NULL);
        attr |= dmHdrAttrBackup;
        DmSetDatabaseInfo(card, id, NULL, &attr, NULL, NULL, NULL, 
                          NULL, NULL, NULL, NULL, NULL, NULL);
    }

    return result;
}





void complain(char *err)
{
    char errnobuf[20];
    WinPushDrawState();
    WinSetCoordinateSystem(kCoordinatesStandard);
    FrmCustomAlert(AlertFormID, err, StrIToA(errnobuf, errno), " ");
    WinPopDrawState();
}

void complain_int(char *err, uint32_t i) 
{
    char intbuf[20];
    WinPushDrawState();
    WinSetCoordinateSystem(kCoordinatesStandard);
    FrmCustomAlert(AlertFormID, err, StrIToA(intbuf, i), " ");
    WinPopDrawState();
}

void assert_failed(uint32_t which)
{
    char intbuf[20];
    FrmCustomAlert(AlertFormID, "arm assert failed!", StrIToA(intbuf, which), " ");
}



extern Boolean ApplicationHandleEvent(EventPtr e); /* NO segment, used in about.c */

Boolean ApplicationHandleEvent(EventPtr e)
{
    if (e->eType == frmLoadEvent) {
        UInt16 formId = e->data.frmLoad.formID;
        FormPtr frmP = FrmInitForm(formId);
        FrmSetActiveForm(frmP);
        switch(formId) {
        case MainFormID:
            FrmSetEventHandler(frmP, MainFormHandleEvent);
            break;
        case DetailsFormID:
            FrmSetEventHandler(frmP, DetailsFormHandleEvent);
            break;
        case LoginFormID:
            FrmSetEventHandler(frmP, LoginFormHandleEvent);
            break;
        case PasswordFormID:
            FrmSetEventHandler(frmP, PasswordFormHandleEvent);
            break;
        case TerminalFormID:
            FrmSetEventHandler(frmP, TerminalFormHandleEvent);
            break;
        case HostKeysFormID:
            FrmSetEventHandler(frmP, HostKeysFormHandleEvent);
            break;
        case HostKeyDetailsFormID:
            FrmSetEventHandler(frmP, HostKeyDetailsFormHandleEvent);
            break;
        case AboutFormID:
            FrmSetEventHandler(frmP, AboutFormHandleEvent);
            break;
        case CreditsFormID:
            FrmSetEventHandler(frmP, CreditsFormHandleEvent);
            break;
        case DisplayPrefsFormID:
            FrmSetEventHandler(frmP, DisplayPrefsFormHandleEvent);
            break;
        case ConnectionPrefsFormID:
            FrmSetEventHandler(frmP, ConnectionPrefsFormHandleEvent);
            break;
        case KeyboardPrefsFormID:
            FrmSetEventHandler(frmP, KeyboardPrefsFormHandleEvent);
            break;
        case MemoFormID:
            FrmSetEventHandler(frmP, MemoFormHandleEvent);
            break;
        case PublicKeysFormID:
            FrmSetEventHandler(frmP, PublicKeysFormHandleEvent);
            break;
        case PublicKeyDetailsFormID:
            FrmSetEventHandler(frmP, PublicKeyDetailsFormHandleEvent);
            break;
        case PassphraseFormID:
            FrmSetEventHandler(frmP, PasswordFormHandleEvent);
            break;
        case PublicKeyChoiceFormID:
            FrmSetEventHandler(frmP, PublicKeyChoiceFormHandleEvent);
            break;
        case BannerFormID:
            FrmSetEventHandler(frmP, BannerFormHandleEvent);
            break;
        default:
            break;
        }
        return true;
    }

    return false;
}


static Boolean RomVersionCompatible (UInt32 requiredVersion)
{
    UInt32 romVersion;
    
    // See if we're on in minimum required version of the ROM or later.
    // The system records the version number in a feature.  A feature is a
    // piece of information which can be looked up by a creator and feature
    // number.
    FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
    return (romVersion >= requiredVersion);
}


static Boolean CPUIsARM(void)
{
    UInt32 cpu;
    FtrGet(sysFtrCreator, sysFtrNumProcessorID, &cpu);
    return sysFtrNumProcessorIsARM(cpu);
}

static void StartupError(char *msg, UInt16 launchFlags)
{
    UInt32 romVersion;
    FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);

    // If the user launched the app from the launcher, explain
    // why the app shouldn't run.  If the app was contacted for something
    // else, like it was asked to find a string by the system find, then
    // don't bother the user with a warning dialog.  These flags tell how
    // the app was launched to decided if a warning should be displayed.
    if ((launchFlags & (sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp)) ==
        (sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp))
    {
        complain(msg);
        
        // Pilot 1.0 will continuously relaunch this app unless we switch to 
        // another safe one.  The sysFileCDefaultApp is considered "safe".
        if (romVersion < 0x02000000)
        {
            AppLaunchWithCommand(sysFileCDefaultApp, 
                                 sysAppLaunchCmdNormalLaunch, NULL);
        }
    }
}


static Boolean NetLibAvailable(void) 
{
    Err err;
    UInt32 version;
    
    err = FtrGet(netFtrCreator, netFtrNumVersion, &version);
    if (err) return false;
    // any NetLib version is OK

    err = SysLibFind("Net.lib", &AppNetRefnum);
    if (err) return false;
    
    return true;
}


UInt32 PilotMain(UInt16 cmd, void *cmdPBP, UInt16 launchFlags)
{
    int loopcount = 0;
    UInt16 version;
    int i;
    UInt32 tenth = SysTicksPerSecond() / 10;
    UInt32 lastUpdate = 0;
    UInt32 now;

    // we don't handle search et al.
    if (cmd != sysAppLaunchCmdNormalLaunch) return 0;

    // ARM CPU required
    // fixme unless better bnlib is written
    if (!CPUIsARM()) {
        StartupError("pssh requires an ARM processor", launchFlags);
        return 0;
    }

    // ROM version 3.5+ required
    // fixme this should always pass given ARM
    if (!RomVersionCompatible(sysMakeROMVersion(3,5,0,0,0))) {
        StartupError("pssh requires ROM version 3.5 or later", launchFlags);
        return 0;
    }

    // NetLib required (any version)
    if (!NetLibAvailable()) {
        StartupError("pssh requires NetLib", launchFlags);
        return 0;
    }

    if (!ConnectionListInit()) {
        StartupError("Couldn't open connection list", launchFlags);
        return 0;
    }

    if (!HostKeysInit()) {
        StartupError("Couldn't open known host key list", launchFlags);
        return 0;
    }

    if (!PublicKeysInit()) {
        StartupError("Couldn't open public key list", launchFlags);
        return 0;
    }

    if (!init_arm()) {
        StartupError("Couldn't find ARM code", launchFlags);
        return 0;
    }

    if (!PrefsInit()) {
        StartupError("Couldn't open preferences", launchFlags);
        return 0;
    }

    // allowed to fail - zlib support is silently disabled
    // fixme zlib causing crash on quit - don't use it
    // ZLSetup(); 

    {
        uint32_t warned;
        Err err;
        
        err = FtrGet(PSSH_CREATOR, ftrSecurityWarning, &warned);
        if (err) warned = 0;
        if (!warned) {
            FrmCustomAlert(AlertFormID, "WARNING: pssh is substantially UNTESTED and probably INSECURE. Do not use for security-critical applications.", " ", " ");
            warned = 1;
            FtrSet(PSSH_CREATOR, ftrSecurityWarning, warned);
        }
    }

    // init virtual Graffiti area API, if any
    CollapseAppStart();

    RAND_init();

    /*
    {
        // fixme quickie dynamic heap check
        UInt16 heapID;
        UInt32 free;
        UInt32 max;
        UInt32 size;
        heapID = MemHeapID(0, 0);
        MemHeapFreeBytes(heapID, &free, &max);
        size = MemHeapSize(heapID);
        complain_int("dynamic heap size ", size);
        complain_int("dynamic heap free ", free);
        complain_int("dynamic heap max chunk ", max);
    }
    */

    {
        MemHandle fontH;
        FontType *fontP;

#define DEFINEFONT(f) \
        fontH = DmGetResource(fontExtRscType, f); \
        fontP = MemHandleLock(fontH); \
        FntDefineFont((FontID)f, fontP);

        DEFINEFONT(NanoFontSingleID);
        DEFINEFONT(NanoFontDoubleID);
        DEFINEFONT(MediumFontSingleID);
        DEFINEFONT(MediumFontDoubleID);
        DEFINEFONT(PasswordFontID);

#undef DEFINEFONT
    }

    // 10 second timeout for DNS and connect()
    AppNetTimeout = SysTicksPerSecond() * 10;

    FrmGotoForm(MainFormID);

    do {
        Int32 delay;
        int lastEventType;

        if (ss  &&  !ssh_is_closed(ss)) {
            fd_set rfds;
            int maxfd;
            int ssfd;
            int selected;
            struct timeval tv;

            FD_ZERO(&rfds);
            FD_SET(STDIN_FILENO, &rfds);
            
            ssfd = ssh_request_select(ss, &rfds, NULL);
            maxfd = MAX(STDIN_FILENO, ssfd);

            // Don't block in select() if there are events in the event queue.
            // fixme even if there are no events, keep block time short 
            // because of stuck-in-select problem
            if (EvtEventAvail()) {
                tv.tv_usec = 0;
                tv.tv_sec = 0;
            } else {
                tv.tv_usec = 100000L;  // 1/10 sec
                tv.tv_sec = 0;
            }

            selected = select(maxfd + 1, &rfds, NULL, NULL, &tv);

            if (selected >= 0) {
                if (FD_ISSET(ssfd, &rfds)) {
                    EventType e = {0};
                    e.eType = usrNetEvent;
                    EvtAddUniqueEventToQueue(&e, 0, true);
                }
            }

            delay = 0; // DON'T block in EvtGetEvent
        } else {
            // No sockets to select() yet
            delay = -1;  // DO block in EvtGetEvent (forever)
        }


        {
            EventType event;
            Err err;

            EvtGetEvent(&event, delay);

            CollapseCheckWinEnterEvent(&event);

            if (event.eType == usrNetEvent) {
                // Handle incoming network data
                if (ss  &&  !ssh_is_closed(ss)) {
                    ssh_read(ss);
                }
            }

            if (TerminalFormStealEvent(&event)) {
                FrmDispatchEvent(&event);
            } else {
                if (!SysHandleEvent(&event)) {
                    if (!MenuHandleEvent(NULL, &event, &err)) {
                        if (!ApplicationHandleEvent(&event)) {
                            FrmDispatchEvent(&event);
                        }
                    }
                }
            }

            if (ss) {
                now = TimGetTicks();
                if (now >= lastUpdate + tenth) {
                    ssh_task(ss);
                    lastUpdate = now;
                }
            }

            if (event.eType == keyDownEvent || event.eType == keyUpEvent || 
                event.eType == penUpEvent   || event.eType == penMoveEvent) 
            {
                RAND_add_event_entropy(&event);
            }

            if (event.eType == appStopEvent) break;
        }
    } while (1);

 done:
    FrmSaveAllForms();
    FrmCloseAllForms();

    MemoListFree();
    PublicKeysFree();
    HostKeysFree();
    ConnectionListFree();

    for (i = 0; i < NetLibCount; i++) {
        NetLibClose(AppNetRefnum, false);
    }

    CollapseAppStop();

    RAND_stop();

    if (ZLAvailable()) ZLTeardown();

    return 0;
}



/*

#if 0
        // memory leak tracing
        {
            static int inited = 0;
            static UInt16 heapID;
            static uint32_t gen = 0;
            UInt32 free;
            UInt32 max;
            UInt32 size;
            extern uint32_t ev_block_count;
            extern uint32_t ev_byte_count;
            //            extern uint32_t ev_blocks_used;
            if (!inited) {
                inited = 1;
                heapID = MemHeapID(0, 0);
            }
            MemHeapFreeBytes(heapID, &free, &max);
            size = MemHeapSize(heapID);
            
            debug_printf("mem %luK,%lu[%luK]AB,%lu gen", (size-free)/1024, ev_block_count, ev_byte_count/1024, gen++);
            // if (gen % 100 == 0) arena_dump_blocks();
        }
#endif
        
        // fixme write queue
        //if (ssh_is_selected(ss, &wfds)) {
        // ssh_write(ss);
        // }
        */
