/*
  sshfc_conn.h

  Author: Sami Lehtinen <sjl@ssh.com>

  Copyright (C) 1999-2000 SSH Communications Security Corp, Helsinki, Finland
  All rights reserved.

  Connection handling for SshFileCopy.
 */
#define SSH_DEBUG_MODULE "SshFileCopyConn"

#include "sshincludes.h"
#include "sshfc_conn.h"

typedef struct SshFileCopyConnThreadContextRec
{
  SshFileCopyConnection conn;
  SshFSMCondition waiting;
  SshFSMCondition finished;
  SshUInt8 *conn_flags;
  SshFSMThread child;
  SshFSMThread parent;
  /* XXX */
  Boolean conn_established_once;
  SshOperationHandle op_handle;
} *SshFileCopyConnThreadContext;

void completion_cb(SshFileClient client, void *context)
{
  SshFSMThread thread = (SshFSMThread) context;
  SSH_FSM_TDATA(SshFileCopyConnThreadContext);

  tdata->op_handle = NULL;
  
  if (!client)
    {
      SSH_FSM_SET_NEXT("fcc_connection_failed");      
    }
  else
    {
      SSH_FSM_SET_NEXT("fcc_connection_ready");
    }

  SSH_FSM_CONTINUE_AFTER_CALLBACK(thread);
}

SSH_FSM_STEP(ssh_fc_conn_establish)
{
  SSH_FSM_TDATA(SshFileCopyConnThreadContext);
  
  if (*tdata->conn_flags != SSH_FCC_DOWN)
    return SSH_FSM_SUSPENDED;
  
  if (tdata->conn->client)
    {
      ssh_file_client_destroy(tdata->conn->client);
      tdata->conn->client = NULL;
    }

  SSH_FSM_ASYNC_CALL(tdata->op_handle = ssh_file_copy_connect   \
                     (tdata->conn, completion_cb, thread));
}

SSH_FSM_STEP(ssh_fc_conn_failed)
{
  SSH_FSM_TDATA(SshFileCopyConnThreadContext);
  *tdata->conn_flags = SSH_FCC_CONNECTION_FAILED;
  SSH_FSM_CONDITION_BROADCAST(tdata->finished);      
  ssh_fsm_kill_thread(tdata->parent);
  return SSH_FSM_FINISH;
}

SSH_FSM_STEP(ssh_fc_conn_ready)
{
  SSH_FSM_TDATA(SshFileCopyConnThreadContext);

  *tdata->conn_flags = SSH_FCC_OK;
  SSH_FSM_CONDITION_BROADCAST(tdata->waiting);
  return SSH_FSM_SUSPENDED;
}

SSH_FSM_STEP(ssh_fc_conn_close)
{
  SSH_FSM_TDATA(SshFileCopyConnThreadContext);
  
  if (tdata->conn->client)
    {
      SSH_TRACE(2, ("Destroying connection..."));
      ssh_file_client_destroy(tdata->conn->client);
      tdata->conn->client = NULL;
    }

  *tdata->conn_flags = SSH_FCC_DOWN;
  return SSH_FSM_SUSPENDED;
}

SSH_FSM_STEP(ssh_fc_abort)
{
  SSH_TRACE(2, ("Aborted conn thread."));
  return SSH_FSM_FINISH;
}

SSH_FSM_STEP(ssh_fc_suspend)
{
  return SSH_FSM_SUSPENDED;
}

void parent_message_handler(SshFSMThread thread, SshUInt32 message)
{
  SSH_FSM_TDATA(SshFileCopyConnThreadContext);
  SSH_PRECOND(thread);

  switch (message)
    {
    case SSH_FCC_CONN_DOWN:
      /* XXX */
      if (!tdata->conn_established_once)
        {
          *tdata->conn_flags = SSH_FCC_DOWN;
          ssh_fsm_set_next(tdata->child, "fcc_establish_connection");
          ssh_fsm_continue(tdata->child);
          tdata->conn_established_once = TRUE;
        }
      else
        {
          *tdata->conn_flags = SSH_FCC_CONNECTION_FAILED;
          SSH_FSM_CONDITION_BROADCAST(tdata->finished);      
          ssh_fsm_kill_thread(tdata->child);
        }
      return;
    case SSH_FCC_CONN_ABORT:
      *tdata->conn_flags = SSH_FCC_ABORTED;
      SSH_TRACE(2, ("Received SSH_FCC_CONN_ABORT message."));
      ssh_fsm_set_next(tdata->child, "fcc_abort");
      ssh_fsm_continue(tdata->child);
      ssh_fsm_kill_thread(thread);
      return;
    case SSH_FCC_CONN_CLOSE:
      ssh_fsm_set_next(tdata->child, "fcc_close_connection");
      ssh_fsm_continue(tdata->child);
      return;
    default:
      ssh_fatal(SSH_DEBUG_MODULE ": parent_message_handler: Invalid "
                "message %lu.", (unsigned long) message);
    }
}

void parent_thread_destroyer(void *tdata)
{
  SshFileCopyConnThreadContext parent_tdata =
    (SshFileCopyConnThreadContext) tdata;
  SshFileCopyConnThreadContext child_tdata;

  child_tdata = ssh_fsm_get_tdata(parent_tdata->child);
  if (child_tdata->op_handle)
    {
      ssh_operation_abort(child_tdata->op_handle);
      child_tdata->op_handle = NULL;
    }
  ssh_fsm_kill_thread(parent_tdata->child);
}

SshFSMThread
ssh_file_copy_connection_thread_spawn(SshFSM fsm,
                                      SshFSMCondition waiting,
                                      SshFSMCondition finished,
                                      SshFileCopyConnection conn,
                                      SshUInt8 *conn_flags)
{
  SshFSMThread parent, child;
  SshFileCopyConnThreadContext parent_tdata, child_tdata;

  SSH_PRECOND(fsm);
  SSH_PRECOND(waiting);
  SSH_PRECOND(finished);
  SSH_PRECOND(conn);
  SSH_PRECOND(conn_flags);
  
  parent = ssh_fsm_spawn(fsm, sizeof(*parent_tdata), "fcc_suspend",
                         parent_message_handler,
                         parent_thread_destroyer);

  parent_tdata = ssh_fsm_get_tdata(parent);
  memset(parent_tdata, 0, sizeof(*parent_tdata));

  parent_tdata->waiting = waiting;
  parent_tdata->finished = finished;
  parent_tdata->conn = conn;
  parent_tdata->conn_flags = conn_flags;
  parent_tdata->child = NULL;
  parent_tdata->parent = NULL;
  /* XXX */
  parent_tdata->conn_established_once = FALSE;
  
  child = ssh_fsm_spawn(fsm, sizeof(*child_tdata),
                        "fcc_suspend", NULL, NULL);

  ssh_fsm_set_thread_name(child, "child");
  
  child_tdata = ssh_fsm_get_tdata(child);

  memset(child_tdata, 0, sizeof(*child_tdata));
  
  *child_tdata = *parent_tdata;

  parent_tdata->child = child;
  child_tdata->parent = parent;
  return parent;
}
