/*
   +-----------+                     +-----------------+ 
   |           |                     |     ARDUINO     |
   | VIA 6522  |                     |      NANO       |
   |           |                     |                 |
   |       PA0 |<------ bit 0 ------>| D2              |                +-------\ 
   |       PA1 |<------ bit 1 ------>| D3          D10 |----- SS ------>|        \
   |       PA2 |<------ bit 2 ------>| D4          D11 |----- MOSI ---->|  SD    |
   |       PA3 |<------ bit 3 ------>| D5          D12 |<---- MISO -----|  CARD  |
   |       PA4 |<------ bit 4 ------>| D6          D13 |----- SCK ----->|        |
   |       PA5 |<------ bit 5 ------>| D7              |                +--------+
   |       PA6 |<------ bit 6 ------>| D8              |
   |       PA7 |<------ bit 7 ------>| D9              |
   |           |                     |                 |
   |       PB7 |<---- MCU_STROBE ----| D14/A0          |
   |       PB0 |--- CPU_STROBE ----->| D15/A1          |
   |           |                     |                 |
   +-----------+                     +-----------------+ 
*/

/*
PORT ASSIGNMENT FOR FAST READ/WRITE
===================================
BIT 0:      PORTD[2]
BIT 1:      PORTD[3]
BIT 2:      PORTD[4]
BIT 3:      PORTD[5]
BIT 4:      PORTD[6]
BIT 5:      PORTD[7]
BIT 6:      PORTB[0]
BIT 7:      PORTB[1]
MCU_STROBE: PORTC[0]
CPU_STROBE: PORTC[1]

in Verilog syntax: data = { PORTB[1:0], PORTD[7:2] };

*/

// FASTWRITE not working (yet)
// #define FASTWRITE 1

#ifdef FASTWRITE
#define get_cpu_strobe    ((PORTC >> 1) & 1)
#define set_mcu_strobe(c) PORTC = (PORTC & 0xFE) | (c)
#else
#define get_cpu_strobe digitalRead(CPU_STROBE)
#define set_mcu_strobe(c) digitalWrite(MCU_STROBE,(c))
#endif

#include <SPI.h>
#include "SdFat.h"

SdFat SD;

#define SD_CS_PIN SS

// pin definitions

#define BIT0 2   
#define BIT1 3   
#define BIT2 4   
#define BIT3 5   
#define BIT4 6   
#define BIT5 7   
#define BIT6 8   
#define BIT7 9   

/* 10,11,12,13 reserved for SD card */

#define MCU_STROBE  14   
#define CPU_STROBE  15   

// indicates that a timeout occurred during wait_cpu_strobe()
byte TIMEOUT = 0;

void wait_cpu_strobe(byte value) {
    
  unsigned long start_time = millis();  
  unsigned long elapsed;  

  if(TIMEOUT) return;
  
  while(get_cpu_strobe != value) {
    elapsed = millis() - start_time;
    if(elapsed > 500) {
      TIMEOUT = 1;
      break;
    }      
  }
}

const byte DIR_INPUT  = 0;
const byte DIR_OUTPUT = 1;

byte last_dir; // remember last data port pins direction 

void set_data_port_direction(byte dir) {

  // check if no need to set pins
  if(dir == last_dir) return;
  
  if(dir == DIR_INPUT) {
    pinMode(BIT0, INPUT);
    pinMode(BIT1, INPUT);
    pinMode(BIT2, INPUT);
    pinMode(BIT3, INPUT);
    pinMode(BIT4, INPUT);
    pinMode(BIT5, INPUT);
    pinMode(BIT6, INPUT);
    pinMode(BIT7, INPUT);     
  }
  else {
    pinMode(BIT0, OUTPUT);
    pinMode(BIT1, OUTPUT);
    pinMode(BIT2, OUTPUT);
    pinMode(BIT3, OUTPUT);
    pinMode(BIT4, OUTPUT);
    pinMode(BIT5, OUTPUT);
    pinMode(BIT6, OUTPUT);
    pinMode(BIT7, OUTPUT);
  }  

  // remember direction
  last_dir = dir;
}

