/**********
 * 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 "ssh.h"
#include "rsrc.h"
#include "session.h"
#include "packetizer.h"
#include "transport.h"
#include "connection.h"
#include "formutils.h"
#include "forms/publickeychoiceform.h"

#include "vt100/vt100.h"

// The "current session" being processed. 
// This is used for malloc(), printf(), etc when deep inside libraries.
// current_ss is set by the ssh_* functions below.
static ssh_session_t *current_ss = NULL;


static ssh_session_t *ssh_set_current(ssh_session_t *ss)
{
    ssh_session_t *old_ss = current_ss;
    current_ss = ss;
    return old_ss;
}


ssh_session_t *ssh_get_current(void)
{
    return current_ss;
}


#define SSH_SET_CURRENT  ssh_session_t *old_ss = ssh_set_current(ss)
#define SSH_RESTORE_CURRENT  ssh_set_current(old_ss)


ssh_session_t *ssh_create(char *username, RectangleType bounds)
{
    ssh_session_t *ss = 
        (ssh_session_t *)nonarena_malloc(sizeof(ssh_session_t));
    bzero(ss, sizeof(ssh_session_t));
    
    ssh_set_current(ss);

    ssh_log(__PRETTY_FUNCTION__);

    // nonzero init
    // make session_init() etc. functions if this gets big
    ss->s.socket = -1;


    ss->tty = vt100_new(ss, bounds);
    ss->username = arena_strdup(username);

    ssh_set_current(NULL);

    return ss;
}

// initiate ordinary connection shutdown
// The connection may not actually be closed when this function 
// terminates, and this session must still be processed in the event loop.
// Use ssh_is_closed() to determine when shutdown is complete.
// Use ssh_kill() to immediately close a connection (not cleanly)
void ssh_close(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    ss->closing = 1;

    if (connection_is_open(ss)) connection_close(ss);
    if (connection_is_closing(ss)) goto done;
    
    if (transport_is_open(ss)) transport_close(ss);
    if (transport_is_closing(ss)) goto done;

    if (packetizer_is_open(ss)) packetizer_close(ss);
    if (packetizer_is_closing(ss)) goto done;

    if (session_is_open(ss)) session_close(ss);
    if (session_is_closing(ss)) goto done;

 done:
    SSH_RESTORE_CURRENT;
}

void ssh_kill(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    ss->closing = 1;

    connection_kill(ss);
    transport_kill(ss);
    packetizer_kill(ss);
    session_kill(ss);

    SSH_RESTORE_CURRENT;
}

void ssh_free(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    ssh_kill(ss);
    arena_free(ss->username);
    vt100_free(ss->tty);

    SSH_RESTORE_CURRENT;

    // ss itself is not arena allocated
    nonarena_free(ss);
}


int ssh_open(ssh_session_t *ss, char *hostname, int port)
{
    NetHostInfoBufType *remoteHost;
    NetSocketAddrINType remoteINAddr;
    NetSocketAddrType *remoteAddr = (NetSocketAddrType *)&remoteINAddr;
    char on = 1;
    int sock;
    int err = 0;

    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    // find remote host
    // GrP gethostbyname() is supposed to return NULL when the host is not 
    // found, but this doesn't seem to happen on my Tungsten C running 5.2.1.
    // Instead, it returns &AppHostInfo, which may still have a *previous* 
    // host lookup result! 
    h_errno = 0;
    bzero(&AppHostInfo, sizeof(AppHostInfo));
    remoteHost = (NetHostInfoBufType *)gethostbyname(hostname);
    if (!remoteHost  ||  h_errno  ||  remoteHost->hostInfo.addrLen == 0) { 
        NetworkError("The server could not be found.", h_errno);
        err = -1; 
        goto done; 
    }
    remoteINAddr.family = AF_INET;
    remoteINAddr.port = port;
    remoteINAddr.addr = remoteHost->address[0];

    // create socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) { 
        NetworkError("Could not create network socket.", errno);
        err = -1; 
        goto done;
    }

    // begin connecting to remote host
    // fixme need non-blocking connect
    err = connect(sock, remoteAddr, sizeof(remoteINAddr));
    if (err) { 
        NetworkError("Could not connect to server.", errno);
        goto done;
    }

    // set socket options: non-blocking, no coalescing on send
    setsockopt(sock, SOL_SOCKET, netSocketOptSockNonBlocking, &on, sizeof(on));
    setsockopt(sock, netSocketOptLevelTCP, netSocketOptTCPNoDelay, &on, sizeof(on));

    ss->s.socket = sock;
    ss->s.hostname = arena_strdup(hostname);
    ss->s.hostaddr = arena_strdup(inet_ntoa(*(struct in_addr *)&remoteINAddr.addr));
    ss->s.state = SESSION_STATE_OPEN;

    // fixme move some of the above to session.c

    packetizer_start(ss, "SSH-2.0-pssh_" PSSH_UVERSION);

 done:
    SSH_RESTORE_CURRENT;
    return err;
}


int ssh_is_closed(ssh_session_t *ss)
{
    // fixme
    return (ss->s.socket == -1);
}


int ssh_request_select(ssh_session_t *ss, fd_set *rfds, fd_set *wfds)
{
    // fixme
    if (ss->s.socket >= 0) {
        FD_SET(ss->s.socket, rfds);
        return ss->s.socket;
    } else {
        return 0;
    }
}


int ssh_is_selected(ssh_session_t *ss, fd_set *fds)
{
    if (ss->s.socket >= 0) {
        return FD_ISSET(ss->s.socket, fds);
    } else {
        return 0;
    }
}


void ssh_read(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    packetizer_receive_data(ss);

    SSH_RESTORE_CURRENT;
}


void ssh_write(ssh_session_t *ss)
{
    // fixme 
    return;
}

int ssh_app_cursor_keys(ssh_session_t *ss)
{
    if (ss->tty) return vt100_app_cursor_keys(ss->tty);
    else return 0;
}

void ssh_key(ssh_session_t *ss, char c)
{
    ssh_keys(ss, &c, 1);
}

void ssh_keys(ssh_session_t *ss, uint8_t *buf, uint16_t bufLen)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (connection_is_open(ss)) {
        connection_send_data(ss, buf, bufLen);
    }

    // maybe scroll to bottom when key is pressed
    vt100_seen_key(ss->tty);

    SSH_RESTORE_CURRENT;
}

void ssh_bytes(ssh_session_t *ss, uint8_t *buf, uint16_t bufLen)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (connection_is_open(ss)) {
        connection_send_data(ss, buf, bufLen);
    }

    // Non-interactive input - do NOT scroll to bottom

    SSH_RESTORE_CURRENT;
}


void ssh_receive_bytes(ssh_session_t *ss, uint8_t *buf, uint16_t bufLen)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) {
        EventType e = {0};
        vt100_write(ss->tty, buf, bufLen);
        e.eType = usrDrawVT100Event;
        EvtAddUniqueEventToQueue(&e, 0, true);
    }

    SSH_RESTORE_CURRENT;
}

void ssh_update(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_update(ss->tty);

    ssh_log(__PRETTY_FUNCTION__ " done");

    SSH_RESTORE_CURRENT;
}


void ssh_task(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss  &&  ss->tty) vt100_task(ss->tty);

    ssh_log(__PRETTY_FUNCTION__ " done");

    SSH_RESTORE_CURRENT;
}

void ssh_request_password(int numAttempts)
{
    // do NOT call ssh_use_password until next event loop!
    FrmPopupForm(PasswordFormID);
}

void ssh_use_password(ssh_session_t *ss, char *username, char *password)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    connection_use_password(ss, username, password);

    SSH_RESTORE_CURRENT;
}


void ssh_request_passphrase(queue_t *q)
{
    // do NOT call ssh_use_password until next event loop!
    PublicKeyChoiceKeys = q;
    FrmPopupForm(PublicKeyChoiceFormID);
}


Boolean ssh_use_passphrase(ssh_session_t *ss, char *username, char *passphrase,
                           MemHandle pubkey)
{
    Boolean ok;

    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    ok = connection_use_passphrase(ss, username, passphrase, pubkey);

    SSH_RESTORE_CURRENT;

    return ok;
}


void ssh_scroll(ssh_session_t *ss, int lines)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_scroll(ss->tty, lines);

    SSH_RESTORE_CURRENT;
}

int ssh_visible_height(ssh_session_t *ss) 
{
    int height = 0;
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) {
        vt100_size(ss->tty, NULL, &height, NULL);
    }

    SSH_RESTORE_CURRENT;

    return height;
}

void ssh_activate(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) {
        EventType e = {0};
        vt100_activate(ss->tty);
        e.eType = usrDrawVT100Event;
        EvtAddUniqueEventToQueue(&e, 0, true);
    }

    SSH_RESTORE_CURRENT;
}

void ssh_deactivate(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_deactivate(ss->tty);

    SSH_RESTORE_CURRENT;
}

void ssh_set_bounds(ssh_session_t *ss, RectangleType bounds) 
{
    int charsWide, charsHigh;

    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) {
        // bounds is LO-RES and entire gadget area
        vt100_set_bounds(ss->tty, bounds);
        
        vt100_size(ss->tty, &charsWide, &charsHigh, &bounds);
        // bounds is now HI-RES and text area only
        connection_use_bounds(ss, bounds, charsWide, charsHigh);
    }

    SSH_RESTORE_CURRENT;
}

void ssh_click(ssh_session_t *ss, int x, int y, int clickCount)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_click(ss->tty, x, y, clickCount);

    SSH_RESTORE_CURRENT;
}

void ssh_deselect(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_deselect(ss->tty);

    SSH_RESTORE_CURRENT;
}



void ssh_copy(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_copy(ss->tty);

    SSH_RESTORE_CURRENT;
}


void ssh_paste(ssh_session_t *ss)
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_paste(ss->tty);

    SSH_RESTORE_CURRENT;
}


int ssh_selection_exists(struct ssh_session_t *ss) 
{
    int result;

    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) result = vt100_selection_exists(ss->tty);
    else result = false;

    SSH_RESTORE_CURRENT;

    return result;
}

void ssh_size(struct ssh_session_t *ss, int *charsWide, int *charsHigh) 
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss->tty) vt100_size(ss->tty, charsWide, charsHigh, NULL);
    else { *charsWide = 80; *charsHigh = 40; }

    SSH_RESTORE_CURRENT;
}


// current set of mutable prefs:
// * font and font size
// * font colors
void ssh_reread_prefs(struct ssh_session_t *ss) 
{
    SSH_SET_CURRENT;

    ssh_log(__PRETTY_FUNCTION__);

    if (ss && ss->tty) {
        int charsWide, charsHigh;
        RectangleType bounds;

        vt100_reread_prefs(ss->tty);

        // send new size notification in case it changed
        vt100_size(ss->tty, &charsWide, &charsHigh, &bounds);
        connection_use_bounds(ss, bounds, charsWide, charsHigh);
    }

    SSH_RESTORE_CURRENT;
}
