/***************************************************************************
 *   copyright           : (C) 2002 by Hendrik Sattler                     *
 *   mail                : post@hendrik-sattler.de                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "scmxx.h"
#include "helper.h"
#include "options.h"
#include "atcommand.h"
#include "ttyaccess.h"
#include "gtincl.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>

void file_transfer_manage (int   action, char** files,
			   char* outfile, char* pipe,
			   char* mem, int slot)
{
  char* filename;
  size_t fn_size;
  struct slot_range** range;
  struct slot_range** drange; //for free slot detection
  int enable_search = 1;
  int i = 0;
  int j = 0;
  int k = 0;
  int current;

  if (mem == NULL) {
    errexit ("%s\n",_("You must define a memory to use."));
  }
  if (strcmp(mem,"?") == 0) {
    file_print_memlist(NULL,1);
    return;
  }

  if (action == 0) {
    errexit ("%s\n",_("You must specify a valid action."));
  }

  if (slot < SCMXX_SLOT_BINARY_MIN &&
      slot != SCMXX_SLOT_UNDEFINED &&
      slot != SCMXX_SLOT_ALL) {
    errexit ("%s\n",_("You must define either a valid slot, nothing or \"all\"."));
  }

  range = file_types_get(action);
  if (range == NULL || range[0] == NULL) {
    errexit("%s\n",_("this phone does not support file exchange."));
  }
  while (range[i] != NULL &&
	 strcmp(range[i]->name,mem)) {
    ++i;
  }
  if (range[i] == NULL) {
    errexit("type %s is not supported.\n",mem);
  }
  if (slot >= SCMXX_SLOT_BINARY_MIN &&
      (range[i]->min > slot || slot > range[i]->max)) { // ... test if slot is correctly set
    if (range[i]->min != range[i]->max) {
      errexit(_("%s values can only be %d to %d.\n"),range[i]->name,range[i]->min,range[i]->max);
    } else {
      errexit(_("%s value can only be %d.\n"),range[i]->name,range[i]->min);
    }
  }
    
  switch(action){
  default:
    errexit("%s\n",_("You must specify exactly one operation."));
    break;
  case SCMXX_ACTION_REMOVE: //deleting
    if (slot == SCMXX_SLOT_ALL) {
      for (current=range[i]->min;current<=range[i]->max;current++) {
	file_delete(mem, current);
      }
    } else if (slot < SCMXX_SLOT_BINARY_MIN) {
      errexit ("%s\n",_("You must define a valid slot or \"all\"."));
    } else {
      file_delete(mem, slot);
    }
    break;
  case SCMXX_ACTION_SEND: //sending
    if (slot != SCMXX_SLOT_ALL) {
      // get the detection range (read, not write)...
      drange = file_types_get(SCMXX_ACTION_SEND);
      // ...find the mem type in it...
      while (drange[j] != NULL &&
	     strcmp(drange[j]->name,mem)) {
	++j;
      }
      if (drange[j] == NULL) {
	// ...and either disable searching if not found...
	enable_search = 0;
	mem_realloc(drange,0);
	drange = range;
	j = i;
      } else {
	// ...or mak min/max a subrange of both ranges
	if (drange[j]->min < range[i]->min) {
	  drange[j]->min = range[i]->min;
	}
	if (drange[j]->max > range[i]->max) {
	  drange[j]->max = range[i]->max;
	}    
      }
    } else {
      drange = range;
      j = i;
    }

    // define current slot...
    if (slot < range[i]->min) { 
      current = range[i]->min; // ...to minimum if below...
    } else {
      current = slot; // ...or the defined slot
    }
    while (files[k] != NULL && current <= range[i]->max) {
      print_verbose(0,_("Using slot %d\n"),current);
      file_send(files[k],mem,current);
      if (k == 0) {
	if (enable_search == 0 && slot != SCMXX_SLOT_ALL) {
	  //stop here if we cannot search for free slots
	  break;
	} else {
	  //make sure that current is in searchable range
	  if (current < drange[j]->min) {
	    current = drange[j]->min;
	  }
	}
      }
      ++k;
      if (files[k] != NULL) {
	if (slot == SCMXX_SLOT_ALL) {
	  ++current; // simply increase if all slot shall be used
	} else {
	  // only empty slots shall be used
	  current = file_detect_free_slot(mem,current,drange[j]->max);
	  if (current < 0) {
	    errexit("%s\n",_("No free slot found."));
	  } else {
	    print_verbose(0,_("Detected empty slot %d\n"),current);
	  }
	}
      }
    }
    break;
  case SCMXX_ACTION_GET: //getting
    if (slot == SCMXX_SLOT_UNDEFINED) {
      slot = SCMXX_SLOT_ALL;
    }
    if (slot == SCMXX_SLOT_ALL) {
      fn_size = str_len(outfile)+numlen(range[i]->max)+1+strlen(mem);
      for (k=range[i]->min;k<=range[i]->max;k++) {
	if (outfile != NULL) {
	  if (!strcmp(outfile,"-")) {
	    file_get(outfile,pipe,mem,k,k-range[i]->min+1,range[i]->max-range[i]->min+1);
	  } else {
	    filename = mem_alloc(fn_size+1,1);
	    snprintf(filename,fn_size+1,"%s%0*d.%s",
		     outfile,numlen(range[i]->max),k,mem);
	    file_get(filename,pipe,mem,k,k-range[i]->min+1,range[i]->max-range[i]->min+1);
	    free(filename);
	  }
	} else {
	  errexit("%s\n",_("You must specify a file name prefix (can be \"\")."));
	}
      }
    } else {
      i = 0;
      k = 1;
      file_get(outfile,pipe,mem,slot,i,k);
    }
    fprintf(stderr,"\n");
    break;
  }
}

void file_delete (char* ftype, int slot) {
  char* ausgabe;

  if (at_sie_binary_write(ftype,slot,0,0) == NULL) return;
  ausgabe = at_read_line();
  if (!strcmp(ausgabe,"OK")) {
    print_verbose(0,_("%s %d deleted.\n"),ftype,slot);
  } else {
    errexit(_("%s %d NOT deleted, something went wrong: %s\n"),ftype,slot,ausgabe);
  }
  mem_realloc(ausgabe,0);
}

int file_detect_free_slot (char* ftype, int minimum, int maximum) {
  char* ausgabe;
  char* content;
  int i;
  enum return_code status;
    
  print_verbose(0,"%s\n",_("Trying to find an empty slot..."));
  i = minimum;
  while (i <= maximum) {
    if (at_sie_binary_read(ftype,i) == NULL) break;
    ausgabe = at_read_line();
    status = at_line_type(ausgabe,AT_SIE_BIN_READ,&content);
    switch (status) {
    case AT_RET_ERROR_CME:
      if (strcmp(content,"unknown")) errexit("%s\n",content);
      /* no break */
    case AT_RET_OK:
      mem_realloc(ausgabe,0);
      return i;
    case AT_RET_ERROR:
      errexit("%s\n",_("unknown cause"));
    case AT_RET_ERROR_CMS:
      errexit("%s\n",content);
    default: 
      do {
	mem_realloc(ausgabe,0);
	ausgabe = at_read_line();
	status = at_line_type(ausgabe,AT_SIE_BIN_READ,&content);
      } while (status == AT_RET_ANSWER ||
	       status == AT_RET_OTHER);
      if (status == AT_RET_OK) {
	++i;
      } else if (status == AT_RET_ERROR) {
	errexit("%s\n",_("unknown cause"));
      } else {
	errexit("%s\n",content);
      }
      mem_realloc(ausgabe,0);
      break;
      //no break
    }
  }
  return -1;
}

