/*

  sftp_client.c 

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 2001 Pekka Riikonen

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

*/

#include "silcincludes.h"
#include "silcsftp.h"

typedef struct {
  SilcSchedule schedule;
  SilcSocketConnection sock;
  SilcSFTP sftp;
} *Client;

Client gclient;

char *dir;
char *file;
bool opendir;
SilcUInt64 offset;

static void sftp_name(SilcSFTP sftp, SilcSFTPStatus status,
		      const SilcSFTPName name, void *context);
static void sftp_handle(SilcSFTP sftp, SilcSFTPStatus status,
			SilcSFTPHandle handle, void *context);
static void sftp_data(SilcSFTP sftp, SilcSFTPStatus status,
		      const unsigned char *data, SilcUInt32 data_len,
		      void *context);

static void send_packet(SilcSocketConnection sock,
			SilcBuffer packet, void *context)
{
  Client client = (Client)context;
  SilcPacketContext packetdata;
  int ret;

  memset(&packetdata, 0, sizeof(packetdata));
  packetdata.type = SILC_PACKET_FTP;
  packetdata.truelen = packet->len + SILC_PACKET_HEADER_LEN;
  packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen, 0);
  silc_packet_send_prepare(sock,
			   SILC_PACKET_HEADER_LEN,
			   packetdata.padlen,
			   packet->len);
  packetdata.buffer = sock->outbuf;
  silc_buffer_put(sock->outbuf, packet->data, packet->len);
  silc_packet_assemble(&packetdata, NULL);
  ret = silc_packet_send(sock, TRUE);
  if (ret != -2)
    return;

  silc_schedule_set_listen_fd(client->schedule, sock->sock, 
			      (SILC_TASK_READ | SILC_TASK_WRITE));
  SILC_SET_OUTBUF_PENDING(sock);
}

static bool packet_parse(SilcPacketParserContext *parser, void *context)
{
  Client client = (Client)parser->context;
  SilcSocketConnection sock = parser->sock;
  SilcPacketContext *packet = parser->packet;
  int ret;
  
  ret = silc_packet_parse(packet, NULL);
  assert(packet->type == SILC_PACKET_FTP);

  silc_sftp_client_receive_process(client->sftp, sock, packet);

  return TRUE;
}

SILC_TASK_CALLBACK(packet_process)
{
  Client client = (Client)context;
  SilcSocketConnection sock = client->sock;
  int ret;

  if (type == SILC_TASK_WRITE) {
    if (sock->outbuf->data - sock->outbuf->head)
     silc_buffer_push(sock->outbuf, sock->outbuf->data - sock->outbuf->head);

    ret = silc_packet_send(sock, TRUE);
    if (ret < 0)
      return;

    silc_schedule_set_listen_fd(client->schedule, fd, SILC_TASK_READ);
    SILC_UNSET_OUTBUF_PENDING(sock);
    silc_buffer_clear(sock->outbuf);
    return;
  }

  if (type == SILC_TASK_READ) {
    ret = silc_packet_receive(sock);
    if (ret < 0)
      return;

    if (ret == 0) {
      silc_net_close_connection(sock->sock);
      silc_socket_free(sock);
      exit(0);
    }

    silc_packet_receive_process(sock, FALSE, NULL, NULL, 0, 
				packet_parse, client);
  }
}

static void sftp_data(SilcSFTP sftp, SilcSFTPStatus status,
		      const unsigned char *data, SilcUInt32 data_len,
		      void *context)
{
  SilcSFTPHandle handle = (SilcSFTPHandle)context;
  int debug = silc_debug;

  if (status != SILC_SFTP_STATUS_OK) {
    SilcSFTPAttributesStruct attrs;

    fprintf(stderr, "Status %d\n", status);

    if (!strcmp(file, "/sftp/sftp_server.c"))
      return;

    /* Open another file */
    opendir = FALSE;
    memset(&attrs, 0, sizeof(attrs));
    file = "/sftp/sftp_server.c";
    fprintf(stderr, "Opening file %s\n", file);
    offset = 0;
    silc_sftp_open(sftp, file, SILC_SFTP_FXF_READ,
		   &attrs, sftp_handle, gclient);
    return;
  }

  if (!debug)
    silc_debug = 1;
  SILC_LOG_HEXDUMP(("data"), (unsigned char *)data, data_len);
  silc_debug = debug;

  offset += data_len;

  /* Attempt to read more */
  fprintf(stderr, "Reading more of file %s\n", file);
  silc_sftp_read(sftp, handle, offset, 2048, sftp_data, handle);
}