byte receive_byte_from_cpu() {

  // set data port pins as INPUT pins
  set_data_port_direction(DIR_INPUT);
  
  // both strobes are 0   
  
  // CPU deposits data byte and sets strobe high
  wait_cpu_strobe(HIGH);           
  
  // read the data byte
  #ifdef FASTWRITE  
  byte data = ((PORTB & 0x03)<<6) | (PORTD >> 2);  // data = { PORTB[1:0], PORTD[7:2] }
  #else
  byte data = 
    (digitalRead(BIT0) << 0) |
    (digitalRead(BIT1) << 1) |
    (digitalRead(BIT2) << 2) |
    (digitalRead(BIT3) << 3) |
    (digitalRead(BIT4) << 4) |
    (digitalRead(BIT5) << 5) |
    (digitalRead(BIT6) << 6) |
    (digitalRead(BIT7) << 7);
  #endif 

  // after reading the byte, MCU sets strobe high
  set_mcu_strobe(HIGH);

  // CPU now sets strobe low
  wait_cpu_strobe(LOW);

  // and MCU sets strobe low
  set_mcu_strobe(LOW);
    
  return data;
}

void send_byte_to_cpu(byte data) {

  // set data port pins as OUTPUT pins
  set_data_port_direction(DIR_OUTPUT);

  // both strobes are 0
  
  // put byte on the data port
  #ifdef FASTWRITE  
  PORTB = (PORTB & 0xFC) | ((data >> 6) & 0x03);   // PORTB[1:0] = data[7:6];
  PORTD = (PORTD & 0x03) | (data << 2);            // PORTD[7:2] = data[5:0];  
  #else
  digitalWrite(BIT0, data &   1);
  digitalWrite(BIT1, data &   2);
  digitalWrite(BIT2, data &   4);
  digitalWrite(BIT3, data &   8);
  digitalWrite(BIT4, data &  16);
  digitalWrite(BIT5, data &  32);
  digitalWrite(BIT6, data &  64);
  digitalWrite(BIT7, data & 128);
  #endif
  
  // after depositing data byte, MCU sets strobe high
  set_mcu_strobe(HIGH);
  
  // wait for CPU to set strobe high
  wait_cpu_strobe(HIGH);  
  
  // tells CPU byte we are finished
  set_mcu_strobe(LOW); 

  // wait for CPU to set strobe low
  wait_cpu_strobe(LOW);  
}

// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************

const byte CMD_READ  =  0;
const byte CMD_WRITE =  1;
const byte CMD_DIR   =  2;
const byte CMD_LOAD  =  4;
const byte CMD_DEL   = 11;
const byte CMD_LS    = 12;
const byte CMD_CD    = 13;
const byte CMD_MKDIR = 14;
const byte CMD_PWD   = 19;
const byte CMD_RMDIR = 15;
const byte CMD_TEST  = 20;
const byte CMD_MOUNT = 23;

const byte ERR_RESPONSE  = 255;
const byte WAIT_RESPONSE =   1;
const byte OK_RESPONSE   =   0;

// @buffer
char filename[64]; 
char tmp[64];
char cd_path[64]; 

// fixed messages
const char *NOT_A_FILE = "?NOT A FILE";
const char *FILE_NOT_FOUND = "?FILE NOT FOUND";
const char *CANT_OPEN_FILE = "?CAN'T OPEN FILE";
const char *ALREADY_EXISTS = "?FILE ALREADY EXISTS";
const char *CANT_CREATE_FILE = "?CAN'T CREATE FILE";
const char *WRITE_ERROR = "?WRITE ERROR";
const char *CANT_DELETE_FILE = "?CAN'T DELETE FILE";
const char *FILE_DELETED = " DELETED";
const char *DIR_NOT_FOUND = "?DIR NOT FOUND";
const char *CANT_REMOVE_DIR = "?CAN'T REMOVE DIR";
const char *DIR_REMOVED = " (DIR) REMOVED";
const char *DIR_ALREADY_EXISTS = "?DIR ALREADY EXISTS";
const char *CANT_MAKE_DIR = "?CAN'T MAKE DIR";
const char *DIR_CREATED = " (DIR) CREATED";
const char *CANT_CD_DIR = "?CAN'T CHANGE DIR";
const char *NOT_A_DIRECTORY = "?NOT A DIRECTORY";
const char *MOUNT_OK = "MOUNTING SDCARD...\rOK";
const char *MOUNT_FAILED = "?SD CARD ERROR";

// file structures used to operate with the SD card
File myFile;
File myDir;

