/* 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.
  ========================================================================= */

#include <math.h>
#include <OPENR/OPENRAPI.h>
#include <OPENR/OUnits.h>
#include <OPENR/OSyslog.h>
#include <OPENR/ODebug.h>
#include <OPENR/core_macro.h>
#include "Reload.h"
#include <sys/stat.h>  // for 'stat' function, to get file attributes
#include <dirent.h>    // for 'DIR', 'dirent' structure to get dir entries
#include <unistd.h>    // for 'rmdir' function (oddly, 'mkdir' is in stat.h)


#ifdef DEBUG
NetTCPConnection Telnet;
#endif

char debugmsg[256];
void debug(const char* msg) {
#ifdef DEBUG
    NetSend(Telnet, msg);
#endif
}

Reload::Reload()
{
}

OStatus
Reload::DoInit(const OSystemEvent& event)
{
  NEW_ALL_SUBJECT_AND_OBSERVER;
  REGISTER_ALL_ENTRY;
  SET_ALL_READY_AND_NOTIFY_ENTRY;
    
  return oSUCCESS;
}

OStatus
Reload::DoStart(const OSystemEvent& event)
{
  ENABLE_ALL_SUBJECT;
  ASSERT_READY_TO_ALL_OBSERVER;

  Command = '\0';
  State = RSS_DISCONNECTED;

  // Listen for a connection on port 50000, setup callbacks

  // For checking
#ifdef DEBUG
    NetTCPListen(50001, &Reload::TelnetConnected, NULL, NULL, NULL);
#endif

    NetTCPListen(50000, &Reload::Connected,
                        &Reload::Sent,
                        &Reload::Received,
                        &Reload::Disconnected);

    return oSUCCESS;
}


OStatus
Reload::DoStop(const OSystemEvent& event)
{
  DISABLE_ALL_SUBJECT;
  DEASSERT_READY_TO_ALL_OBSERVER;

  return oSUCCESS;
}

OStatus
Reload::DoDestroy(const OSystemEvent& event)
{
  DELETE_ALL_SUBJECT_AND_OBSERVER;
  return oSUCCESS;
}


/* Extra listening connection-- for testing */
#ifdef DEBUG
void Reload::TelnetConnected(NetTCPConnection Conn, int Dummy) {

    // If successful, choose this as our active connection
    if (Dummy != 0) {
        Telnet = Conn;
    }

    NetSend(Conn, "Connected. Using this connection as live log\n");
 
    // Start new Connection
    NetTCPListen(50001, &Reload::TelnetConnected, NULL, NULL, NULL);
}
#endif

void Reload::Connected(NetTCPConnection Conn, int Dummy) {
// Connect Callback

    debug("Connected.\n");

    // If successful, choose this as our active connection
    if (Dummy != 0) {
        Listen = Conn;

       // We want to receive exactly 1 byte now.
       State = RSS_WAITING_FOR_COMMAND;
       NetNextReceive(Conn, 1, 1);
    }

    // Start new Connection
    NetTCPListen(50000, &Reload::Connected,
                        &Reload::Sent,
                        &Reload::Received,
                        &Reload::Disconnected);

}


int Reload::List(NetTCPConnection Conn, char* name, unsigned char depth)
{
  //   
  //   Will write to dest the following:
  //   For each entry:
  //     - four-byte size in bytes
  //     - one-byte type specifier: File ('F'), Directory ('D')
  //     - one-byte name length parameter -- 0 to signal end of listing
  //     - the name
  //
  //   If a directory is specified with recursion depth 0, only the directory entry
  //   is returned. If a file is specified, the depth field is irrelevant.
  //   An invalid target name will return just one entry, with a zero as name length
  //   The name provided must be an absolute path begininng with '/ms/'

  int error;
  struct stat Stat;
  char buf[10];

  error = stat(name, &Stat);            // Get stat for file/dir
  if(error != 0){                     // File does not exist 
    return -1;                        //  [can (should) only happen on root request]
  }

  // If ok, send real stats for this entry
  *(long*)buf = (unsigned long)Stat.st_size;   // Size in bytes
  buf[4] = 'F';                         // Set 'F' for File
  if(S_ISDIR(Stat.st_mode)){
    buf[4] = 'D';                     // Set 'D' for Directory
  }
  buf[5] = strlen(name);
  NetSend(Conn, buf, 6);                // Send data so far
  NetSend(Conn, name, buf[5]);          // Send file or dir name

  if((buf[4] == 'D') && (depth > 0)){
    // if it's a directory and we haven't reached depth limit, recurse 
    DIR* thisdir;
    struct dirent* entry;
    char* newname;

    thisdir = opendir(name);
    while((entry = readdir(thisdir)) != NULL){
      if((strcmp(entry->d_name, ".")  != 0) &&
          (strcmp(entry->d_name, "..") != 0)){
        newname = (char*)malloc(buf[5] + strlen(entry->d_name) + 2);
        sprintf(newname, "%s/%s", name, entry->d_name);
        List(Conn, newname, depth-1);
        free(newname);
      }
    }
    closedir(thisdir);
  }
  return 0;

}


