#include "filesystem.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string>
#include <windows.h>
#include "error.h"
#include "brick.h"

//#define MAX_WRITE_BYTES 0x00ff
//#define MAX_READ_BYTES 0xffff


#define MAX_WRITE_BYTES 0x159A
#define MAX_READ_BYTES 0x0800

//#define MAX_WRITE_BYTES 0x3AAA
//#define MAX_READ_BYTES 0xffff

#define OPEN_WRITE 0x81
#define OPEN_WRITE_LINEAR 0x89
#define OPEN_WRITE_LINEAR_DATA 0x8B


Filesystem::Filesystem(Connection *connection){
  this->connection = connection;
  this->file_list_size = 0;
}
Filesystem::~Filesystem(){
  free(this->file_list);
}

//fix bug with file left open when a exception is thrown
void Filesystem::download_file(string pc_name, string nxt_name){
  FILE *to;
  unsigned int i,file_size;
  unsigned char *data;
  Nxt_file from;
  if((to =fopen(pc_name.c_str(),"wb"))==NULL){
    throw Nxt_exception::Nxt_exception("download_file","Filesystem", PC_FILE_ERROR);
  }
  from = this->open_file(nxt_name, READ);
  file_size = from.size;
  data =(unsigned char *) malloc(file_size*sizeof(unsigned char));
  read_file(from, data, file_size);
  i=0;
  while(i<file_size){
    fputc(data[i], to);
    i++;
  }
  free(data);
  fclose(to);
  close_file(from);
  return;
}

void Filesystem::delete_file(string file_name, bool reply){
  unsigned int name_length;
  unsigned char command[24];
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned int i=0;
  name_length = file_name.length();
  if(name_length > MAX_NAME_LENGTH){
    file_name.erase(MAX_NAME_LENGTH+1);
    name_length=MAX_NAME_LENGTH;
  }
  command[0]=0x16;  //command length
  command[1]=0x00;
  if(reply){
    command[2]=0x01;
  }
  else{
    command[2]=0x81;
  }
  command[3]=0x85;
  while(i<name_length){
    command[i+4]=file_name[i];
    i++;
  }
  while(i<MAX_NAME_LENGTH){
    command[i+4]='\0';
    i++;
  }
  connection->send(&command[0],24);
  if(reply){
    connection->receive(&answer[0],25);
    if(answer[4]){
      throw Nxt_exception::Nxt_exception("delete_file","Filesystem", 0x00FF & answer[4]);
    }
  }
}

//fix bug with file left open when a exception is thrown
void Filesystem::upload_file(string pc_name, string nxt_name){
  FILE *from;
  unsigned int i,file_size;
  unsigned char *data;
  string file_ext;
  Nxt_file to;
  if((from =fopen(pc_name.c_str(),"rb"))==NULL){
    throw Nxt_exception::Nxt_exception("upload_file","Filesystem", PC_FILE_ERROR);
  }
  fseek( from, 0L, SEEK_END );
  file_size = ftell( from );
  fseek( from, 0, SEEK_SET);

  file_ext = nxt_name.substr( nxt_name.find_last_of(".")+1);
  i=0;
  while(i<file_ext.length()){
    file_ext[i] = std::tolower(file_ext[i]);
    i++;
  }
  if(file_ext == "ric" || file_ext == "rxe"){
     to = open_file(nxt_name, WRITE_LINEAR, file_size);
  }
  else{
     to = open_file(nxt_name, WRITE, file_size);
  }
  data = (unsigned char *) malloc(file_size * sizeof(unsigned char));
  i=0;
  while(i<file_size){
    data[i] = fgetc(from);
    i++;
  }
  write_file(to, data, file_size);
  free(data);
  fclose(from);
  close_file(to);
  return;
}