/* these are just for documenting the
 * paket size limits
 */
#define FILE_PDUSIZE_MAX_SPEC 176 //max as in spec
#define FILE_PDUSIZE_MAX_SL42 175 //max for SL42 v23
#define FILE_PDUSIZE_MIN_S45 6 //min for S45/ME45 v10

#define FILE_PDUSIZE 175

void file_send (char* file, char* ftype, int slot) {
  char* data;
  unsigned int count = file_read_binary(file,&data);

  unsigned int pcount = (count/FILE_PDUSIZE);
  unsigned int i = 1;
  unsigned int k;
  unsigned int psize = FILE_PDUSIZE;
  char buffer[(FILE_PDUSIZE*2)+1];
  char* ack;
  char* vendor = at_get_vendor();
  char* model = at_get_model();
  unsigned int strategy = 0;

  if (count%FILE_PDUSIZE) ++pcount;

  /* we need different stategies here
   * 0: equal packet size except last packet (S55)
   * 1: try to split the last two packets equally (S45/ME45)
   * It does not matter for other models, see above for default.
   */
  if (strcasecmp(vendor,"SIEMENS") == 0 &&
      (strcasecmp(model,"C45") == 0 || /* not sure if needed but works with this mode */
       strcasecmp(model,"ME45") == 0 ||
       strcasecmp(model,"S45") == 0 ||
       strcasecmp(model,"S45i") == 0)) {
    strategy = 1;
  }
  mem_realloc(vendor,0);
  mem_realloc(model,0);

  print_verbose(0,"%s\n",_("File transfer..."));
  for (; i <= pcount; ++i) {
    /* split the transfer size between the last two packets
     * make the first one bigger
     */
    switch (strategy) {
    default:
    case 0:
      if (psize > count) psize = count;
      break;
    case 1:
      if (count < 2*FILE_PDUSIZE && count > FILE_PDUSIZE)
	psize = count/2 + (count%2); /* second last packet */
      else if (count < FILE_PDUSIZE)
	psize = count; /* last packet */
      break;
    }

    /* filling buffer */
    memset(buffer,0,sizeof(buffer));
    for (k = 0; k < psize; ++k) {
      sprintf(buffer+(k*2),"%02x",((uint8_t*)data)[k]&0xFF);
    }
    data += k;
    count -= k;

    //starting packet transfer
    if (at_sie_binary_write(ftype,slot,i,pcount) == NULL) return;
    print_verbose(0,"%s",_("Waiting for data request..."));
    ack = at_read_line();

    if (strncmp(ack,"> ",2) != 0) {
      if (strcmp(ack,"+CME ERROR: INVALID INDEX") == 0) {
	print_verbose(0,"%s\n",_("Packet buffer seems to be filled with something else, clearing..."));
	if (at_sie_binary_write(ftype,slot,-1,-1) == NULL) return;
	mem_realloc(ack,0);
	ack = at_read_line();
	if (strcmp(ack,"OK") != 0)
	  errexit("%s\n",ack);
      } else {
	errexit("\n\"%s\"\n",ack);
      }
    }
    mem_realloc(ack,0);


    if (verbosity_get()) {
      print_verbose(1,_("\nSending %d Bytes in packet %d\n"),psize,i);
    } else {
      print_verbose(0,"%s",_("Sending data..."));
    }

    //sending hex data
    at_data_send(buffer,2*psize);
    ack = at_read_line();
    if (strcmp(ack,"OK") == 0) {
      print_verbose(0,_("Packet %d sent\n"),i);
    } else if (strcmp(ack,"+CME ERROR: INV CHAR IN TEXT") == 0) {
      errexit("%s\n",_("Wrong data format"));
    } else {
      errexit("\n\"%s\"\n",ack);
    }
    mem_realloc(ack,0);
  }
  if (pcount) {
    print_verbose(0,"%s\n",_("File transfer complete."));
  } else {
    print_verbose(0,"%s\n",_("Nothing to transfer."));
  }
}