int Reload::Start_Get(NetTCPConnection Conn, char* name)
{
  // Send file info, set up file stransfer.

  int error;
  struct stat Stat;

    error = stat(name, &Stat);
    if ((error != 0) || (!S_ISREG(Stat.st_mode))) {
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);   // If error, start over
        NetSend(Conn, "\xFF", 1);     // But send error code
        return -1;
    }

    // Use internal output buffering: don't send until we got at least 5 bytes
    Conn->MinSend = 5;
    NetSend(Conn, "\0", 1);           // Send 0 for OK
    NetSend(Conn, &Stat.st_size,  4); // Send filesize
    Conn->MinSend = 0;                // Turn output buffering back off
    NetNextReceive(Conn, 1, 1);

    // Save file size and set byte count to track how many bytes are left
    filesize = count = Stat.st_size;
    

    // Open file. Actual sending will be done in Sent callback
    State = RSS_GET__SENDING_FILE;
    file = fopen(name, "r");
    return 0;
}


int Reload::Start_Put(NetTCPConnection Conn,
                      char* filename, unsigned long filesize)
{
  // Start receiving file

  file = fopen(filename, "w");

    if (file == NULL) {
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        NetSend(Conn, "\xFF", 1);   // Send error (non-zero) response
        return -1;
    }

    if (filesize == 0) {
        // just close it and send ack's.
        fclose(file);
        NetSend(Conn, "\0", 1);    // Send OK (zero) response
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        NetSend(Conn, "\0", 1);  // Send confirmation zero byte;
        return 0;
    }

    count = 0;
    State = RSS_PUT__RECEIVING_FILE;
    NetNextReceive(Conn, 1, Conn->RecvBufSize);
    NetSend(Conn, "\0", 1);    // Send OK (zero) response
    return 0;
}


void Reload::Touch(NetTCPConnection Conn, char* filename) {
    int error;
    struct stat Stat;

    State = RSS_WAITING_FOR_COMMAND;
    NetNextReceive(Conn, 1, 1);

    error = stat(filename, &Stat);
    if ((error != 0) || (S_ISREG(Stat.st_mode))) {
        // process only files, existing or newly created.
        file = fopen(filename, "a");

        if (file == NULL) {
            NetSend(Conn, "\xFF", 1);   // Send error (non-zero) response
            return;
        }

        fclose(file);
    }

    NetSend(Conn, "\0", 1);     // Send OK (zero) response
    return;

}


void Reload::Sent(NetTCPConnection Conn, int Bytes)
// Send Callback. Only state to serve is: we were sending a file
{
  int i;
  switch (State){

    case RSS_GET__SENDING_FILE:

        // if no bytes to send, go back to waiting for command
        if (count == 0) {
            State = RSS_WAITING_FOR_COMMAND;
            return;
        }

        // serve a bufferful of data or the last remaining bytes 
        if (count > Conn->SendBufSize) {
            i = fread(Conn->SendBuffer, 1, Conn->SendBufSize, file);
            count -= Conn->SendBufSize;
        } else {
            i = fread(Conn->SendBuffer, 1, count, file);
            count = 0;
            fclose(file);
        }

        // if after this we want to stop sending, reset variables
        if ((count == 0) || (i == 0) || (i == -1)) {
            count = 0;
            State = RSS_WAITING_FOR_COMMAND;
        }

        // Send the data
        if (i > 0) {
            NetSend(Conn, Conn->SendBuffer, i);
        } 

        break;

    default:
      break;
  }
}


