/* LICENSE:
  =========================================================================
    CMPack'04 Source Code Release for OPEN-R SDK 1.1.5-r2 for ERS7
    Copyright (C) 2004 Multirobot Lab [Project Head: Manuela Veloso]
    School of Computer Science, Carnegie Mellon University
    All rights reserved.
  ========================================================================= */


/*
  Note on program: 

  This was developed concurrently with a prototype of the EasyNet networking
  framework on which the server object is based. To minimize the transmission
  problems that that early version was having, I started developing this program
  with a sweeping approach whereby the whole directory tree is requested from
  the Aibo in a single go, reducing the interactivity required. From the returned
  listing, a hierarchical linked list is then set up to aid in the detection of
  which files have to be transferred or deleted.

  If I were to re-implement this now, I would probably try a purely recursive
  approach, doing away with all the linked list stuff...  Oh well, the program's
  here now-- if you want to have fun...  :)

*/

const char VERSION[] = "v1.00a_r1";

#include <arpa/inet.h>
#include <dirent.h>     // for 'dirent' structure to get dir entry names and size
#include <errno.h>      // for 'errno', to use 'perror' function
#include <fcntl.h>      // for 'fcntl', to make socket non-blocking
#include <netdb.h>      // for 'hostent' structure, usesed for address lookup
#include <netinet/in.h>
#include <signal.h>     // for 'SIGINT', to intercept Ctrl-C
#include <stdbool.h>    // for 'bool', 'true', 'false'
#include <stdio.h>      // for 'stderr', in sigint_handler(), and 'remove'
#include <stdlib.h>
#include <string.h>     // for 'strlen'
#include <sys/socket.h>
#include <sys/stat.h>   // for 'stat' function, to get file attributes
#include <sys/time.h>   // for 'gettimeofday' (#includes time.h)
#include <unistd.h>     // for 'unlink', 'rmdir' etc




// Definitions for network communication
#define ERR_NONE     0
#define ERR_TIMEOUT -1
#define ERR_LOOKUP  -2
#define ERR_CONNECT -3
#define ERR_OTHER   -4

// Max command-line tokens
#define MAXTOKENS  7

// This will be printed in case of error: "Error xxx: "
char errcause[][30] = { " reporting error", // 0 is no error
                        " connecting",
                        " resolving address",
                        " connecting",
                        "",
};

// Command-specific error strings
char TimedOutStr[]    =  "Operation timed out";
char ListErrStr[][50] = {"Server replied: No such file or directory",
                         "Could not send request",
                         "Invalid response received from host",
                         "Invalid filename received from host",
};
char GetErrStr[][50]  = {"Server replied: Invalid filename",
                         "Could not create file locally",
                         "Listing request failed",
                         "Could not send request",
                         "Invalid response received from host",
                         "Server replied: Invalid Get request",
                         "Invalid filesize received from host",
                         "Filesizes returned by List and Get do not match",
                         "Data transfer failed",
                         "Received more bytes than expected",
};

char PutErrStr[][50]  = {"Server Replied: Error creating file",
                         "Invalid filename",
                         "Could not open file for reading",
                         "Could not send request",
                         "Invalid response received from host",
                         "Data transfer failed",
                         "Could not read acknowledgement from host",
                         "Server replied: Error receiving data",
};

 
// Directory Management Linked List definitions
// The 'RelativeName' always gives a complete usable path by concatenating it
// to the 'BasePath', and -excpet for the BaseEntry- will always start with a '/'

typedef struct ListEntryStruct {
  char   Type;                       // This will be '-' if unknown or not found
  unsigned long Size;                // This will be 0 for directories
  char*  RelativeName;               // This will be an empty string for the BaseEntry
  struct ListEntryStruct *Next;      // Next entry in this same directory
  struct ListEntryStruct *Parent;    // Always a dir, will be NULL for the BaseEntry
  struct ListEntryStruct *Contents;  // This is only meaningful for directories
  struct ListEntryStruct *LastOfDir; // Only for dirs, it's the last entry in this dir
} *ListEntry;


typedef struct ListingBaseStruct {
  struct ListEntryStruct BaseEntry;  // This is the root path we were actually given
  bool       Remote;                 // Is this listing remote?
  char*      BasePath;
  int        MaxDepth;               // 0 means no subdirectories
  ListEntry  LastEntryAdded;
} *ListingBase;


// Global vars for network communication
int sock = -1;
int fd = -1;
int handle = -1;
const int BUFFERSIZE = 8192;

// Global vars for lolcal operations
char      line[256];                  // Current line
char      Tokens[MAXTOKENS][256];      // Holds tokens of a command
char      TargetHost[100] = "";       // Name or IP of target robot
char      CurrentSection[100] = "";   // Hold the name of the [Section] we're reading
char      OptionsString[100]  = "";   // Contains optional switches for commands. 
char      DefaultOptions[100];        // Used to revert to default switches 
char      FullNameBuffers[2][256];    // Static buffers for 'FullName' function 
int       FullNameIndex = 0;          // Static buffer index for 'FullName' function



const char* errstring(int cause) {
  // Returns appropriate error string
  switch (cause) {

    case ERR_LOOKUP:
      return hstrerror(h_errno);
    case ERR_TIMEOUT:
      return TimedOutStr;
    default:
      return strerror(errno); 
  }
}


void inputstr(char* dest, int maxchars, char* prompt) {
  printf("%s", prompt);
  fgets(dest, maxchars, stdin);
  dest[strlen(dest)-1] = '\0';
}


void ExitProc(void) {
  if (sock >= 0) close(sock);
  if (handle != -1) {
    close( handle );
  }
}


void sigint_handler(void)
{
  fprintf(stderr, "sigint_handler()\n");

  if (sock >= 0) close(sock);
  if (fd >= 0) close(fd);
  exit(1);
}


// ----------------------------------------------
// ---------- Network Lookup functions ----------
// ----------------------------------------------
//
//    netaddrofname: takes a hostname string, returns an IP unsigned long
//    straddrofname: takes a hostname string, returns an IP string
//
//    The latter two functions return NULL if no address is found

unsigned long netaddrofname(char *hostname) {
  struct hostent* host = gethostbyname(hostname);
  if (host == NULL) {
    return 0;
  } else {
    return ((struct in_addr *)host->h_addr_list[0])->s_addr;
  }
}

char *straddrofname(char *hostname) {
  struct hostent* host = gethostbyname(hostname);
  if (host == NULL) {
    return NULL;
  } else {
    return (char*)inet_ntoa(*(struct in_addr *)host->h_addr_list[0]);
  }
}
// ----------------------------------------------
// ----------                          ----------
// ----------------------------------------------