int Filesystem::get_first(string wild_card, Nxt_file *file){
  Nxt_file temp;
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned char command[24];
  unsigned int i=0;
  unsigned int name_length = wild_card.length();
  if(name_length > MAX_NAME_LENGTH){
    wild_card.erase(MAX_NAME_LENGTH+1);
    name_length=MAX_NAME_LENGTH;
  }
  command[0]=0x16;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=0x86;
  while(i<name_length){
    command[i+4]=wild_card[i];
    i++;
  }
  while(i<MAX_NAME_LENGTH){
    command[i+4]='\0';
    i++;
  }
  connection->send(&command[0],24);
  connection->receive(&answer[0],30);
  if(answer[4]){
     if((unsigned int) answer[4]!=  NXT_FILE_NOT_FOUND){
       throw Nxt_exception::Nxt_exception("get_first","Filesystem", 0x00FF & answer[4]);
     }
     else{
       return 0;
     }
  }
  file->handle = answer[5];
  i=0;
  while(i<MAX_NAME_LENGTH){
    file->name[i] = answer[6+i];
    //printf("command[%d]: 0x%x\n", i+6, answer[i+6]);
    i++;
  }
  //cout << file->name << endl;
  file->size = (0xFF & answer[26]) | ((0xFF & answer[27]) << 8)| ((0xFF & answer[28]) << 16)| ((0xFF & answer[29]) << 24);
  temp.handle = file->handle;
  //printf("\nClosing file\n");
  try{
    close_file(temp);
  }
  catch (Nxt_exception& e){
    if( e.error_code() != NXT_HANDLE_ALREADY_CLOSED){
      throw Nxt_exception::Nxt_exception("get_first","Filesystem", 0x00FF & answer[4]);
    }
  }
  return 1;
}

int Filesystem::get_next(int handle, Nxt_file *file){
  Nxt_file temp;
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned char command[5];
  unsigned int i=0;
  command[0] = 0x03;
  command[1] = 0x00;
  command[2] = 0x01;
  command[3] = 0x87;
  command[4] = handle;
  connection->send(&command[0],5);
  connection->receive(&answer[0],30);
  if(answer[4]){
    if( (unsigned int) answer[4]!=  NXT_FILE_NOT_FOUND){
       throw Nxt_exception::Nxt_exception("get_next","Filesystem", 0x00FF & answer[4]);
     }
     else{
       return 0;
     }
  }
  file->handle = answer[5];
  i=0;
  while(i<MAX_NAME_LENGTH){
    file->name[i] = answer[6+i];
    i++;
  }
  file->size = (0xFF & answer[26]) | ((0xFF & answer[27]) << 8)| ((0xFF & answer[28]) << 16)| ((0xFF & answer[29]) << 24);
  temp.handle = file->handle;
  try{
    close_file(temp);
  }
  catch (Nxt_exception& e){
    if(e.error_code () != NXT_HANDLE_ALREADY_CLOSED){
      throw Nxt_exception::Nxt_exception("get_next","Filesystem", 0x00FF & answer[4]);
    }
  }
  return 1;
}

unsigned int Filesystem::update_file_list(string wild_card){
  unsigned int idx;
  Nxt_file temp;
  free(file_list);
  if(this->get_first(wild_card, &temp)){
     file_list = (Nxt_file*) malloc(sizeof(Nxt_file));
     file_list[0] = temp;
     file_list_size = 1;
  }
  else{
    file_list_size =0;
    return file_list_size;
  }
  idx=0;
  while(this->get_next(file_list[idx].handle, &temp)){
    file_list = (Nxt_file *) realloc(file_list, (file_list_size+1)* sizeof(Nxt_file));
    file_list[idx+1] = temp;
    file_list_size++;
    idx++;
  }
  return file_list_size;
}
unsigned int Filesystem::create_file_list(string wild_card){
  return update_file_list(wild_card);
}

unsigned int Filesystem::get_file_list_size(){
  return file_list_size;
}

Nxt_file Filesystem::get_file_list_element(unsigned int idx){
  if(idx>=file_list_size){
     Nxt_file empty;
     empty.handle =0;
     empty.name[0] = '\0';
     empty.size =0;
     return empty;
  }
  else{
     return file_list[idx];
  }
}

//This is so ugly
unsigned int Filesystem::get_free_flash(){
   Brick *temp = new Brick(this->connection);
   unsigned int temp2 = temp->get_flash_memory();
   delete(temp);
   return temp2;
}

Nxt_file Filesystem::open_read(string file_name){
  Nxt_file file;
  unsigned int name_length;
  unsigned char command[24];
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned int i=0;
  name_length = file_name.length();
  if(name_length > MAX_NAME_LENGTH){
    file_name.erase(MAX_NAME_LENGTH+1);
    name_length=MAX_NAME_LENGTH;
  }
  command[0]=0x16;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=0x80;
  while(i<name_length){
    command[i+4]=file_name[i];
    i++;
  }
  while(i<MAX_NAME_LENGTH){
    command[i+4]='\0';
    i++;
  }
  connection->send(&command[0],24);
  connection->receive(&answer[0],10);
  if(answer[4]){
     throw Nxt_exception::Nxt_exception("open_read","Filesystem", 0x00FF & answer[4]);
  }
  file.handle = answer[5];
  file.size = (0xFF & answer[6]) | ((0xFF & answer[7]) << 8)| ((0xFF & answer[8]) << 16)| ((0xFF & answer[9]) << 24);
  i=0;
  while(i<MAX_NAME_LENGTH){
    file.name[i] = file_name[i];
    i++;
  }
  return file;
}