void setup() {
  // debug on serial
  Serial.begin(9600);  
  Serial.println(F("SDCARD library: SDFat.h"));

  mount_sdcard();
  
  // control pins setup
  pinMode(CPU_STROBE, INPUT);
  pinMode(MCU_STROBE, OUTPUT);  
  digitalWrite(MCU_STROBE, LOW);   

  // data pins setup
  last_dir = -1;  // no previous data direction  
  set_data_port_direction(DIR_INPUT);

  // set working directory to root
  strcpy(cd_path, "/");
}

bool mount_sdcard() {
  // initialize SD card
  if(!SD.begin(SD_CS_PIN)) {
    Serial.println(F("SD card initialization failed"));
    return false;
  }
  else {
    Serial.println(F("SD card initialized"));
    return true;
  }
}

// **************************************************************************************
// **************************************************************************************
// ********************************* @CMD_DIR  ******************************************
// **************************************************************************************
// **************************************************************************************

byte list_files;
void comando_dir(byte command) {
  Serial.println(F("command DIR received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("dir to read: "));
  Serial.println(filename);  
  
  if(filename[0]==0) {
    // no argument given, use cd_path
    strcpy(filename, cd_path);    
  }

  myDir = SD.open(filename);

  if(!myDir) {
    Serial.println(F("dir not found"));    
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(DIR_NOT_FOUND);
    return;    
  }

  // dir opened OK
  send_byte_to_cpu(OK_RESPONSE);

  list_files = 0;  // starts listing dirs (0) then files (1)

  while(true) {
    byte c = receive_byte_from_cpu();
    if(TIMEOUT) {
      Serial.println(F("timeout while receiving 'next line' byte"));    
      break; 
    } else if(c == WAIT_RESPONSE) {
      // do nothing, just keep listening
      continue;   
    } else if(c == ERR_RESPONSE) {
      // user asked to abort dir listing
      Serial.println(F("dir interrupted by the user"));
      break;       
    } else if(c == OK_RESPONSE) {
      if(!send_next_dir_line(command)) {
        Serial.println(F("dir terminated"));
        break;
      }
    }
  }
  myDir.close();
}

byte send_next_dir_line(byte command) {
  // user wants a new line of dir listing

  while(1) {
    myFile = myDir.openNextFile();
    
    if(!myFile) {
      if(list_files==0) {
        list_files++;
        myDir.rewind();
        continue;
      }
      else {
        // no more lines to send       
        myDir.close(); 
        send_byte_to_cpu(ERR_RESPONSE);   
        return 0; // no more lines
      }
    }

    if(myFile.isDirectory() && list_files == 0) break;
    if(!myFile.isDirectory() && list_files == 1) break;
  }
  
  // send the text line 
  send_byte_to_cpu(OK_RESPONSE);    
  send_directory_entry(command);                  
  myFile.close(); 
  return 1; // more lines to send
}

void send_directory_entry(byte command) {
  myFile.getName(filename, 64);
  strtoupper(filename);
    
  if(myFile.isDirectory()) {
    if(command != CMD_DIR) {
      sprintf(tmp, "(DIR) %s\r", filename);
      print_string_to_cpu(tmp);
      Serial.println(tmp);     
    }  
    else {
      sprintf(tmp, "%-15s (DIR)\r", filename);
      print_string_to_cpu(tmp);
      Serial.println(tmp);     
    }                
  }
  else {
    if(command != CMD_DIR) {
      // BUG the following line does not work
      // sprintf(tmp, "%5d %s", entry.size(), filename);        
      // use this instead
      sprintf(tmp, "%5d ", myFile.size());
      print_string_to_cpu(tmp);
      Serial.print(tmp);       

      print_string_to_cpu(filename);                
      Serial.println(filename);               

      send_byte_to_cpu('\r');           
    }
    else {                            
      char type[5];
      char address[5];      
      
      strcpy(type,"");
      strcpy(address, ""); 

      char *x = strchr(filename, '#');
      if(x != NULL) {
        *x++ = 0;              
        if(x[0]=='0' && x[1]=='6') {
            strcpy(type,"BIN ");
            strcpy(address,x+2);  
        }
        else if(x[0]=='F' && x[1]=='1') {
            strcpy(type,"BAS ");
            strcpy(address,x+2);  
        }        
        else if(x[0]=='F' && x[1]=='8') {
            strcpy(type,"ASB ");
            strcpy(address,x+2);
        }
        else {
            type[0] = '#';
            type[1] = x[0];
            type[2] = x[1];
            strcpy(address,x+2);
        }
      }
                            
      sprintf(tmp, "%-15s", filename);
      print_string_to_cpu(tmp);                              
      Serial.print(filename);               

      sprintf(tmp, "%6d ", myFile.size());
      print_string_to_cpu(tmp);
      Serial.print(tmp);

      print_string_to_cpu(type);
      Serial.print(type);

      if(type[0]!=0) {
        send_byte_to_cpu('$');  
        Serial.print("$");      
      }
      print_string_to_cpu(address);     
      Serial.println(address);

      send_byte_to_cpu('\r');
    }
  }          
}

// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_READ  ******************************************
// **************************************************************************************
// **************************************************************************************

void comando_read() {
  Serial.println(F("command CMD_READ received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("file to read: "));
  Serial.println(filename);

  if(!SD.exists(filename)) {
    Serial.println(F("error opening file"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(FILE_NOT_FOUND);
    return;    
  }

  // open the file 
  myFile = SD.open(filename);
  if(!myFile) {            
    Serial.println(F("error opening file"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(CANT_OPEN_FILE);
    return;
  }

  if(myFile.isDirectory()) {
    myFile.close();
    Serial.println(F("not a file"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(NOT_A_FILE);
    return;
  }

  Serial.println(F("file opened on the SD card"));

  // ok response
  send_byte_to_cpu(OK_RESPONSE);
  if(TIMEOUT) return;
  Serial.println(F("ok response sent to CPU"));
    
  // sends size as low and high byte
  int size = myFile.size();
  send_byte_to_cpu(size & 0xFF);
  send_byte_to_cpu((size >> 8) & 0xFF);
  if(TIMEOUT) return;
  Serial.println(F("file size sent to CPU"));

  int bytes_sent = 0;    
  while(myFile.available() && !TIMEOUT) {      
    send_byte_to_cpu(myFile.read());
    if(!TIMEOUT) bytes_sent++;
  }    
  myFile.close();

  if(TIMEOUT) {
    Serial.print(F("timeout, bytes sent: ")); 
    Serial.println(bytes_sent); 
    return;
  }
    
  Serial.println(F("file read ok"));
}

void send_string_to_cpu(char *msg) {
  while(1) {
    byte c = *msg++;
    send_byte_to_cpu(c);
    if(TIMEOUT) break;
    if(c==0) break;
  }
}

// come send_string_to_cpu ma senza lo zero finale
void print_string_to_cpu(char *msg) {
  while(1) {
    byte c = *msg++;
    if(c==0) break;
    send_byte_to_cpu(c);
    if(TIMEOUT) break;    
  }
}


void receive_string_from_cpu(char *msg) {
  while(1) {
    byte c = receive_byte_from_cpu();
    if(TIMEOUT) break;    
    *msg++ = c;
    if(c==0) break;
  }  
}

// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_WRITE  *****************************************
// **************************************************************************************
// **************************************************************************************

int receive_word_from_cpu() {
  byte lo = receive_byte_from_cpu();
  byte hi = receive_byte_from_cpu();
  int data = lo | (hi << 8);  
  return data;
}

void comando_write() {
  Serial.println(F("command CMD_WRITE received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("file to write: "));
  Serial.println(filename);
  
  if(SD.exists(filename)) {
    Serial.println(F("file already exist"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(ALREADY_EXISTS);
    return;    
  }

  // open the file 
  myFile = SD.open(filename, FILE_WRITE);
  if(!myFile) {            
    Serial.println(F("error opening file for write"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(CANT_CREATE_FILE);
    return;
  }  
  Serial.println(F("file opened for write on the SD card"));

  // ok response
  send_byte_to_cpu(OK_RESPONSE);
  if(TIMEOUT) return;
  Serial.println(F("first ok response sent to CPU"));
    
  // get file size low and high byte
  int size = receive_word_from_cpu();
  if(TIMEOUT) return;
  Serial.print(F("received file size: "));
  Serial.println(size);

  byte error = 0;
  for(int t=0;t<size;t++) {
    byte data = receive_byte_from_cpu();
    if(TIMEOUT) break;
    int n = myFile.write((unsigned char)data);
    if(n!=1) error = 1;
  }

  // close the file
  myFile.close();

  if(TIMEOUT) return;

  // report write issues
  if(error) {
    Serial.println(F("file write error"));    
    send_byte_to_cpu(ERR_RESPONSE);
    if(TIMEOUT) return;
    send_string_to_cpu(WRITE_ERROR);
    return;
  }
      
  Serial.println(F("file read ok"));
  send_byte_to_cpu(OK_RESPONSE);
}

// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_LOAD  ******************************************
// **************************************************************************************
// **************************************************************************************

void comando_load() {
  Serial.println(F("command CMD_LOAD received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("file to read: "));
  Serial.println(filename);

  // if a matching file name is found, use it
  if(matchname(filename, tmp)==1) {
    Serial.print(F("found matching file: "));
    Serial.println(tmp);
    strcpy(filename, tmp);
  }

  if(!SD.exists(filename)) {
    Serial.println(F("error opening file"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(FILE_NOT_FOUND);
    return;    
  }

  // open the file 
  myFile = SD.open(filename);
  if(!myFile) {            
    Serial.println(F("error opening file"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(CANT_OPEN_FILE);
    return;
  }

  if(myFile.isDirectory()) {
    myFile.close();
    Serial.println(F("not a file"));
    send_byte_to_cpu(ERR_RESPONSE);
    send_string_to_cpu(NOT_A_FILE);
    return;
  }

  Serial.println(F("file opened on the SD card"));

  // ok response
  send_byte_to_cpu(OK_RESPONSE);
  if(TIMEOUT) return;
  Serial.println(F("ok response sent to CPU"));

  // sends matched filename
  send_string_to_cpu(filename);
    
  // sends size as low and high byte
  int size = myFile.size();
  send_byte_to_cpu(size & 0xFF);
  send_byte_to_cpu((size >> 8) & 0xFF);
  if(TIMEOUT) return;
  Serial.println(F("file size sent to CPU"));

  int bytes_sent = 0;    
  while(myFile.available() && !TIMEOUT) {      
    send_byte_to_cpu(myFile.read());
    if(!TIMEOUT) bytes_sent++;
  }    
  myFile.close();

  if(TIMEOUT) {
    Serial.print(F("timeout, bytes sent: ")); 
    Serial.println(bytes_sent); 
    return;
  }
    
  Serial.println(F("file read ok"));
}

void strtoupper(char *str){
      int len = strlen(str), i;
       
      for(i=0;i<len;i++)
            if(str[i]>='a' && str[i]<='z')
                 str[i] = str[i]-'a'+'A';
}

// splits filename into file_path and file_name
// e.g. "root/myfolder/pluto" => "root/myfolder" , "pluto"
//      "myfolder/pluto" => "myfolder", "pluto"
//      "pluto" => "", "pluto"
//      "/pluto" => "/", "pluto"

void split_path(char *filename, char *file_path, char *file_name) {
  strcpy(file_path, filename);
  
  for(int t=strlen(file_path)-1;t>0;t--) {
    if(file_path[t] == '/') {
       file_path[t] = 0;
       strcpy(file_name, &file_path[t+1]);  
       if(t==0) strcpy(file_path, "/");  // case of root folder
       return;
    }
  }
  strcpy(file_path, "");
  strcpy(file_name, filename);  
}

//
// returns in dest the first file that matches (starts with) "filename"
// returns 1 if matching file is found
// returns 0 if no matching file is found
//
byte matchname(char *filename, char *dest) {
  // @buffer
  char file_path[64];
  char file_name[64];

  // split filename into file_path and file_name
  split_path(filename, file_path, file_name);

  Serial.print(F("after split file_path="));
  Serial.print(file_path);
  Serial.print(F(", file_name="));
  Serial.println(file_name);

  if(strlen(file_path)==0) Serial.println(F("scanning the current directory"));
  else                     Serial.println(F("scanning the file_path directory"));

  // open the directory containing the file
  myDir = SD.open(strlen(file_path)==0 ? cd_path : file_path);

  if(!myDir) return 0;

  Serial.println(F("dir opened"));

  // scans twice: scanmode=0 for exact match, scanmode=1 for partial match
  for(byte scanmode=0;scanmode<=1;scanmode++) {
    if(scanmode==0) Serial.println(F("scanning for exact match..."));
    if(scanmode==1) Serial.println(F("scanning for partial match..."));
   
    // scan all the directory
    while(1) {

      send_byte_to_cpu(WAIT_RESPONSE);
      if(TIMEOUT) return 0;        
      
      myFile = myDir.openNextFile();
      
      // end of directory
      if(!myFile) {
        if(scanmode == 0) {
          // exact match not found, rewind the directory and scan fot partial match
          myDir.rewind();
          break;
        }
        else if(scanmode == 1) {
          // match not found even with partial match, return error
          myDir.close();
          return 0;      
        }
      }

      // copy filename in dest and closes it
      myFile.getName(dest, 64);
      strtoupper(dest);
      myFile.close();

      Serial.print(F("file entry: "));
      Serial.println(dest);

      // verify the match
      int len = strlen(file_name);
      bool match_found = strncmp(file_name, dest, len) == 0;

      // enforce exact match
      if(scanmode == 0) match_found = match_found && dest[strlen(file_name)] == '#';

      if(match_found) {
        if(scanmode==0) Serial.println(F("found an exact match"));
        if(scanmode==1) Serial.println(F("found a partial match"));

        // matching, dest already contains the matched file name

        // if file_path is empty then it's current directory, do nothing
        // else file_path needs to be combined with file name
        if(file_path[0]!=0) {
          Serial.println(F("not on current dir, joining paths"));
          strcpy(filename, dest);
          if(file_path[0]=='/' && file_path[1]==0)  sprintf(dest,"/%s", filename);              // case of root folder
          else                                      sprintf(dest,"%s/%s", file_path, filename); // case of normal nested folder
          //Serial.println(filename);
          //Serial.println(file_path);
          //Serial.println(dest);
        }
        myDir.close();
        return 1;           
      }    
    }
  }     
}

// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_DEL ********************************************
// **************************************************************************************
// **************************************************************************************

void comando_del() {
  Serial.println(F("command DEL received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("file to delete: "));
  Serial.println(filename);
  
  if(!SD.exists(filename)) {
    Serial.println(F("file does not exist"));
    send_string_to_cpu(FILE_NOT_FOUND);
    return;    
  }

  if(TIMEOUT) return;

  // open the file for deletion
  myFile = SD.open(filename, FILE_WRITE);
  if(!myFile) {            
    Serial.println(F("error opening file for deletion"));    
    send_string_to_cpu(CANT_DELETE_FILE);
    myFile.close();
    return;
  }  

  if(TIMEOUT) return;
    
  if(!myFile.remove()) {
    Serial.println(F("error removing file"));
    send_string_to_cpu(CANT_DELETE_FILE);      
    myFile.close();
    return;
  }

  // close the file
  myFile.close();

  print_string_to_cpu(filename);  
  send_string_to_cpu(FILE_DELETED); 

  Serial.println(F("file deleted"));       
}

// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_RMDIR ******************************************
// **************************************************************************************
// **************************************************************************************

void comando_rmdir() {
  Serial.println(F("command RMDIR received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("directory to delete: "));
  Serial.println(filename);

  if(TIMEOUT) return;

  // check if the directory exists
  if(!SD.exists(filename)) {
    Serial.println(F("dir does not exist"));
    send_string_to_cpu(DIR_NOT_FOUND);
    return;
  }

  // removes the directory
  if(!SD.rmdir(filename)) {
    Serial.println(F("error removing dir"));
    send_string_to_cpu(CANT_REMOVE_DIR);
    return;
  }

  print_string_to_cpu(filename);
  send_string_to_cpu(DIR_REMOVED);

  Serial.println(F("dir removed"));
}

// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_MKDIR ******************************************
// **************************************************************************************
// **************************************************************************************

void comando_mkdir() {
  Serial.println(F("command MKDIR received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("directory to create: "));
  Serial.println(filename);

  if(TIMEOUT) return;

  // check if the directory exists
  if(SD.exists(filename)) {
    Serial.println(F("dir already exist"));
    send_string_to_cpu(DIR_ALREADY_EXISTS);
    return;
  }

  // removes the directory
  if(!SD.mkdir(filename)) {
    Serial.println(F("error creating"));
    send_string_to_cpu(CANT_MAKE_DIR);
    return;
  }

  print_string_to_cpu(filename);
  send_string_to_cpu(DIR_CREATED);

  Serial.println(F("dir created"));
}

// **************************************************************************************
// **************************************************************************************
// ********************************* @CMD_CD ********************************************
// **************************************************************************************
// **************************************************************************************

void comando_cd() {
  Serial.println(F("command CD received from CPU"));

  // reads filename as 0 terminated string
  receive_string_from_cpu(filename);
  if(TIMEOUT) return;
  Serial.print(F("directory to change to: "));
  Serial.println(filename);

  if(TIMEOUT) return;

  // CD without arguments
  if(filename[0] == 0) {
    // ok response
    send_byte_to_cpu(OK_RESPONSE);
    return;
  }

  // CD ..
  if(filename[0] == '.' && filename[1] == '.' && filename[2] == 0) {  
    strcpy(filename, cd_path);
    for(int t=strlen(filename)-1;t>=1;t--) {
      byte c = filename[t];
      filename[t] = 0;
      if(c == '/') break;        
    }
  }

  // CD /
  if(filename[0] == '/' && filename[1] == 0) {
    // changes to the root directory
    if(SD.chdir()) {
      // set root working directory
      strcpy(cd_path, filename);      
      Serial.print(F("dir changed to:"));
      Serial.println(cd_path);
      send_byte_to_cpu(OK_RESPONSE);
      return;      
    }   
    else {
      Serial.println(F("error changing dir"));
      send_byte_to_cpu(ERR_RESPONSE);
      send_string_to_cpu(CANT_CD_DIR);
      return;
    }
  }

  // CD dirname  
  if(SD.chdir(filename)) {
    // update working directory
    if(filename[0] == '/') {       
      strcpy(cd_path, filename);     // path is absolute, replace cwd   
    }
    else {
      // path is relative, append to cwd
      if(cd_path[0] == '/' && cd_path[1] == 0) {        
        cd_path[0] = 0;  // avoid the double slash ("//DIR") when cwd is root 
      }
      sprintf(tmp, "%s/%s", cd_path, filename);
      strcpy(cd_path, tmp);
    }
    
    
    Serial.print(F("dir changed to:"));
    Serial.println(cd_path);    
    send_byte_to_cpu(OK_RESPONSE);
    return;
  } 

  // errors 

  send_byte_to_cpu(ERR_RESPONSE);
  
  // check if the directory exists
  if(!SD.exists(filename)) {
    Serial.println(F("dir not found"));    
    send_string_to_cpu(DIR_NOT_FOUND);
    return;
  }
  
  myFile = SD.open(filename);
  bool isDir = myFile.isDirectory();
  myFile.close();
  
  if(!isDir) {            
    Serial.println(F("not a directory"));    
    send_string_to_cpu(NOT_A_DIRECTORY);    
  }
  else {
    Serial.println(F("error changing dir"));    
    send_string_to_cpu(CANT_CD_DIR);    
  }
}

// **************************************************************************************
// **************************************************************************************
// ********************************* @CMD_PWD *******************************************
// **************************************************************************************
// **************************************************************************************

void comando_pwd() {
  Serial.println(F("command PWD received from CPU"));
  send_string_to_cpu(cd_path);
}

// **************************************************************************************
// **************************************************************************************
// ********************************* LOOP  **********************************************
// **************************************************************************************
// **************************************************************************************

void loop() {  
  TIMEOUT = 0;

  byte data = receive_byte_from_cpu();
  if(TIMEOUT) return;

  unsigned long start_time = millis();

       if(data == CMD_READ)  comando_read();      
  else if(data == CMD_WRITE) comando_write();      
  else if(data == CMD_LOAD)  comando_load();      
  else if(data == CMD_DEL)   comando_del();    
  else if(data == CMD_RMDIR) comando_rmdir();    
  else if(data == CMD_MKDIR) comando_mkdir();     
  else if(data == CMD_CD)    comando_cd();      
  else if(data == CMD_PWD)   comando_pwd();      
  else if(data == CMD_DIR)   comando_dir(data);    
  else if(data == CMD_LS)    comando_dir(data);
  else if(data == CMD_TEST) {
    while(!TIMEOUT) {
      byte data = receive_byte_from_cpu();
      send_byte_to_cpu(data ^ 0xFF);
    }
  }
  else if(data == CMD_MOUNT) {
    bool mount = mount_sdcard();
    if(mount) send_string_to_cpu(MOUNT_OK);
    else send_string_to_cpu(MOUNT_FAILED);
  }
  else {
    Serial.print(F("unknown command "));
    Serial.print(data);
    Serial.println(F(" received"));
  }

  Serial.print(F("command processing time: "));
  Serial.println(millis() - start_time);
  
  if(TIMEOUT) Serial.println(F("TIMEOUT during command"));    
}