void Connect(char *host, unsigned short port)
     // Establish a connection to the robot
{
  struct sockaddr_in  saddr;
    
  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    exit(1);
  }

  memset((void*)&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_port = htons(port);
  saddr.sin_addr.s_addr = netaddrofname(host);

  if (saddr.sin_addr.s_addr == 0) {
    perror("lookup");
    exit(1);
  }

  if (connect(sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
    perror("connect");
    exit(1);
  }    
}

void Close(void) {
  // Close the connection if one is in place
  if (sock >= 0) {
    if (close(sock) < 0 ) {
      perror("close");
      exit(1);
    }
    sock = -1;
  }
}


int CloseIfConnected(void) {
  // Close the connection if one is in place
  if (sock >= 0) {
    if (close(sock) < 0 ) {
      return -1;
    }
    sock = -1;
  }
  return 0;
}


int Timeout_Connect(char *host, unsigned short port, int timeout_secs)
{
  // This routine will accept a hostname, a port, and a timeout in seconds.
  // If successful, it will return the newly created file descriptor
  // (always positive), otherwise one of the following (negative) errors:
  // ERR_TIMEOUT, ERR_LOOKUP, ERR_CONNECT, ERR_OTHER 


  struct timeval timeout;
  fd_set write_fds;
  struct sockaddr_in saddr;
  int fd;

  fd = socket (PF_INET, SOCK_STREAM, 0);

  if (fd < 0) {
    return ERR_OTHER;
  }

  memset((void*)&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_port = htons(port);
  saddr.sin_addr.s_addr = netaddrofname(host);

  if (saddr.sin_addr.s_addr == 0) {
    return ERR_LOOKUP;
  }

  // Set the non-blocking flag so we can use timeout
  if (fcntl (fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) < 0) {
    return ERR_OTHER;
  }

  // Put the specified seconds into the required time data structure
  timeout.tv_sec = timeout_secs;
  timeout.tv_usec = 0;

  while (1) {

    // This loop will keep trying as long as the timer hasn't run out
    if (connect (fd, (struct sockaddr *)&saddr,
                 sizeof(struct sockaddr_in)) < 0) {

      if ((errno != EAGAIN) && (errno != EINPROGRESS)) {
        return ERR_CONNECT;
      }

    } else {
      // Connection successful, set socket to blocking
      // again and return file descriptor.

      fcntl (fd, F_SETFL, fcntl(fd, F_GETFL) & !O_NONBLOCK);
      return fd;
    }

    // Connection couldn't be established, but could be for temporary
    // resons. We will wait until the timer runs out.
    FD_ZERO(&write_fds);
    FD_SET(fd, &write_fds);

    // this is where we sneak in the timeout
    while (select (getdtablesize(), NULL, &write_fds, NULL, &timeout) < 0) {
      if (errno != EINTR) {
        return ERR_OTHER;
      }
    }

    if ((timeout.tv_sec == 0) && (timeout.tv_usec == 0)) {
      // Here the timeout has expired, otherwise the fd is now available
      // and we'll try again at the next iteration of the while loop.
      errno = ETIMEDOUT;
      return ERR_TIMEOUT;
    }		
  }

  return fd;
}



int block_read(int socket, void* dest, int min, int max) {
  // Reads at least min bytes from socket

  int count = 0;
  char* buf;
  int bytesread = 1;
  int left;

  buf  = (char*)malloc(max);
  left = max;

  while ((count < min) && (bytesread > 0)) {
    bytesread = read(socket, buf + count, left);
    count += bytesread;
    if (bytesread < 0) {
      return -1;  // There was an error, pass it through.
    }
    left = max - count;
  }
  memcpy(dest, buf, count);
  free(buf);
  return count;
}


inline int nread(int socket, void* dest, int min) {
  // Reads exactly min bytes from socket
  return(block_read(socket, dest, min, min));
}


int List(char* target, unsigned char depth,
         void (*Handler)(char, unsigned long, char*, void*),
         void* HandlerParam) {

  // This function requests a listing for the specified target and recursion
  // depth, and for each entry it calls the Handler function passed to it,
  // with a type specifier ('F' for file, 'D' for directory, '-' for end of
  // listing), the file length, the name, and as an extra parameter the list
  // to which it should be added.  Returns number of received entries, where
  // 0 means server replied that name is not valid.

  struct ListRequestStruct {
    char           CommandByte;
    unsigned char  ListingDepth;
    unsigned char  NameLength;
    char           Name[256];
  } ListRequest;

  struct ListEntryStruct {    
    unsigned long  Size;
    char           Type;
    unsigned char  NameLength;
  } ListEntry;

  char EntryName[256];
  int  b;
  int  count = 0;

  ListRequest.CommandByte  = 'l';
  ListRequest.ListingDepth = depth;
  ListRequest.NameLength   = strlen(target);
  strcpy(ListRequest.Name, target);

  if (write(sock, &ListRequest, ListRequest.NameLength + 3) < 1) {
    return -1;  // Error writing request
  }

  while (1) {
    b = nread(sock, &ListEntry, 6);        // Receive entry
    if (b < 0) {
      return -2;  // Error receiving response
    }

    if (ListEntry.NameLength > 0) {
      b = nread(sock, EntryName, ListEntry.NameLength); // Receive name
      if (b < ListEntry.NameLength) {
        return -3;   // Error: not enough bytes received for name
      }
      EntryName[ListEntry.NameLength] = '\0';          // Terminate string
      if (Handler != NULL) {
        (*Handler)(ListEntry.Type, ListEntry.Size, EntryName, HandlerParam);
      }
      count++;
    } else {
      if (Handler != NULL) {
        (*Handler)('-', 0, "", HandlerParam);
      }
      return count;   // We're done, return the number of entries.
    }
  }
}


int Get(char* target, char* localname) {
  // Get a file from the robot.

  struct GetRequestStruct {
    char           CommandByte;
    unsigned char  NameLength;
    char           Name[256];
  } Request;

  int b, RequestSize;
  unsigned long FileSize = 0, count = 0;
  FILE* file;
  char Buffer[BUFFERSIZE];

  void CloseFile(void) {
    fclose(file);
    unlink(localname);
  }

  file = fopen(localname, "w");
  if (file == NULL) {
    return -2;  // Error creating file locally
  }

  Request.CommandByte  = 'g';
  Request.NameLength   = strlen(target);
  strcpy(Request.Name, target);

  RequestSize = Request.NameLength + 2;
  if (write(sock, &Request, RequestSize) < RequestSize) {
    CloseFile(); return -3;   // Error writing request
  }

  b = read(sock, Buffer, 1);    // Receive response
  if (b < 1) {
    CloseFile(); return -4;   // Error receiving response
  }
  if (Buffer[0] != '\0') {
    CloseFile(); return -5;   // Server returned error to Get request
  }

  b = nread(sock, &FileSize, 4);   // Receive file size
  if (b < 4) {
    CloseFile(); return -6;   // Error receiving filesize
  }
    
  count = 0;
  //printf("  Receiving...");
  while (count < FileSize) {
    b = read(sock, Buffer, BUFFERSIZE);
    if (b < 1) {
      printf("  Error: transmission problem.\n");
      CloseFile(); return -8;   // Error receiving file
    }
    fwrite(Buffer, 1, b, file);
    count += b;
    if (count > FileSize) {
      printf("  Error: received more bytes than expected.\n");
      CloseFile(); return -9;   // Received more bytes than expected!
    }
  }
  //printf("  Done.\n");
  fclose(file);
  return 1;
}




int Cat(char* target) {
  // Cat a file from the robot.

  struct GetRequestStruct {
    char           CommandByte;
    unsigned char  NameLength;
    char           Name[256];
  } Request;

  int b, RequestSize;
  unsigned long FileSize = 0, count = 0;
  char Buffer[BUFFERSIZE];

  Request.CommandByte  = 'g';
  Request.NameLength   = strlen(target);
  strcpy(Request.Name, target);

  RequestSize = Request.NameLength + 2;
  if (write(sock, &Request, RequestSize) < RequestSize) {
    return -3;   // Error writing request
  }

  b = read(sock, Buffer, 1);    // Receive response
  if (b < 1) {
    return -4;   // Error receiving response
  }
  if (Buffer[0] != '\0') {
    return -5;   // Server returned error to Get request
  }

  b = nread(sock, &FileSize, 4);   // Receive file size
  if (b < 4) {
    return -6;   // Error receiving filesize
  }
    
  count = 0;

  while (count < FileSize) {
    b = read(sock, Buffer, BUFFERSIZE);
    if (b < 1) {
      printf("  Error: transmission problem.\n");
      return -8;   // Error receiving file
    }
    Buffer[b] = '\0';
    printf("%s", Buffer);
    count += b;
    if (count > FileSize) {
      printf("  Error: received more bytes than expected.\n");
      return -9;   // Received more bytes than expected!
    }
  }

  return 1;
}




int Put(char* localname, char* target) {
  // Put a file on the robot.

  struct PutRequestStruct {
    char           CommandByte;
    char           FileSize[4]; // has to be array to avoid aligning
    unsigned char  NameLength;
    char           Name[256];
  } Request;

  int error, RequestSize, b;
  unsigned long count;
  FILE* file;
  char Buffer[BUFFERSIZE];
  struct stat Stat;

  error = stat(localname, &Stat);
  if ((error != 0) || (!S_ISREG(Stat.st_mode))) {
    return -1;  // Invalid filename
  }

  file = fopen(localname, "r");
  if (file == NULL) {
    return -2;  // Error opening file for reading
  }

  Request.CommandByte       = 'p';
  *(long*)Request.FileSize  = (long)Stat.st_size;
  Request.NameLength        = strlen(target);
  strcpy(Request.Name, target);


  RequestSize = Request.NameLength + 6;
  if (write(sock, &Request, RequestSize) < RequestSize) {
    fclose(file); return -3;   // Error writing request
  }

  b = read(sock, Buffer, 1);     // Receive response
  if (b < 1) {
    fclose(file); return -4;   // Error receiving response
  }
  if (Buffer[0] != '\0') {
    fclose(file); return 0;    // Server replied: Error creating file
  }
   
  count = 0;
  //printf("  Sending...");
  while (count < Stat.st_size) {
    b = fread(Buffer, 1, BUFFERSIZE, file);
    count += b;

    if ((error = write(sock, Buffer, b)) < 1) {
      return -5;             // Error transferring data
    }
  }
  fclose(file);

  // File sent. Awaiting aknowledgement...
  b = read(sock, Buffer, 1);
  if (b < 1) {
    return -6;  // Error receiving acknowledgement
  }
  if (Buffer[0] != 0) {
    return -7;  // Server replied: Error receiving
  }
  //printf("  Done.\n");
  return 1;
}



int Touch(char* filename) {
  // Self-expl.

  char buf[256];
  int  result;

  buf[0] = 't';
  buf[1] = strlen(filename);
  strcpy(buf+2, filename);
    
  if (ConnectIfNeeded_NoMsg() < 0) {
    return -4;
  }

  if (write(sock, &buf, 2+buf[1]) < 1) return -1;
 
  result = read(sock, buf, 1);
  if (result < 1) return -2;
  if (buf[0] != 0) return -3;

  return 0;
}



void List_PrintListEntries(char Type, unsigned long Size, char* Name, void* Dummy) {
  // Handler for List function used for debugging, not linked in actual program
  if (Type != '-') {
    printf("%c %8lu  %s\n", Type, Size, Name);
  }
}



void ConnectIfNeeded(void) {
  if (sock < 0) {
    if (TargetHost[0] == '\0') {
      printf("Reload: Error: Target not specified!\n");
      exit(1);
    }
    sock = Timeout_Connect(TargetHost, 50000, 8); // 8 sec timeout
        
    if (sock < 0) {
      printf("Reload: Error%s: %s\n", errcause[-sock], errstring(sock));
      exit(1);
    }
  } 
}


int ConnectIfNeeded_NoMsg(void) {
  if (sock < 0) {
    sock = Timeout_Connect(TargetHost, 50000, 8); // 8 sec timeout
  }
  // An error should be handled as in the "ConnectIfNeeded" function above
  return sock; 
}


int ConnectIfNeeded_Msg(void) {
  if (sock < 0) {
    if (TargetHost[0] == '\0') {
      printf("Not connected.\n");
      return sock;
    }
    sock = Timeout_Connect(TargetHost, 50000, 8); // 8 sec timeout
    if (sock < 0) { 
      printf("error%s: %s: %s\n", errcause[-sock], errstring(sock), TargetHost);
    } else {
      printf("Connected to %s.\n", TargetHost); 
    }
  }
  return sock; 
}



int CopyLocalFileToRemoteFile(char* FullSourceName, char* FullDestName) {
  // Yup.
  int Result;

  ConnectIfNeeded();

  Result = Put(FullSourceName, FullDestName);

  if (Result < 1) {
    printf("Error putting file: %s\n", PutErrStr[-Result]);
    printf(" -> %s\n", FullSourceName);
    return -1;
  }

  return 0;
}



int CopyRemoteFileToLocalFile(char* FullSourceName, char* FullDestName) {
  // Like the name says.
  int Result;

  ConnectIfNeeded();

  Result = Get(FullSourceName, FullDestName);
 
  if (Result < 1) {
    printf("Error getting file: %s\n", GetErrStr[-Result]);
    printf(" -> %s\n", FullSourceName);
    return -1;
  }

  return 0;
}


int CopyFileAs(char* FullSourceName, char* FullDestName) {
  // General version that can be used both from Aibo to PC and vice versa.
  // It will figure out which and call one of the two functions above.

  bool SourceRemote, DestRemote;

  SourceRemote = (strncasecmp(FullSourceName, "/ms/", 4) == 0);
  DestRemote   = (strncasecmp(FullDestName,   "/ms/", 4) == 0);

  if (SourceRemote == DestRemote) return -1;
  if (SourceRemote) {
    return CopyRemoteFileToLocalFile(FullSourceName, FullDestName);
  } else {
    return CopyLocalFileToRemoteFile(FullSourceName, FullDestName);
  }
}



int MakeRemoteDir(char* FullDirName) {
  // Self-expl.

  char buf[256];
  int  result;

  buf[0] = 'm';
  buf[1] = strlen(FullDirName);
  strcpy(buf+2, FullDirName);
    
  ConnectIfNeeded();

  if (write(sock, &buf, 2+buf[1]) < 1) {
    printf("Warning: Error sending mkdir command!\n -> %s\n", FullDirName);
    return -1;
  }
  result = read(sock, buf, 1);
  if (result < 1) {
    printf("Warning: Error receiving mkdir response!\n -> %s\n", FullDirName);
    return -2;
  }
  if (buf[0] != 0) {
    printf("Warning: Server replied: Cannot make specified dir! -> %s\n, FullDirName", FullDirName);
    return -3;
  }

  return 0;
}



int MakeRemoteDir_NoMsg(char* FullDirName) {
  // Self-expl. No msgs printed.

  char buf[256];
  int  result;

  buf[0] = 'm';
  buf[1] = strlen(FullDirName);
  strcpy(buf+2, FullDirName);
    
  if (ConnectIfNeeded_NoMsg() < 0)     return -4;
  if (write(sock, &buf, 2+buf[1]) < 1) return -1;

  result = read(sock, buf, 1);

  if (result < 1)  return -2;
  if (buf[0] != 0) return -3;

  return 0;
}



int MakeLocalDir(char* FullDirName) {
  // Self-expl.

  int     Result;
  mode_t  mode = 0777;

  Result = mkdir(FullDirName, mode);
  return Result;
}




int RemoveRemoteFileOrDir(char* FullDestName, char Type) {
  // 'Type' is used to see if it's a file or a directory
  char Ack, CommandBuffer[256];
  int  Result;

  CommandBuffer[0] = 'd';                  // 'd' is for delete (files only)
  if (Type == 'D') CommandBuffer[0] = 'v'; // 'v' is for remove (dirs only)
  CommandBuffer[1] = strlen(FullDestName);
  strcpy(CommandBuffer + 2, FullDestName);

  if (write(sock, CommandBuffer, 2 + CommandBuffer[1]) < 1) {
    printf("Reload: Error sending command!\n -> Remove: %s\n", FullDestName);
    return -1;
  }
  Result = read(sock, &Ack, 1);
  if (Result < 1) {
    printf("Reload: Error receiving response!\n -> Remove: %s\n", FullDestName);
    return -2;
  }
  if (Ack != 0) {
    printf("Reload: Server replied: Cannot remove specified object!\n");
    printf(" -> Remove: %s\n", FullDestName);
    return -3;
  }

  return 0;
}




int RemoveRemoteFileOrDir_NoMsg(char* FullDestName, char Type) {
  // 'Type' is used to see if it's a file or a directory
  char Ack, CommandBuffer[256];
  int  Result;

  CommandBuffer[0] = 'd';                  // 'd' is for delete (files only)
  if (Type == 'D') CommandBuffer[0] = 'v'; // 'v' is for remove (dirs only)
  CommandBuffer[1] = strlen(FullDestName);
  strcpy(CommandBuffer + 2, FullDestName);

  if (write(sock, CommandBuffer, 2 + CommandBuffer[1]) < 1) return -1;

  Result = read(sock, &Ack, 1);
  if (Result < 1) return -2;
  if (Ack != 0) return -3;

  return 0;
}




int RemoveLocalFileOrDir(char* FullDestName) {
  //For local disk, we don't have to specify whether a file it'is a file of a directory 

  char answer[256];
  int  Result;

  Result = remove(FullDestName);
  return Result;
}




void Reboot(void) {
  // Send reboot command
  char Command = 'r';
  ConnectIfNeeded();
  if (write(sock, &Command, 1) < 1) {
    printf("Reload: Error sending Reboot command.\n");
    exit(1);
  }
  printf("Aibo rebooting...\n");
}


int Reboot_NoMsg(void) {
  // Send reboot command
  char Command = 'r';
  if (write(sock, &Command, 1) < 1) return -1;
  return 0;
}


void Shutdown(void) {
  // Send shutdown command
  char Command = 'x';
  ConnectIfNeeded();
  if (write(sock, &Command, 1) < 1) {
    printf("Reload: Error sending Shutdown command.\n");
    exit(1);
  }
  printf("Aibo shutting down.\n");
}


int Shutdown_NoMsg(void) {
  // Send shutdown command
  char Command = 'x';
  if (write(sock, &Command, 1) < 1) return -1;
}



int SetClock(void) {
  // Synchronizes date, time and timezone with Aibo

  char buf[6];
  int Result;
  struct timeval tv;
  struct timezone tz;
  gettimeofday(&tv, &tz);
  tv.tv_sec -= 946684800;  // offset to year 2000 (unix uses 1970)

  ConnectIfNeeded();

  buf[0] = 's';            // 's' is the command for 'setclock'

  ((long*)(buf+1))[0] = (long)tv.tv_sec;   // Load time in seconds
  buf[5] = (char)(tz.tz_minuteswest / 60); // Load offset hours
    
  if (write(sock, &buf, 6) < 1) {
    printf("Warning: Error sending SetClock command!\n");
    return -1;
  }

  Result = read(sock, buf, 1);
  if (Result < 1) {
    printf("Warning: Error receiving SetClock response!\n");
    return -2;
  }

  if (buf[0] != 0) {
    printf("Warning: Server replied: Could not set time!\n");
    return -3;
  }

  printf("Remote clock set.\n");
  return 0;
}





void unbuffer_tty()
{
    system("stty -icanon -echo ctlecho stop '' eol ^A");
    fflush(stdin);
}


void reset_tty()
{
    system("stty icanon echo -ctlecho stop ^S eol ^@");
}


void OnExit() {
  reset_tty();
  CloseIfConnected();
}


void OnCtrlC() {
  printf("\n");
  exit(-1);
}



  // Definitions and declarations

  typedef enum {S_START, S_1B, S_1B_4F, S_1B_5B, S_1B_5B_33} State;  
  typedef enum {START, REACHED_AMBIGUITY, PRESSED_ON_AMBIGUITY, PRESSED} TabState;
  typedef enum {CHOMP_DIR, PARENT_DIR, THIS_DIR, NO_CHOP} FullpathType;  
  typedef struct {
    char* name;  // The name of the object 
    char  type;  // The type of object -- F: File, D: dir
  } TabListItem;


  #define MAX_HISTORY      100
  #define MAX_CMDSIZE      512
  #define MAX_DIRSIZE      512

  // At least this many entries will be allocated in the tab lists, and
  // if more are needed, they will be allocated this many at a time

  #define ALLOC_CHUNK_SIZE 100


  // Global history vars

  char*    history [MAX_HISTORY];

  char     command [MAX_CMDSIZE];
  char     input   [MAX_CMDSIZE];
  char     token   [MAX_CMDSIZE];

  char     rem_dir [MAX_DIRSIZE];
  char     loc_dir [MAX_DIRSIZE];
  char     tmp_dir [MAX_DIRSIZE];
  char     tab_token_buf[MAX_CMDSIZE];

  int      hist_str = 0;
  int      newest_cmd = 0;
  int      oldest_cmd = 0;
  int      curr_pos;
  int      tab_start, tab_end;
  State    state;
  
  TabState tab_state;
  TabListItem*  tab_list    = NULL; // The full list (i.e: directory list) to search              
  TabListItem*  tab_matches = NULL; // The matching char*'s from the tab_list
  char*    tab_matches_type = NULL; // The type of objects in tab_matches -- F or D 
  int      tab_list_length  = 0;    // How many do we have in tab_list?
  int      tab_matches_length = 0;  // How many do we have in tab_matches?
  bool     tab_local        = true; // Will tab completion be local or remote?
  char*    tab_complete     = NULL; // String to be added as result of tab completion

char* lc(char* str) {
  char* s = str;
  while ((s != NULL) && (*s != '\0')) {
    *s++ = tolower(*s);
  }
  return str;
}



char* fullpath(char* dest, char* root, char* leaf, FullpathType type) {
  char *c, *d;

  *dest = '\0';

  // add in the root dir if appropriate
  if ((leaf == NULL) || ((leaf[0] != '/') && (leaf[0] != '~'))) {
    strcpy(dest, root);

    // if there is no slash at the end, add one
    char* c = &dest[strlen(root)];
     if ((--c >= dest) && (*c != '/')) {
       strcat(dest, "/");
    }
  }

  if (leaf != NULL) {
    // if home dir is used, fill it in
    if (leaf[0] == '~') {
      leaf++;
      strcat(dest, getenv("HOME"));
      if (*leaf != '/') strcat(dest, "/");
    }

    strcat(dest, leaf);
  }


  /*
  CHOMP_DIR  : Remove '/'s at the end of string
  THIS_DIR   : Leave last '/' and put end of string right after  
  PARENT_DIR : Replace last '/' with end of string
  NO_CHOP    : Do none of the above
  */
  
  if (type == THIS_DIR) {
    c = strrchr(dest, '/');
    if (c != NULL) *(++c) = '\0';
  }

  if (type == CHOMP_DIR) {
    c = dest + strlen(dest) - 1;
    while ((*c == '/') && (c > dest)) *c-- = '\0';
  }

  if (type == PARENT_DIR) {
    c = strrchr(dest, '/');
    if (c != NULL) *c = '\0';
  }


  return dest;
}



char* fullpath_remote(char* dest, char* root, char* leaf, FullpathType type) {
  char* c;

  *dest = '\0';

  // add in the root dir if appropriate
  if ((leaf == NULL) || ((leaf[0] != '/') && (leaf[0] != '~'))) {
    strcpy(dest, root);

    // put a slash at the end if we need to merge it with leaf path
    if ((leaf != NULL) && (*leaf != '\0')) {
      char* c = &dest[strlen(root)];
      if ((--c >= dest) && (*c != '/')) {
         strcat(dest, "/");
      }
    }
  }

  if (leaf != NULL) {
    // if home dir is used, fill it in
    if (leaf[0] == '~') {
      leaf++;
    }

    if ((leaf[0] == '/') && (strncmp(leaf, "/ms", 3) != 0)) {
        strcat(dest, "/ms/");
        leaf++;
    }

    strcat(dest, leaf);
  }


  /*
  CHOMP_DIR  : Remove '/'s at the end of string
  THIS_DIR   : Leave last '/' and put end of string right after  
  PARENT_DIR : Replace last '/' with end of string
  NO_CHOP    : Do none of the above
  */
  
  if (type == THIS_DIR) {
    c = strrchr(dest, '/');
    if (c != NULL) *(++c) = '\0';
  }

  if (type == CHOMP_DIR) {
    c = dest + strlen(dest) - 1;
    while ((*c == '/') && (c > dest)) *c-- = '\0';
  }

  if (type == PARENT_DIR) {
    c = strrchr(dest, '/');
    if (c != NULL) *c = '\0';
  }


  // This is the remote version: check for invalid strings that
  // don't start with '/ms' or '/ms/' and fix them

  if (strcmp(dest, "/ms") == 0) return dest;   // This is good.
  if (strncmp(dest, "/ms/", 4) != 0) {         // This is not.
    if ((*dest == '/') || (*dest == '\0')) {
       // let's add '/ms' if no dir or if it starts with '/' already
       memmove(dest+3, dest, strlen(dest)+1);
       strncpy(dest, "/ms", 3);
    } else {
       // let's add '/ms/' is there is no slash
       memmove(dest+4, dest, strlen(dest)+1);
       strncpy(dest, "/ms/", 4);
    }
  }

  return dest;
}



char* nameonly(char* name) {
  char* c;
  if ((c = strrchr(name, '/')) == NULL) {
    return name;
  } else {
    return ++c;
  }
}


void List_LS(char Type, unsigned long Size, char* Name, void* Dummy) {
  // Handler for List function used for ls command

  static bool first = true;

  if (first) {
    first = false;
    return; 
  }

  if (Type == '-') {
    first = true;
    return;
  }

  int len = strlen(rem_dir);
  if (strncasecmp(rem_dir, Name, len) != 0) {
     printf("--> Error: Unexpected path!\n");
     return;
  }

  switch(Type) {  

  case 'F' :
    printf("---------- 1 root  root %8u              %s\n",  Size, nameonly(lc(Name + len + 1)));
    break;

  case 'D' :
    printf("d--------- 1 root  root %8u              %s/\n", Size, nameonly(lc(Name + len + 1)));
    break;

  default:
    printf("--> Error: Unexpected file type!\n");
    break;

  }

}



int listcmp(const void* a, const void* b) {
  return strcmp(((TabListItem*)a)->name, ((TabListItem*)b)->name);
}



void tab_clear_list() {

  int i;
  while (tab_list_length > 0) {
    free(tab_list[--tab_list_length].name);
  }
  free(tab_list);
  tab_list      = NULL;
}


void tab_get_list_local(char* dir) {
// This fills in the tab_list[] array and set tab_list_length
// from the local host

  DIR*            thisdir;
  struct dirent*  entry;
  char            type;
  int             error;
  int             count  = 0;
  int             chunks = 0;
  int             total  = 0;


  // destroy previous strings;
  tab_clear_list();

  if ((thisdir = opendir(dir)) == NULL) return;

  while ((entry = readdir(thisdir)) != NULL) {

    // allocate new memory if we just ran out
    if (count++ == total) {
      total    = ++chunks * ALLOC_CHUNK_SIZE;
      tab_list = realloc(tab_list, total*sizeof(TabListItem));
    }

    type = 'F';
    if (entry->d_type == DT_DIR) {

      // If this is a directory, add a slash to its name
      type = 'D';
      tab_list[tab_list_length].name = malloc(strlen(entry->d_name) + 2);
      strcpy(tab_list[tab_list_length].name, entry->d_name);
      strcat(tab_list[tab_list_length].name, "/");

    } else {
      tab_list[tab_list_length].name = strdup(entry->d_name);
    }

    tab_list[tab_list_length++].type = type;

  }
  closedir(thisdir);
}


void tab_display_all_matches() {
  
  int term_rows, term_cols;
  int list_rows, list_cols;
  int i, r, c, len, max = 0;
  int complete_rows;

  //    printf("*%d:%d::%d:%d*\n", tab_list_length, tab_matches_length, r, complete_rows );


  // Get current rows and columns
  FILE* stty = popen("stty size", "r");
  fscanf(stty, "%d %d", &term_rows, &term_cols);
  pclose(stty);

  // Find max length of a match
  for (i = 0; i < tab_matches_length; i++) {
    len = strlen(tab_matches[i].name);
    if (len > max) {max = len;} 
  }

  // Compute number of rows and cols for the listing
  max += 2;  // Consider two blank spaces when displaying matches
  list_cols = term_cols / max;
  list_rows = numchunks(tab_matches_length, list_cols);

  for (r = 0; r < list_rows; r++) {
    for (c = 0; c < list_cols; c++) {
      if ((r + c*list_rows) < tab_matches_length) {
        printf("%-*s", max, tab_matches[r + c*list_rows].name);
      }
    }
    printf("\n");
  }

  // Done! :)
}




void tab_get_list_remote(char* dir) {
// This fills in the tab_list[] array and set tab_list_length
// from the remote host

  int             count  = 0;
  int             chunks = 0;
  int             total  = 0;
  bool            first  = 1;

  // destroy previous strings;
  tab_clear_list();

  // if we are not connected, don't try to connect, just return an empty list
  if (sock < 0) return;

  void List_make_tab_list(char Type, unsigned long Size, char* Name, void* Dummy) {
  // Handler for List function used to fill in the tab_list[]
  // array and its tab_list_length for tab completion

    // Skip end and beginning record

    if (first) {
      first = false;
      return; 
    }

    if (Type == '-') {
      first = true;
      return;
    }

    if ((Type == '-') || (strlen(Name) == strlen(dir))) return; 

    // allocate new memory if we just ran out
    if (count++ == total) {
      total    = ++chunks * ALLOC_CHUNK_SIZE;
      tab_list = realloc(tab_list, total*sizeof(TabListItem));
    }

    // Add record
    tab_list[tab_list_length].name = strdup(nameonly(lc(Name)));
    if (Type == 'D') strcat(tab_list[tab_list_length].name, "/");
    tab_list[tab_list_length++].type = Type;

  }

//  printf("\nListing %s...\n", dir);

  List(dir, 1, List_make_tab_list, NULL);

}



void tab_get_list_commands() {
// This fills in the tab_list[] array and set tab_list_length
// from the list of commands

#define ADD_COMMAND(x) tab_list[tab_list_length].name = strdup(#x); \
                       tab_list[tab_list_length++].type = 'F';

  // destroy previous strings;
  tab_clear_list();

  // allocate mem for enough commands
  tab_list = realloc(tab_list, 50*sizeof(TabListItem));

  ADD_COMMAND(!);
  ADD_COMMAND(bye);
  ADD_COMMAND(cat);
  ADD_COMMAND(cd);
  ADD_COMMAND(cdup);
  ADD_COMMAND(close);
  ADD_COMMAND(delete);
  ADD_COMMAND(dir);
  ADD_COMMAND(get);
  ADD_COMMAND(help);
  ADD_COMMAND(lcd);
  ADD_COMMAND(ls);
  ADD_COMMAND(mkdir);
  ADD_COMMAND(open);
  ADD_COMMAND(put);
  ADD_COMMAND(pwd);
  ADD_COMMAND(quit);
  ADD_COMMAND(reboot);
  ADD_COMMAND(rm);
  ADD_COMMAND(rmdir);
  ADD_COMMAND(shutdown);
  ADD_COMMAND(touch);


}



int numchunks(int total, int chunksize) {
// Returns how many chunks of chunksize bytes must be allocated
// to house the given total number of bytes

  int i = total / chunksize;
  if (total % chunksize) i++;
  return i;

}



int strncom(char* str1, char* str2, int max) {
// Returns the number of chars in common between str1
// and str2, strarting from the beginning and limiting
// the search to the specified maximum value

  int com = 0;
  while ((*str1 != '\0') && (*str1++ == *str2++) && (com < max)) com++;
  return com;

}


char* tab_token(char*str, int pos) {
// Isolate the token from the string.
  char* token;
  strcpy(tab_token_buf, str);                  // copy the string
  tab_token_buf[pos] = 0;                      // end it where the cursor is
  token = strrchr(tab_token_buf, ' ');         // find last space from there
  if (token++ == NULL) token = tab_token_buf;  // return beginning or just after space
  memmove(tab_token_buf, token, strlen(token)+1);
}


char* tab_search(char* token) {
// This searches the tab_list for match with the token right before
// the cursor position, pos. It returns the type of match and puts
// the remaining string to be printed for the completion in buf.

  int   i, len, matches;
  int   max = 10000000;  // max length in common. start very big :)
  int   chunks  = numchunks(tab_matches_length, ALLOC_CHUNK_SIZE);
  int   total   = chunks * ALLOC_CHUNK_SIZE;
  
  matches = 0;
  len = strlen(token);

  // search tab_list -- first pass: store items that start with str
  for (i = 0; i < tab_list_length; i++) {

    // skip if it doesn't start with our string
    if (strncmp(token, tab_list[i].name, len) != 0) continue;

    // we have found a match
    // allocate new memory if we just ran out
    if (matches == total) {
        total = ++chunks * ALLOC_CHUNK_SIZE;
        tab_matches      = realloc(tab_matches, total*sizeof(TabListItem));
    }

    // store item, and increment matches count
    tab_matches[matches++] = tab_list[i];
  }

  // second pass: figure out common completion
  if (matches == 0) {
    max = 0;
  } else {
    for (i = 0; i < matches; i++) {
      len = strncom(tab_matches[0].name, tab_matches[i].name, max);
      if (len < max) max = len;
    }
  }

  // qsort the matches in alphabetical order
  qsort(tab_matches, matches, sizeof(TabListItem), listcmp);

  // set the tab_complete string to the string the program should print
  free(tab_complete);
  len = strlen(token);
  if (matches == 0) {
    tab_complete = strdup("");
  } else {
    tab_complete = (char*)strndup(&tab_matches[0].name[len], max-len);
  }

  // reduce memory for matches if what was there before is now too big 
  i = numchunks(tab_matches_length, ALLOC_CHUNK_SIZE);
  if (i > chunks) {
    tab_matches = realloc(tab_matches, chunks*ALLOC_CHUNK_SIZE*sizeof(TabListItem));
  }

  // update length variable and return completion string
  tab_matches_length = matches;
  return tab_complete;

}



int older_hist(int history_index) {
  if (history_index == 0) history_index = MAX_HISTORY;
  return --history_index;
}

int newer_hist(int history_index) {
  if (++history_index == MAX_HISTORY) history_index = 0;
  return history_index;
}


char* str_addchr(char* str, char c, int pos) {
  int len = strlen(str);
  memmove(&str[pos+1], &str[pos], len - pos + 1);
  str[pos] = c;
}

char* str_delchr(char* str, int pos) {
  int len = strlen(str);
  memmove(&str[pos], &str[pos+1], len - pos);
}


char* get_line(char* dest, bool iscommand, int maxsize) {

  char c;
  int i, len;

  hist_str = newest_cmd;
  state = S_START;

  // Start with a fresh empty string in our working buffer
  dest[0] = '\0';
  curr_pos = 0;
    

  while (1) {

    c = getchar();

    if (c != 0x09) tab_state = START;

    switch (state) {

    case S_START:

        switch (c) {

        case 0x1B : state = S_1B; break;

        case 0x09 : /* TAB */
            switch (tab_state) {
                case START :
                    {
                    // check if we want to complete a command
                    char* firstspace = strchr(dest, ' ');
                    char* token = tab_token(dest, curr_pos);

                    if ((firstspace == NULL) || (firstspace > &dest[curr_pos])) {
                      tab_get_list_commands();
                      
                    } else {


                      strcpy(tmp_dir, dest);
                      *strchr(tmp_dir, ' ') = 0;

                      if ((strcmp(tmp_dir, "cat")    == 0) ||
                          (strcmp(tmp_dir, "cd")     == 0) ||
                          (strcmp(tmp_dir, "del")    == 0) ||
                          (strcmp(tmp_dir, "dele")   == 0) ||
                          (strcmp(tmp_dir, "delete") == 0) ||
                          (strcmp(tmp_dir, "dir")    == 0) ||
                          (strcmp(tmp_dir, "get")    == 0) ||
                          (strcmp(tmp_dir, "ls")     == 0) ||
                          (strcmp(tmp_dir, "mkd")    == 0) ||
                          (strcmp(tmp_dir, "mkdir")  == 0) ||
                          (strcmp(tmp_dir, "rm")     == 0) ||
                          (strcmp(tmp_dir, "rmdir")  == 0) ||
                          (strcmp(tmp_dir, "rmd")    == 0) ||
                          (strcmp(tmp_dir, "touch")  == 0))
                      {
                        tab_get_list_remote(fullpath_remote(tmp_dir, rem_dir, token, THIS_DIR));
                      } else {
                        tab_get_list_local (fullpath(tmp_dir, loc_dir, token, THIS_DIR));
                      }
                    }

                    char* add  = tab_search(nameonly(token));

                    // If no matchs found, do nothing, just exit
                    if (tab_matches_length == 0) break;

                    // Get copy of current position to update the display later
                    int old_pos = curr_pos;

                    // If there are matches, complete to last unambiguous char
                    for (i = 0; add[i] != '\0'; i++) {
                      str_addchr(dest, add[i], curr_pos++);
                    }
                    
                    // Compute how many times we'll have to back the cursor up
                    i = strlen(&dest[old_pos]) - strlen(add);


                    // If it was only one *file* match and we are at end of line, add space
                    if ((tab_matches_length == 1) && (tab_matches[0].type == 'F') &&
                        (dest[curr_pos] == '\0')) {
                      str_addchr(dest, ' ', curr_pos++);
                      i--;  // So we don't back up over the added space.
                    }

                    // Print new string on terminal
                    printf("%s", &dest[old_pos]);

                    // Backup up by the number of positions computer above
                    while (i-- > 0) printf("\b");

                    // If there was only one match, we're done. Else, it's an ambiguity.
                    if (tab_matches_length == 1) break;

                    // if we were *already* on ambiguity, set state to PRESSED_ON_AMBIGUITY
                    // (No chars to add means we were already there)
                    if (add[0] == '\0') { 
                      tab_state = PRESSED_ON_AMBIGUITY;
                    } else {
                      // else, set to REACHED_AMBIGUITY
                      tab_state = REACHED_AMBIGUITY;
                    }

                    break;
                    }

                case REACHED_AMBIGUITY :
                    // Nothing to do-- don't move cursor, set to PRESSED_ON_AMBIGUITY
                    tab_state = PRESSED_ON_AMBIGUITY;
                    break;

                case PRESSED_ON_AMBIGUITY :
                    // display complete list of matches
                    printf("\n");
                    tab_display_all_matches();
                    printf("arf> %s", dest);
                    for (i = strlen(dest); i > curr_pos; i--) printf("\b");
                    break;
                default: break;
            }
            break;

        case 0x7F : /* BACKSPACE */
            if (curr_pos) {
                str_delchr(dest, --curr_pos);
                printf("\b%s ", &dest[curr_pos]);
                for (i = strlen(dest); i >= curr_pos; i--) printf("\b");
            }
            break;

        case 0x0D : c = 0x0A;

        case 0x0A : /* ENTER */
            printf("\n");
            if (iscommand) {
              history[newest_cmd++] = strdup(dest);
            }
            // if we got enter, exit this routine returning the string 
            return dest;

        default   :
            // printf("%2X ", c); break;
            if (curr_pos < maxsize) {
              str_addchr(dest, c, curr_pos++);
              printf("%c%s", c, &dest[curr_pos]);
              for (i = strlen(dest); i > curr_pos; i--) printf("\b");
            }
            break;
        }
        break;

    case S_1B:

        switch (c) {

        case 0x4F : state = S_1B_4F; break;
        case 0x5B : state = S_1B_5B; break;
        default   : printf("S_1B+%2X ", c); state = S_START; break;

        }
        break;


    case S_1B_4F:

        switch (c) {

        case 0x41 : /* UP    */
             if (iscommand && (hist_str != oldest_cmd)) {
                 // Go to start of line; wipe line; go back again;
                 for (i = curr_pos; i > 0; i--) printf("\b");
                 for (i = 0; i < strlen(dest); i++) printf(" ");
                 for (i ; i > 0; i--) printf("\b");

                 // Save current buffer if we haven't aready done so
                 if (hist_str == newest_cmd) {
                     free(history[newest_cmd]);
                     history[newest_cmd] = strdup(dest);
                 }

                 // Update history string pointer and copy dest to buffer
                 hist_str = older_hist(hist_str);
                 strcpy(dest, history[hist_str]); 
                 curr_pos = strlen(dest);
                 printf("%s", dest);

             }
             state = S_START;
             break;

        case 0x42 : /* DOWN  */
             if (iscommand && (hist_str != newest_cmd)) {
                 // Go to start of line; wipe line; go back again;
                 for (i = curr_pos; i > 0; i--) printf("\b");
                 for (i = 0; i < strlen(dest); i++) printf(" ");
                 for (i ; i > 0; i--) printf("\b");

                 // Update history string pointer and copy dest to buffer
                 hist_str = newer_hist(hist_str);
                 strcpy(dest, history[hist_str]); 
                 curr_pos = strlen(dest);
                 printf("%s", dest);
             }
             state = S_START;
             break;

        case 0x43 : /* RIGHT */
            if (curr_pos < strlen(dest)) {
                printf("%c", dest[curr_pos++]);
            }
            state = S_START;
            break;

        case 0x44 : /* LEFT  */
            if (curr_pos) {
                curr_pos--;
                printf("\b");
            }
            state = S_START;
            break;

        default   : printf("S_1B_4F+%2X ", c); state = S_START; break;

        }
        break;


    case S_1B_5B:

        switch (c) {

        case 0x33 : state = S_1B_5B_33; break;
        default   : printf("S_1B_5B+%2X ", c); state = S_START; break;

        }
        break;


    case S_1B_5B_33:

        switch (c) {

        case 0x7E : /* DELETE */
            if (curr_pos < strlen(dest)) {
                str_delchr(dest, curr_pos);
            }
            printf("%s ", &dest[curr_pos]);
            for (i = strlen(dest); i >= curr_pos; i--) printf("\b");

            state = S_START; break;
            
        default   : state = S_START; break;

        }
        break;


    default:
        break;

    }

  }

}





char* get_command_line() {
  return get_line(command, true, MAX_CMDSIZE);
}


char* get_input_line() {
  return get_line(input, false, MAX_CMDSIZE);
}



int main(int argc, char* argv[]) {

  // Init history to all NULLs.
  memset(history, 0, sizeof(history));


  // Register the function to be called on exit
  if (atexit(OnExit) == -1 ) {
    perror("atexit");
    return -1;
  }

  // Register the function to be called on Ctrl-C
  signal(SIGINT, OnCtrlC);

  // Set the terminal to unbuffered, raw mode to read all keys
  unbuffer_tty();


  // Try to connect if a parameter is given on the command line
  if (argc > 1) {
    strcpy(TargetHost, argv[1]);
    ConnectIfNeeded_Msg();
  }

  // Set dirs to initial values
  getcwd(loc_dir, MAX_DIRSIZE);
  strcpy(rem_dir, "/ms");

  // Main command loop. Output prompt, get command, execute.

  while (1) {

    printf("arf> ");
    get_command_line();
    
    // Make a copy of the command to parse it
    strcpy(token, command);

    // Try again if no command was entered
    if (strtok(token, " \t") == NULL) continue;




    /***** <SHELL> *****/
    if (command[0] == '!') {
      if ((strtok(NULL, " \t") == NULL) && (strcmp(token, "!") == 0)) {
        if (getenv("SHELL") == NULL) {
          printf("The SHELL environment variable is not set.\n");
          continue;
        }
        strcpy(input, getenv("SHELL"));  
      } else {
        strcpy(input, &command[1]);  
      }
      reset_tty();
      system(input);
      unbuffer_tty();
      continue;
    }



    /***** HELP *****/
    if (strcmp(token, "help") == 0) {
      tab_get_list_commands();
      tab_search("");
      qsort(tab_list, tab_list_length, sizeof(TabListItem), listcmp);
      printf("Aibo Reload Ftp (ARF). Commands are:\n\n");

      tab_display_all_matches();

      printf("\n");

/*
      printf("!               close           lcd             put             shutdown\n"
             "bye             delete          ls              pwd             touch   \n"
             "cat             dir             mdir            quit                    \n"
             "cd              get             mkdir           reboot                  \n"
             "cdup            help            open            rm                      \n\n");
*/

      continue;
    }



    /***** OPEN *****/
    if (strcmp(token, "open") == 0) {
      if (sock >= 0) {
        printf("Already connected to %s, use close first.\n", TargetHost);
      } else {
        char* remotehost = strtok(NULL, " \t");
        if (remotehost == NULL) {
          printf("(to) ");
          get_input_line();
          remotehost = strtok(input, " \t");
          if (remotehost == NULL) {
            printf("usage: open host-name\n");
            continue;
          }
        }
        strcpy(TargetHost, remotehost); 
        ConnectIfNeeded_Msg();
      }
      continue;
    }



    /***** LS  *****/
    /***** DIR *****/
    if ((strcmp(token, "ls") == 0) || (strcmp(token, "dir") == 0)) {

      char* userinput = strtok(NULL, " \t");
      fullpath_remote(tmp_dir, rem_dir, userinput, CHOMP_DIR);

      if (ConnectIfNeeded_Msg() < 0) continue; 
      List(tmp_dir, 1, List_LS, NULL);
      continue;
    }



    /***** CD *****/
    if (strcmp(token, "cd") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("(remote-directory) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: cd remote-directory\n");
          continue;
        }
      }

      if (ConnectIfNeeded_Msg() < 0) continue; 

      if (strcmp(userinput, "..") == 0) {
        fullpath_remote(tmp_dir, rem_dir, NULL, CHOMP_DIR);     // Remove last '/' if any
        fullpath_remote(tmp_dir, rem_dir, NULL, PARENT_DIR);    // Cut to previous dir
      } else {
        fullpath_remote(tmp_dir, rem_dir, userinput, CHOMP_DIR);
      }

      if (List(tmp_dir, 0, NULL, NULL) == 0) {
        printf("Access denied (invalid directory).\n");
      } else {
        strcpy(rem_dir, tmp_dir);
        printf("\"%s\" is current directory.\n", rem_dir);
      }
      continue;
    }


    /***** CDUP *****/
    if (strcmp(token, "cdup") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      strcpy(tmp_dir, rem_dir);
      if (tmp_dir[strlen(tmp_dir) - 1] == '/') {
         tmp_dir[strlen(tmp_dir) - 1] = '\0';
      }
      char* parent = strrchr(tmp_dir, '/');
      if (parent != tmp_dir) {
        *parent = '\0';
      }

      if (ConnectIfNeeded_Msg() < 0) continue; 

      if (List(tmp_dir, 0, NULL, NULL) == 0) {
        printf("Access denied (invalid directory).\n");
      } else {
        strcpy(rem_dir, tmp_dir);
        printf("\"%s\" is current directory.\n", rem_dir);
      }
      continue;
    }



    /***** LCD *****/
    if (strcmp(token, "lcd") == 0) {
      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("Local directory now %s\n", loc_dir);
        continue;
      }

      if (userinput[0] == '~') {
        sprintf(tmp_dir, "%s%s", getenv("HOME"), userinput+1);
        printf("dir: *%s*\n", tmp_dir);
      } else {
        strcpy(tmp_dir, userinput);
      }

      if (chdir(tmp_dir) != 0) {
        printf("local: %s: No such file or directory\n", userinput);
      } else {
     
        getcwd(loc_dir, MAX_DIRSIZE);
        printf("Local directory now %s\n", loc_dir);
      }
      continue;
    }



    /***** PWD *****/
    if (strcmp(token, "pwd") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      printf("\"%s\" is current directory.\n", rem_dir);
      continue;
    }



    /***** GET *****/
    if (strcmp(token, "get") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      // 'input' will be our remote filename and 'tmp_dir' the local one  
      char local_file [256];
      char remote_file[256];
      char* userinput = strtok(NULL, " \t");
      if (userinput != NULL) {
        strncpy(remote_file,  userinput, 256);
        strncpy(local_file, nameonly(userinput), 256);
      } else {
        printf("(remote-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: get remote-file [ local-file ]\n");
          continue;
        }
        strncpy(remote_file,  userinput, 256);
        printf("(local-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput == NULL) {
          strncpy(local_file, remote_file, 256);
        } else {
          strncpy(local_file, userinput, 256);
        }
      }

      fullpath_remote(input,   rem_dir, remote_file, NO_CHOP);
      fullpath       (tmp_dir, loc_dir, local_file,  NO_CHOP);

      if (ConnectIfNeeded_Msg() < 0) continue; 

      int result = Get(input, tmp_dir);
      if (result == -2) {
        printf("local: %s: Unable to create file.\n", local_file);
      } else if (result == -5) {
        printf("Access denied: File not found.\n");
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("Transfer complete.\n");
      }
      continue;
    }



    /***** CAT *****/
    if (strcmp(token, "cat") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      // 'input' will be our remote filename  
      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("(remote-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: cat remote-file\n");
          continue;
        }
      }

      fullpath_remote(input,   rem_dir, userinput, NO_CHOP);

      if (ConnectIfNeeded_Msg() < 0) continue; 

      int result = Cat(input);
      if (result == -5) {
        printf("Access denied: File not found.\n");
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        // printf("Transfer complete.\n");
      }
      continue;
    }



    /***** PUT *****/
    if (strcmp(token, "put") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      // 'input' will be our local filename and 'tmp_dir' the remote one  
      char local_file [256];
      char remote_file[256];
      char* userinput = strtok(NULL, " \t");
      if (userinput != NULL) {
        strncpy(local_file,  userinput, 256);
        strncpy(remote_file, nameonly(userinput), 256);
      } else {
        printf("(local-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: put local-file [ remote-file ]\n");
          continue;
        }
        strncpy(local_file,  userinput, 256);
        printf("(remote-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput == NULL) {
          strncpy(remote_file, local_file, 256);
        } else {
          strncpy(remote_file, userinput, 256);
        }
      }

      fullpath       (input,   loc_dir, local_file,  NO_CHOP);
      fullpath_remote(tmp_dir, rem_dir, remote_file, NO_CHOP);

      sprintf(input,   "%s/%s", loc_dir, local_file);
      sprintf(tmp_dir, "%s/%s", rem_dir, remote_file); 

      if (ConnectIfNeeded_Msg() < 0) continue; 

      int result = Put(input, tmp_dir);
      if (result == -1) {
        printf("local: %s: No such file or directory\n", local_file);
      } else if (result == -3) {
        printf("%s: Permission denied.\n", remote_file);
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("Transfer complete.\n");
      }
      continue;
    }



    /***** TOUCH *****/
    if (strcmp(token, "touch") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("(remote-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: %s remote-file\n", token);
          continue;
        }
      }
      sprintf(tmp_dir, "%s/%s", rem_dir, userinput);

      if (ConnectIfNeeded_Msg() < 0) continue; 
      int result = Touch(tmp_dir);

      if (result == -3) {
        printf("%s: Permission denied.\n", userinput);
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("File touched.\n");
      }
      continue;
    }



    /***** MKDIR *****/
    /*****  MKD  *****/
    if ((strcmp(token, "mkdir") == 0) || (strcmp(token, "mkd") == 0)) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("(directory-name) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: %s directory-name\n", token);
          continue;
        }
      }
      sprintf(tmp_dir, "%s/%s", rem_dir, userinput);

      if (ConnectIfNeeded_Msg() < 0) continue; 

      int result = MakeRemoteDir_NoMsg(tmp_dir);
      if (result == -3) {
        printf("%s: Permission denied.\n", userinput);
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("MKD command successful.\n");
      }
      continue;
    }



    /***** RMDIR *****/
    /*****  RMD  *****/
    if ((strcmp(token, "rmdir") == 0) || (strcmp(token, "rmd") == 0)) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("(directory-name) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: %s directory-name\n", token);
          continue;
        }
      }
      sprintf(tmp_dir, "%s/%s", rem_dir, userinput);

      if (ConnectIfNeeded_Msg() < 0) continue; 

      int result = RemoveRemoteFileOrDir_NoMsg(tmp_dir, 'D');
      if (result == -3) {
        printf("%s: Permission denied.\n", userinput);
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("RMD command successful.\n");
      }
      continue;
    }


    /***** DELETE *****/
    /*****  DELE  *****/
    /*****   DEL  *****/
    /*****    RM  *****/
    if ((strcmp(token, "delete") == 0) || (strcmp(token, "dele") == 0) ||
        (strcmp(token, "del")    == 0) || (strcmp(token, "rm")   == 0)) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      char* userinput = strtok(NULL, " \t");
      if (userinput == NULL) {
        printf("(remote-file) ");
        get_input_line();
        userinput = strtok(input, " \t");
        if (userinput /*still*/ == NULL) {
          printf("usage: %s remote-file\n", token);
          continue;
        }
      }
      sprintf(tmp_dir, "%s/%s", rem_dir, userinput);

      if (ConnectIfNeeded_Msg() < 0) continue; 

      int result = RemoveRemoteFileOrDir_NoMsg(tmp_dir, 'F');
      if (result == -3) {
        printf("%s: No such file on remote host.\n", userinput);
      } else if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("DELE command successful.\n");
      }
      continue;
    }



    /***** CLOSE *****/
    if (strcmp(token, "close") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      if (sock >= 0) {
        int result = CloseIfConnected();
        if (result < 0) {
            printf("error closing the socket\n");
        } else {
            printf("Connection closed.\n");
        }
      } else {
        printf("Not connected.\n");
      }
      continue;
    }



    /***** REBOOT *****/
    if (strcmp(token, "reboot") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      if (ConnectIfNeeded_Msg() < 0) continue; 
      int result = Reboot_NoMsg();
      if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("Aibo rebooting.\n");
        if (CloseIfConnected() == 0) {
          // printf("Connection closed.\n");
        }
      }
      continue;
    }



    /***** SHUTDOWN *****/
    if (strcmp(token, "shutdown") == 0) {

      if (TargetHost[0] == '\0') {
        printf("Not connected.\n");
        continue;
      }

      if (ConnectIfNeeded_Msg() < 0) continue; 
      int result = Shutdown_NoMsg();
      if (result < 0) {
        printf("--> Error: Error communicating with host! (%d)\n", result);
      } else {
        printf("Aibo shutting down.\n");
        if (CloseIfConnected() == 0) {
          //printf("Connection closed.\n");
        }
      }
      continue;
    }



    /***** BYE *****/
    if (strcmp(token, "bye") == 0) {
        exit(0);
    }
     
    /***** QUIT *****/
    if (strcmp(token, "quit") == 0) {
        exit(0);
    }
     
    /***** Q *****/
    if (strcmp(token, "q") == 0) {
        exit(0);
    }

    printf("?Invalid command\n");

  }


  return 0;
}