Nxt_file Filesystem::open_append(string file_name){
  Nxt_file file;
  unsigned int name_length;
  unsigned char command[24];
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned int i=0;
  name_length = file_name.length();
  if(name_length > MAX_NAME_LENGTH){
    file_name.erase(MAX_NAME_LENGTH+1);
    name_length=MAX_NAME_LENGTH;
  }
  command[0]=0x16;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=0x8C;
  while(i<name_length){
    command[i+4]=file_name[i];
    i++;
  }
  while(i<MAX_NAME_LENGTH){
    command[i+4]='\0';
    i++;
  }
  connection->send(&command[0],24);
  connection->receive(&answer[0],10);
  if(answer[4]){
     throw Nxt_exception::Nxt_exception("open_append","Filesystem", 0x00FF & answer[4]);
  }
  file.handle = answer[5];
  file.size = (0xFF & answer[6]) | ((0xFF & answer[7]) << 8)| ((0xFF & answer[8]) << 16)| ((0xFF & answer[9]) << 24);
  i=0;
  while(i<MAX_NAME_LENGTH){
    file.name[i] = file_name[i];
    i++;
  }
  return file;
}

Nxt_file Filesystem::open_write(string file_name, unsigned int file_size, char type){
  Nxt_file file;
  unsigned int name_length;
  unsigned char command[28];
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned int i=0;
  name_length = file_name.length();
  if(name_length > MAX_NAME_LENGTH){
    file_name.erase(MAX_NAME_LENGTH+1);
    name_length=MAX_NAME_LENGTH;
  }
  command[0]=0x1A;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=type;
  while(i<name_length){
    command[i+4]=file_name[i];
    i++;
  }
  while(i<MAX_NAME_LENGTH){
    command[i+4]='\0';
    i++;
  }
  command[24] = file_size & 0xff;
  command[25] = (file_size >> 8 ) & 0xff;
  command[26] = (file_size >> 16) & 0xff;
  command[27] = (file_size >> 24) & 0xff;
  connection->send(&command[0],28);
  connection->receive(&answer[0],6);
  if(answer[4]){
     throw Nxt_exception::Nxt_exception("open_write","Filesystem", 0x00FF & answer[4]);
  }
  file.handle = answer[5];
  file.size = file_size;
  i=0;
  while(i<MAX_NAME_LENGTH){
    file.name[i] = file_name[i];
    i++;
  }
  return file;
}

void Filesystem::close_file(Nxt_file &file){
  unsigned char command[5];
  unsigned char answer[NXT_BUFFER_SIZE];
  command[0]=0x03;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=0x84;
  command[4]= file.handle;

  connection->send(&command[0],5);
  connection->receive(&answer[0],6);
  if(answer[4]){
     throw Nxt_exception::Nxt_exception("close","Filesystem", 0x00FF & answer[4]);
  }
  file.name[0] = '\0';
  file.handle = 0;
  file.size = 0;
  return;
}

unsigned int Filesystem::read_file(Nxt_file &file, unsigned char *buffer, unsigned int num_bytes){
  unsigned short int bytes_to_read=0;
  unsigned int i,j;
  i=num_bytes/MAX_READ_BYTES;
  j=0;
  while(j<i){
    this->read_command(file, &buffer[j*MAX_READ_BYTES], MAX_READ_BYTES);
    j++;
  }
  bytes_to_read = num_bytes%MAX_READ_BYTES;
  if(bytes_to_read){
    this->read_command(file, &buffer[i*MAX_READ_BYTES],bytes_to_read);
  }
  return bytes_to_read + (i*MAX_READ_BYTES);
}

