/**********
 * Copyright (c) 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 "formutils.h"
#include "data/connectionlist.h"
#include "data/publickeys.h"
#include "data/prefs.h"
#include "rsrc/rsrc.h"
#include "ssh/ssh.h"
#include "forms/CollapseUtils.h"

#include "publickeychoiceform.h"


static Boolean RetrieveUsernameAndPassphrase(void) FORMS_SEGMENT;
static void ForgetPassphrase(void) FORMS_SEGMENT;
static void SetPassphraseEcho(Boolean echo) FORMS_SEGMENT;
static Boolean LoadPublicKeyChoiceForm(void) FORMS_SEGMENT;

extern struct ssh_session_t *ss;

queue_t *PublicKeyChoiceKeys = NULL;

static char **key_names = NULL;
static MemHandle *key_handles = NULL;
static uint16_t key_count = 0;


static Boolean RetrieveUsernameAndPassphrase(void)
{
    // fixme retrieve popup choice too
    FieldPtr usernameFld = PrvGetObjectByID(PublicKeyChoiceFormUsernameFieldID);
    FieldPtr passphraseFld = PrvGetObjectByID(PublicKeyChoiceFormPassphraseFieldID);
    MemHandle usernameH;
    MemHandle passphraseH;
    char *username;
    char *passphrase;
    Boolean ok;
    int keyIndex;

    FldReleaseFocus(usernameFld);
    FldCompactText(usernameFld);
    usernameH = FldGetTextHandle(usernameFld);

    FldReleaseFocus(passphraseFld);
    FldCompactText(passphraseFld);
    passphraseH = FldGetTextHandle(passphraseFld);

    if (usernameH) {
        username = MemHandleLock(usernameH);
    } else {
        username = "";
    }

    if (passphraseH) {
        passphrase = MemHandleLock(passphraseH);
    } else {
        passphrase = "";
    }

    keyIndex = LstGetSelection(PrvGetObjectByID(PublicKeyChoiceFormKeyListID));
    if (keyIndex == noListSelection) {
        complain("no selection");
        ok = false;
    } else {
        ok = ssh_use_passphrase(ss, username, passphrase, key_handles[keyIndex]);
    }

    if (usernameH) MemHandleUnlock(usernameH);
    if (passphraseH) MemHandleUnlock(passphraseH);

    if (!ok) {
        // password wrong - try again
        // leave username field alone and clear passphrase field
        PrvSetFieldToValue(passphraseFld, "");
        FldEraseField(passphraseFld);
        FldDrawField(passphraseFld);
        PrvSetFocusByID(PublicKeyChoiceFormPassphraseFieldID);
    } else {
        // password ok - clean up field contents
        if (usernameH) {
            FldSetTextHandle(usernameFld, NULL);
            MemHandleFree(usernameH);
        }

        if (passphraseH) {
            FldSetTextHandle(passphraseFld, NULL);
            MemSet(MemHandleLock(passphraseH), MemHandleSize(passphraseH), 0);
            MemHandleUnlock(passphraseH);
            MemHandleFree(passphraseH);
        }
    }

    return ok;
}


static void ForgetPassphrase(void)
{
    FieldPtr passphraseFld = PrvGetObjectByID(PublicKeyChoiceFormPassphraseFieldID);
    MemHandle passphraseH;

    FldReleaseFocus(passphraseFld);
    FldCompactText(passphraseFld);
    passphraseH = FldGetTextHandle(passphraseFld);
    FldSetTextHandle(passphraseFld, NULL);

    if (passphraseH) {
        MemSet(MemHandleLock(passphraseH), MemHandleSize(passphraseH), 0);
        MemHandleUnlock(passphraseH);
        MemHandleFree(passphraseH);
    }
}


static void SetPassphraseEcho(Boolean echo)
{
    PrvSetControlValue(PublicKeyChoiceFormEchoCheckboxID, echo);
    FldSetFont(PrvGetObjectByID(PublicKeyChoiceFormPassphraseFieldID), 
               echo ? stdFont : (FontID)PasswordFontID);
}


static Boolean LoadPublicKeyChoiceForm(void)
{
    MemHandle recordH;
    Boolean ok = true;
    char *hostname;
    char *username;
    char *portname;

    recordH=ConnectionListReadSelectedRecord(&hostname, &portname, &username);
    if (!recordH) return false;

    if (ok) ok = PrvSetFieldToValueByID(PublicKeyChoiceFormHostFieldID, hostname);
    if (ok) ok = PrvSetFieldToValueByID(PublicKeyChoiceFormUsernameFieldID, username);

    if (ok) {
        if (StrLen(username) == 0) {
            // missing username - set caret to username
            PrvSetFocusByID(PublicKeyChoiceFormUsernameFieldID);
        } else {
            // username ok - set caret to passphrase
            PrvSetFocusByID(PublicKeyChoiceFormPassphraseFieldID);
        }
    }

    {
        // populate public key list
        ListPtr lst;
        uint16_t i, j;
        queue_t *q = PublicKeyChoiceKeys;

        // get all pubkey names and handles
        key_count = queue_count(q);
        key_names = arena_malloc(key_count * sizeof(char *));
        key_handles = arena_malloc(key_count * sizeof(MemHandle));
        for (i = 0; i < key_count; i++) {
            key_handles[i] = queue_peek(q, i);
            key_names[i] = NameForPublicKey(key_handles[i]);
        }

        // sort keys by name to type-select works (bubble sort)
        for (i = 0; i < key_count; i++) {
            for (j = i+1; j < key_count; j++) {
                if (strcmp(key_names[i], key_names[j]) > 0) {
                    char *tmp_name = key_names[i];
                    MemHandle tmp_handle = key_handles[i];
                    key_names[i] = key_names[j];
                    key_handles[i] = key_handles[j];
                    key_names[j] = tmp_name;
                    key_handles[j] = tmp_handle;
                }
            }
        }

        lst = PrvGetObjectByID(PublicKeyChoiceFormKeyListID);
        LstSetListChoices(lst, key_names, key_count);
        LstSetHeight(lst, key_count);

        // fixme select a previously-good key for this connection using prefs
        
        LstSetSelection(lst, 0);
        PrvSetControlLabel(PublicKeyChoiceFormKeyTriggerID, key_names[0]);
    }

    if (ok) SetPassphraseEcho(PrefsGetInt(prefEchoPassword, 0));

    MemHandleUnlock(recordH);

    return ok;
}


static void DoOK(void)
{
    // send username and passphrase to connection
    if (RetrieveUsernameAndPassphrase()) {
        FrmReturnToForm(0);
    } else {
        // passphrase was wrong (probably)
        FrmCustomAlert(AlertFormID, "Incorrect passphrase.", " ", " ");
    }
}


Boolean PublicKeyChoiceFormHandleEvent(EventPtr e)
{
    FormPtr frmP = FrmGetActiveForm();

    switch (e->eType) {
    case frmOpenEvent:
        CollapseSetState(frmP, collapseStateUser);
        if (!LoadPublicKeyChoiceForm()) {
            // load failed - go back to terminal form
            FrmReturnToForm(0);
        } else {
            FrmDrawForm(frmP);
        }
        return true;

    case keyDownEvent:
        if (!EvtKeydownIsVirtual(e)) {
            unsigned char c = e->data.keyDown.chr;
            if (c == '\n'  ||  c == '\r') {
                // enter in username - switch to password
                // enter in password - OK button
                // enter in neither - OK button
                UInt16 focusID;
                UInt16 focusIndex = FrmGetFocus(FrmGetActiveForm());
                if (focusIndex == noFocus) {
                    DoOK();
                    return true;
                } 
                focusID = FrmGetObjectId(FrmGetActiveForm(), focusIndex);
                if (focusID == PublicKeyChoiceFormUsernameFieldID) {
                    PrvSetFocusByID(PublicKeyChoiceFormPassphraseFieldID);
                    PrvFieldSelectAll(PublicKeyChoiceFormPassphraseFieldID);
                    return true;
                } else if (focusID == PublicKeyChoiceFormPassphraseFieldID) {
                    DoOK();
                    return true;
                }
            }
        }
        return false;

    case ctlSelectEvent:
        switch (e->data.ctlSelect.controlID) {
        case PublicKeyChoiceFormOKButtonID:
            DoOK();
            return true;

        case PublicKeyChoiceFormCancelButtonID:
            // skip further pubkey auth (continue to password, probably)
            FrmReturnToForm(0);
            ssh_use_passphrase(ss, NULL, NULL, NULL); // this is user cancel
            return true;

        case PublicKeyChoiceFormEchoCheckboxID:
            // change passphrase field echoing
            PrefsPutInt(prefEchoPassword, e->data.ctlSelect.on);
            SetPassphraseEcho(e->data.ctlSelect.on);
            return true;

        default:
            return false;
        }

    case frmCloseEvent:
        // clear entered passphrase, if any
        ForgetPassphrase();
        // free key names
        if (key_names) {
            int i;
            LstSetListChoices(PrvGetObjectByID(PublicKeyChoiceFormKeyListID), 
                          NULL, 0);
            PrvSetControlLabel(PublicKeyChoiceFormKeyTriggerID, "");
            for (i = 0; i < key_count; i++) {
                arena_free(key_names[i]);
            }
            arena_free(key_names);
            key_names = NULL;
            key_count = NULL;
        }
        if (key_handles) {
            arena_free(key_handles);
            key_handles = NULL;
        }
        return false;

    default:
        return false;
    }
}