// Receive callback. Makes heavy use of states to determine what we
// were doing and what to do next.
void Reload::Received(NetTCPConnection Conn, int Bytes)
{
  // Declare variables for local use
  OBootCondition BootCondition;
  OTime Clock;

  if(Bytes == 0){
    // Error, just reset to base state.
    State = RSS_WAITING_FOR_COMMAND;
    NetNextReceive(Conn, 1, 1);
    return;
  }

  switch(State) {

// ===============================================
//            -- Command Selection --
// ===============================================

    case RSS_WAITING_FOR_COMMAND:
        if (Bytes > 1) {
            // Error, command should be 1 byte only. 
            NetNextReceive(Conn, 1, 1);
            debug("*** Error Receiving Command! (size > 1) ***\n");
        }
        Command = Conn->ReceiveBuffer[0];

        switch(Command) {
        
        case 'l':   // LIST
            State = RSS_LIST__WAITING_FOR_PARAMS;
            debug("Received LIST command\n");
            NetNextReceive(Conn, 2, 2+255);
            break;

        case 'g':   // GET
            State = RSS_GET__WAITING_FOR_PARAMS;
            debug("Received GET command\n");
            NetNextReceive(Conn, 1, 1+255);
            break;

        case 'p':   // PUT
            State = RSS_PUT__WAITING_FOR_PARAMS;
            debug("Received PUT command\n");
            NetNextReceive(Conn, 5, 5+255);
            break;

        case 't':   // TOUCH
            State = RSS_TOUCH__WAITING_FOR_PARAMS;
            debug("Received TOUCH command\n");
            NetNextReceive(Conn, 1, 1+255);
            break;

        case 'm':   // MKDIR
            State = RSS_MKDIR__WAITING_FOR_PARAMS;
            debug("Received MKDIR command\n");
            NetNextReceive(Conn, 1, 1+255);
            break;

        case 'v':   // RMDIR
            State = RSS_RMDIR__WAITING_FOR_PARAMS;
            debug("Received RMDIR command\n");
            NetNextReceive(Conn, 1, 1+255);
            break;

        case 'd':   // REMOVE
            State = RSS_REMOVE__WAITING_FOR_PARAMS;
            debug("Received REMOVE command\n");
            NetNextReceive(Conn, 1, 1+255);
            break;

        case 'r' :  // REBOOT
            debug("Received REBOOT command\n");
            BootCondition.bitmap = obcbBOOT_TIMER;
            BootCondition.bootTime = 0;
            BootCondition.bootTimeType = obcbttRELATIVE;
            OPENR::Shutdown(BootCondition);
            break;

        case 'x':   // SHUTDOWN
            debug("Received SHUTDOWN command\n");
            OPENR::Fatal(ofatalPAUSE_SW);
            break;

        case 's':   // SETTIME
            State = RSS_SETTIME__WAITING_FOR_PARAMS;
            debug("Received SETTIME command\n");
            NetNextReceive(Conn, 5, 5);
            break;

        default:
            debug("Received <unknown> command\n");
            NetNextReceive(Conn, 1, 1);
            break;
        }
        break;

// ===============================================
//                     LIST
// ===============================================

    case RSS_LIST__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "LIST: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        list_depth  = Conn->ReceiveBuffer[0];
        name_length = Conn->ReceiveBuffer[1];
        memcpy(filename, &Conn->ReceiveBuffer[2], name_length);
        filename[name_length] = '\0';

        Conn->MinSend = 2048;  // use buffering

        if (List(Conn, filename, list_depth) == 0) {
            NetSend(Conn, "0000-\0", 6);  // Send termination record
        } else {
            NetSend(Conn, "0000E\0", 6);  // Send Error end record
        }
        NetSend(Conn, NULL);  // Flush
        Conn->MinSend = 0;    // Reset to no buffering

        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        break;

   // ===============================================
   //                      GET
   // ===============================================

    case RSS_GET__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "GET: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        name_length = Conn->ReceiveBuffer[0];
        memcpy(filename, &Conn->ReceiveBuffer[1], name_length);
        filename[name_length] = '\0';
        Start_Get(Conn, filename);
        break;

    case RSS_GET__SENDING_FILE:
        debug("GET: *** ERROR *** Data received while RSS_GET__SENDING_FILE\n");
        break;

    // ===============================================
    //                      PUT
    // ===============================================

    case RSS_PUT__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "PUT: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        filesize  = *(long*)Conn->ReceiveBuffer;
        name_length = Conn->ReceiveBuffer[4];
        memcpy(filename, &Conn->ReceiveBuffer[5], name_length);
        filename[name_length] = '\0';
        Start_Put(Conn, filename, filesize); 
        break;

    case RSS_PUT__RECEIVING_FILE:
        debug("o..");
        fwrite(Conn->ReceiveBuffer, 1, Bytes, file);
        count += Bytes;
        if (count > filesize) {
            debug("\nPUT: *** ERROR *** Received more bytes than filesize\n");
            // Error: Received more bytes than expected
            fclose(file);
            State = RSS_WAITING_FOR_COMMAND;
            NetNextReceive(Conn, 1, 1);
        }
        if (count == filesize) {
            debug("\nPUT: Finished receiving, closing file\n");
            fclose(file);
            State = RSS_WAITING_FOR_COMMAND;
            NetNextReceive(Conn, 1, 1);
            NetSend(Conn, "\0", 1);  // Send confirmation zero byte;
        }
        break;

// ===============================================
//                     TOUCH
// ===============================================

    case RSS_TOUCH__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "TOUCH: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        name_length = Conn->ReceiveBuffer[0];
        memcpy(filename, &Conn->ReceiveBuffer[1], name_length);
        filename[name_length] = '\0';
        Touch(Conn, filename); 

        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);

        break;

// ===============================================
//                      MKDIR
// ===============================================

    case RSS_MKDIR__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "MKDIR: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        name_length = Conn->ReceiveBuffer[0];
        memcpy(filename, &Conn->ReceiveBuffer[1], name_length);
        filename[name_length] = '\0';
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        if (mkdir(filename, 0777) == 0) {
            debug("OK\n");
            NetSend(Conn, "\0", 1);
        } else {
            debug("*** ERROR ***\n");
            NetSend(Conn, "\xFF", 1);
        }
        break;

// ===============================================
//                      RMDIR
// ===============================================

    case RSS_RMDIR__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "RMDIR: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        name_length = Conn->ReceiveBuffer[0];
        memcpy(filename, &Conn->ReceiveBuffer[1], name_length);
        filename[name_length] = '\0';
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        if (rmdir(filename) == 0) {
            debug("OK\n");
            NetSend(Conn, "\0", 1);
        } else {
            debug("*** ERROR ***\n");
            NetSend(Conn, "\xFF", 1);
        }
        break;

// ===============================================
//                      REMOVE
// ===============================================

    case RSS_REMOVE__WAITING_FOR_PARAMS:
        sprintf(debugmsg, "REMOVE: Received params - %d bytes\n", Bytes);
        debug(debugmsg);
        name_length = Conn->ReceiveBuffer[0];
        memcpy(filename, &Conn->ReceiveBuffer[1], name_length);
        filename[name_length] = '\0';
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        if (remove(filename) == 0) {
            debug("OK\n");
            NetSend(Conn, "\0", 1);
        } else {
            debug("*** ERROR ***\n");
            NetSend(Conn, "\xFF", 1);
        }
        break;

// ===============================================
//                     SETTIME
// ===============================================

    case RSS_SETTIME__WAITING_FOR_PARAMS:
        debug("SETTIME: Received params\n");
        OStatus ostatus;
        Clock.SetClock(*(time_t*)Conn->ReceiveBuffer);  // Greenwich time
        Clock.SetTimeDif(Conn->ReceiveBuffer[4]);  // (signed) time difference in hours
        ostatus = OPENR::SetTime(Clock);  // Set system hardware clock.
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        if (ostatus == oSUCCESS) {
            NetSend(Conn, "\0", 1);    // Send OK
        } else {
            NetSend(Conn, "\xFF", 1);  // Send error (non-zero) byte
        }
        break;

// ===============================================
//
// ===============================================

    default:
        debug("*** Unexpected state! ***\n");
        State = RSS_WAITING_FOR_COMMAND;
        NetNextReceive(Conn, 1, 1);
        break;
    }

}


void Reload::Disconnected(NetTCPConnection Conn, int Dummy) {

    debug("Disconnected.\n");
    // Should be RSS_DISCONNECTED, but this helps with erroneous
    // multiple connections
    State = RSS_WAITING_FOR_COMMAND;

  // Listen for connection again 
  Listen = NetTCPListen(50000, &Reload::Connected,
                        &Reload::Sent,
                        &Reload::Received,
                        &Reload::Disconnected);
}