static void sftp_name(SilcSFTP sftp, SilcSFTPStatus status,
		      const SilcSFTPName name, void *context)
{
  Client client = (Client)context;
  int i;

  SILC_LOG_DEBUG(("Name"));
  fprintf(stderr, "Status %d\n", status);

  fprintf(stderr, "Directory: %s\n", dir);
  for (i = 0; i < name->count; i++) {
    fprintf(stderr, "%s\n", name->long_filename[i]);
  }

  if (!strcmp(dir, "sftp")) {
    SilcSFTPAttributesStruct attrs;

    /* open */
    opendir = FALSE;
    memset(&attrs, 0, sizeof(attrs));
    file = "passwd";
    fprintf(stderr, "Opening file %s\n", file);
    offset = 0;
    silc_sftp_open(sftp, file, SILC_SFTP_FXF_READ,
		   &attrs, sftp_handle, client);
    return;
  }

  if (!strcmp(dir, "/"))
    dir = "sftp";

  fprintf(stderr, "Opening %s\n", dir);

  /* opendir */
  opendir = TRUE;
  silc_sftp_opendir(sftp, dir, sftp_handle, client);
}

static void sftp_handle(SilcSFTP sftp, SilcSFTPStatus status,
			SilcSFTPHandle handle, void *context)
{
  Client client = (Client)context;

  SILC_LOG_DEBUG(("Handle"));
  fprintf(stderr, "Status %d\n", status);
  if (status != SILC_SFTP_STATUS_OK)
    return;

  if (opendir) {
    fprintf(stderr, "Reading %s\n", dir);
    /* Readdir */
    silc_sftp_readdir(sftp, handle, sftp_name, client);
  } else {
    fprintf(stderr, "Reading file %s\n", file);

    /* Read */
    silc_sftp_read(sftp, handle, 0, 2048, sftp_data, handle);
  }
}

static void sftp_version(SilcSFTP sftp, SilcSFTPStatus status,
			 SilcSFTPVersion version, void *context)
{
  Client client = (Client)context;
  fprintf(stderr, "Version: %d\n", (int)version);

  SILC_LOG_DEBUG(("Version"));
  fprintf(stderr, "Status %d\n", status);

  /* opendir */
  dir = "/";
  fprintf(stderr, "Opening %s\n", dir);
  opendir = TRUE;
  silc_sftp_opendir(sftp, dir, sftp_handle, client);
}

int main(int argc, char **argv)
{
  Client client = silc_calloc(1, sizeof(*client));
  int sock;

  gclient = client;

  if (argc > 1 && !strcmp(argv[1], "-d")) {
    silc_debug = 1;
    silc_debug_hexdump = 1;
    silc_log_set_debug_string("");
  }

  client->schedule = silc_schedule_init(100);
  if (!client->schedule)
    return -1;

  /* Connecto to server */
  sock = silc_net_create_connection(NULL, 5000, "127.0.0.1");
  if (sock < 0)
    return -1;
  silc_socket_alloc(sock, 0, NULL, &client->sock);
  silc_schedule_task_add(client->schedule, sock,
			 packet_process, client, 0, 0,
			 SILC_TASK_GENERIC, SILC_TASK_PRI_NORMAL);

  /* Start SFTP session */
  client->sftp = silc_sftp_client_start(client->sock, send_packet, client,
					sftp_version, client);

  silc_schedule(client->schedule);
  return 0;
}