unsigned short int Filesystem::read_command(Nxt_file &file, unsigned char *buffer, unsigned short int num_bytes){
  unsigned char command[7];
  unsigned char *answer = (unsigned char *) malloc((num_bytes*sizeof(unsigned char))+8);
  unsigned short int bytes_to_read,i;
  command[0]=0x05;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=0x82;
  command[4]= file.handle;
  command[5] = num_bytes & 0x00ff;
  command[6] = (num_bytes >> 8 & 0x00ff);
  connection->send(&command[0],7);
  connection->receive(&answer[0],8+num_bytes);
  if(answer[4]){
    throw Nxt_exception::Nxt_exception("read_command","Filesystem", 0x00FF & answer[4]);
  }
  bytes_to_read = (0xFF & answer[6]) | ((0xFF & answer[7]) << 8);
  if(bytes_to_read != (num_bytes)){
    throw Nxt_exception::Nxt_exception("read_command","Filesystem", NXT_UNDEFINED_FILE_ERROR);
  }
  i=0;
  while(i<num_bytes){
    buffer[i]=answer[8+i];
    i++;
  }
  free(answer);
  return bytes_to_read;
}

//returns bytes wtritten
unsigned int Filesystem::write_file(Nxt_file &file, unsigned char *buffer, unsigned int num_bytes){
  unsigned short int bytes_to_write=0;
  unsigned int i,j;
  i=num_bytes/MAX_WRITE_BYTES;
  j=0;
  while(j<i){
    this->write_command(file, &buffer[j*MAX_WRITE_BYTES], MAX_WRITE_BYTES);
    j++;
  }
  bytes_to_write = num_bytes%MAX_WRITE_BYTES;
  if(bytes_to_write){
    this->write_command(file, &buffer[i*MAX_WRITE_BYTES],bytes_to_write);
  }
  return bytes_to_write + (i*MAX_WRITE_BYTES);
}

unsigned short int Filesystem::write_command(Nxt_file &file, unsigned char *buffer, unsigned short int num_bytes){
  unsigned char answer[NXT_BUFFER_SIZE];
  unsigned int i;
  unsigned short int bytes_to_write, bytes_written;
  if(num_bytes>MAX_WRITE_BYTES){
     return 0;
  }
  bytes_to_write = num_bytes + 3;
  unsigned char *command = (unsigned char *) malloc ( (num_bytes*sizeof(unsigned char))+5);
  command[0]= bytes_to_write & 0xff;  //command length
  command[1]= (bytes_to_write >> 8) & 0xff;
  command[2]=0x01;
  command[3]=0x83;
  command[4]= file.handle;
  i=0;
  while(i<num_bytes){
    command[i+5]=buffer[i];
    i++;
  }
  connection->send(&command[0], num_bytes+5);
  //Sleep(1000);
  connection->receive(&answer[0],8);
  free(command);
  if(answer[4]){
    throw Nxt_exception::Nxt_exception("write_command","Filesystem", 0x00FF & answer[4]);
  }
  bytes_written = (0xFF & answer[6]) | ((0xFF & answer[7]) << 8);
  if(bytes_written != (num_bytes)){
    throw Nxt_exception::Nxt_exception("write_command","Filesystem", NXT_UNDEFINED_FILE_ERROR);
  }
  return bytes_written;
}

Nxt_file Filesystem::open_file(string file_name, File_mode mode, unsigned int file_size ){
   Nxt_file file;
   if(mode == READ){
     return open_read(file_name);
   }
   if(mode == WRITE){
     return open_write(file_name, file_size, OPEN_WRITE);
   }
   if(mode == WRITE_LINEAR){
       return open_write(file_name, file_size, OPEN_WRITE_LINEAR);
   }
   if(mode==WRITE_LINEAR_DATA){
      return open_write(file_name, file_size, OPEN_WRITE_LINEAR_DATA);
   }
   if(mode== APPEND){
     return open_append(file_name);
   }
   file.handle=0;
   file.name[0] = '\0';
   file.size = 0;
   return file;
}

void Filesystem::delete_file(Nxt_file &file, bool reply){
   return this->delete_file(file.name, reply);
}

void Filesystem::delete_flash(){
  unsigned char command[4];
  unsigned char answer[NXT_BUFFER_SIZE];
  command[0]=0x02;  //command length
  command[1]=0x00;
  command[2]=0x01;
  command[3]=0xA0;

  connection->send(&command[0],4);
  //if(!connection->get_type() == NXT_NETWORK){
  Sleep(3000);
    //sleep is done on the server over network
  //}
  connection->receive(&answer[0],5);
  if(answer[4]){
     throw Nxt_exception::Nxt_exception("delete_flash","Filesystem", 0x00FF & answer[4]);
  }
  return;
}