void file_get (char* file, char* pipe,
	       char* ftype, unsigned long slot,
	       unsigned long count, unsigned long total)
{
  char* ausgabe;
  char* temp = NULL;
  char* command;
  char* parsestr;
  enum return_code atcode;
  unsigned long current[2];
  unsigned long totals[2];
  int myfd = -1;
  unsigned int i = 0;
  unsigned char data;
  char datastr[3];
  FILE* pipefd = NULL;
  char* text;
  long text_size = console_width(0);

  memset(datastr,0,sizeof(datastr));

  command = at_sie_binary_read(ftype,slot);
  if (command == NULL) return;

  text = mem_alloc(text_size,1);
  snprintf(text,text_size,_("%s slot %lu"),ftype,slot);
  current[0] = 0;
  totals[0] = 1;
  current[1] = count;
  totals[1] = total;

  parsestr = mem_alloc(1+strlen(ftype)+2+numlen(slot)+(2*(strlen(",%d")+1))+1,1);
  /* example: ^SBNR: "vcf",9,1,3 */
  sprintf(parsestr,"\"%s\",%lu,%%d,%%d",ftype,slot);
  do{
    ausgabe = at_read_line();
    atcode = at_line_type(ausgabe,command,&temp);
    //here we have to check not only for an output of OK, but because of
    //funny behaviour of the S55 and later, also for "+CME ERROR: unknown".
    //both stand for an empty entry
    if (atcode == AT_RET_OK ||
	(atcode == AT_RET_ERROR_CME && strcmp(temp,"unknown") == 0)) {
      mem_realloc(ausgabe,0);
      mem_realloc(parsestr,0);
      ++current[1];
      console_print_status(STDERR_FILENO,text,strlen(text),current,totals,2);
      file_close(myfd);
      return;
    } else if (atcode&AT_RET_ERROR) {
      file_close(myfd);
      errexit("%s\n",(temp)?temp:ausgabe);			
    } else {
      console_print_status(STDERR_FILENO,text,strlen(text),current,totals,2);
      if (2 != sscanf(temp,parsestr,&current[0],&totals[0]))
	errexit(_("parsing answer from phone failed: \"%s\"\n"),ausgabe);
      if (str_len(file) && myfd==-1) {
	myfd=file_open_output(file);
      }
      if (str_len(pipe) && pipefd==NULL) {
	pipefd=popen(pipe,"w");
      }
      mem_realloc(ausgabe,0);
      ausgabe = at_read_line();
      for (i=0;i<strlen(ausgabe);i+=2) {	
	data=hexstr2int(ausgabe+i,2);
	if (myfd!=-1) {
	  if (write(myfd,&data,1) == -1) {
	    file_close(myfd);
	    if (str_len(pipe) && pipefd!=NULL) {
	      pclose(pipefd);
	    }
	    errexit(_("writing to file \"%s\" failed: %s\n"),file, strerror(errno));
	  }
	}
	if (str_len(pipe) && pipefd!=NULL) {
	  if (fwrite(&data,1,1,pipefd)!=1) {
	    file_close(myfd);
	    pclose(pipefd);
	    errexit(_("writing to pipe \"%s\" failed.\n"),pipe);
	  }
	}
      }
      mem_realloc(ausgabe,0);
    }
  } while (current[0] != totals[0]);
  ++current[1];
  console_print_status(STDERR_FILENO,text,strlen(text),current,totals,2);

  file_close(myfd);
  if (str_len(pipe) && pipefd!=NULL && pclose(pipefd)==-1) {
    file_close(myfd);
    errexit(_("closing pipe \"%s\" failed.\n"),pipe);
  }
  ausgabe = at_read_line();
  if (at_line_type(ausgabe,NULL,&temp) == AT_RET_OK) {
    mem_realloc(ausgabe,0);
  } else {
    errexit("%s\n",temp);
  }
}
