/* apccomm_am_tr.c
   Part of "APCComm" 
   Copyright (C) 2000,2001 Ralf Hoffmann
   Contact: ralf.hoffmann@epost.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.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  
*/
/* $Id: apccomm_am_tr.c,v 1.7 2001/08/26 00:29:42 ralf Exp $ */

#include "apccomm_am_tr.h"
#include "apccomm_am.h"

#define DDRA 0xbfd200
#define DDRB 0xbfe301
#define PRA 0xbfd000
#define PRB 0xbfe101
#define ICR 0xbfed01

unsigned char *icr=(unsigned char*)ICR;
unsigned char *prb=(unsigned char*)PRB;
unsigned char *pra=(unsigned char*)PRA;
unsigned char *ddrb=(unsigned char*)DDRB;
unsigned char *ddra=(unsigned char*)DDRA;

int lastvalue;

int getack()
{
  unsigned char v;
  v=*pra;
  lastvalue=v&3;
  return ((v&4)==0)?0:1;
}

int getvalue()
{
  getack();
  return lastvalue;
}

void setack(int value)
{
  unsigned char v;
  v=*prb;
  if(value==0) v&=0xf;
  else v|=0x10;
  *prb=v;
}

void putnibble(int value)
{
  unsigned char v,nv,ack;
  v=*prb;
  ack=v&0x10;
  nv=value;
  nv|=ack;
  *prb=nv;
}

void putnibbleack(int value,int ack)
{
  unsigned char v;
  v=value;
  v|=ack<<2;
  *prb=v;
}

void synchro()
{
  int count;
  setack(1);
  putnibble(0);
  for(count=0;getack()!=0;count++) {
    Delay(1);
    if(count%25==0) {
      if ( ! BE_QUIET ) printf(".");
      fflush(stdout);
    }
  }
  putnibble(1);
  for(count=0;getvalue()!=1;count++) {
    Delay(1);
    if(count%25==0) {
      if ( ! BE_QUIET ) printf(".");
      fflush(stdout);
    }
  }
  putnibble(2);
  for(count=0;getvalue()!=2;count++) {
    Delay(1);
    if(count%25==0) {
      if ( ! BE_QUIET ) printf(".");
      fflush(stdout);
    }
  }
  setack(1);
}

int sendfile(const char *name, int mode)
{
  int sendfile;
  int bytecyc;
  int outchecksum,inchecksum,checksum,wrong,tc;
  int sendbytes,ts;
  double d1;
  ULONG secss,secso,secs;
  ULONG usecss,usecso,usecs;
  int count;
  char outbuf[128],sendbuf[128];
  int pos=0;
  char *outbasename;
  apc_command_t com;
  int ret=0;

  if(init_outgoing_file(name)!=0) {
    printf("can't open file %s!\n",name);
    return 1;
  } else {
    sendfile=1;
    if ( ! BE_QUIET ) printf("sending file \"%s\", size %d\n",name,outfile.size);
  }
  checksum=0;
  bytecyc=0;
  putint(outblock,APC_COMMAND_FILETRANSFER);

  putint(outblock+4,outfile.size);

  outbasename=(char*)FilePart(name);
  putint(outblock+8,min(strlen(outbasename),TRANSFERBLOCKSIZE-16));
  strncpy(outblock+12,outbasename,TRANSFERBLOCKSIZE-16);
  putint(outblock+TRANSFERBLOCKSIZE-4,permAmiga2PC(mode));

  /* sending the command to the other side
   * it get received via recvcommand
   */
  transferblock();

  com = (apc_command_t) getint( inblock );
  if ( com != APC_COMMAND_ABORT ) {

    /* preparing progress display */
    sprintf(outbuf,"%d",outfile.size);
    sprintf(sendbuf,"SEND: %%%dd/%%%dd (%%3d%%%%)",strlen(outbuf),strlen(outbuf));
    pos=6+strlen(outbuf)+1+strlen(outbuf)+2+3+1+1;
    
    inchecksum=outchecksum=0;
    sendbytes=0;
    wrong=0;
    
    /* now synchronize with other side and check state */
    if(recvcommand(&com,NULL,NULL)==0) {
      if(com==APC_COMMAND_NOP) {
	CurrentTime(&secss,&usecss);
	for(count=0;sendfile==1;count++) {
	  ts=readtobuffer(outblock,TRANSFERBLOCKSIZE-4,&outchecksum);
	  sendbytes+=ts;
	  if ( ts < 1 ) sendfile=2;
	  if ( ts < 0 ) wrong = 3;

	  if(wrong==0) putint(outblock+TRANSFERBLOCKSIZE-4,outchecksum);
	  else putint(outblock+TRANSFERBLOCKSIZE-4,0xffffffff);

	  if ( ( sendfile == 1 ) || ( wrong != 0 ) ) {
	    transferblock();
	    tc=getint(inblock+TRANSFERBLOCKSIZE/2-4);
	    if(tc==0xffffffff) wrong=2;
	  }
	  if(wrong!=0) break;
	  
	  if ( ( count == 19 ) || ( sendfile != 1 ) ) {
	    count=0;
	    if ( outfile.size )
	      sprintf(outbuf,sendbuf,sendbytes,outfile.size,(sendbytes*100)/outfile.size);
	    else
	      sprintf(outbuf,sendbuf,0,0,100);	      
	    if ( ! BE_QUIET ) printf("\r%s",outbuf);
	    fflush(stdout);
	  }
	}
	CurrentTime(&secso,&usecso);
	if ( BE_VERBOSE ) printf("\nTransfer finished");
	else if ( ! BE_QUIET ) printf("\n");
	if(wrong==1) {
	  if ( BE_VERBOSE ) printf("\n");
	  printf("Error while transfer\nI have received wrong datas!\n");
	  printf("Please repeat transmission!\n");
	  printf( "Wrong file was %s\n", name );
	} else if(wrong==2) {
	  if ( BE_VERBOSE ) printf("\n");
	  printf("Error while transfer\nThe PC reports data loss!\n");
	  printf("Please repeat transmission!\n");
	  printf( "Wrong file was %s\n", name );
	} else if(wrong==3) {
	  if ( BE_VERBOSE ) printf("\n");
	  printf("Error while reading file\n");
	  printf( "Wrong file was %s\n", name );
	} else {
	  secs=secso-secss;
	  usecs=usecso-usecss;
	  if(usecs<0) {
	    secs--;
	    usecs+=1000000;
	  }
	  
	  if ( ( secs == 0 ) && ( usecs == 0 ) ) d1 = 0.0;
	  else {
	    d1=outfile.size;
	    d1*=1000000;
	    d1/=(secs*1000000.0+usecs);
	    d1/=1024;
	  }
	  if ( BE_VERBOSE ) printf(", file sended with %d KBytes/s\n",(int)d1);

	  tfl.files++;
	  tfl.bytes += outfile.size;
	  tfl.secs += secs;
	  tfl.usecs += usecs;
	  if ( tfl.usecs >= 1000000 ) {
	    tfl.secs++;
	    tfl.usecs -= 1000000;
	  }
	}
      } else {
	if(com==APC_COMMAND_SKIPFILE) {
	  if ( ! BE_QUIET ) printf("Skipping file!\n");
	} else if(com==APC_COMMAND_ABORT) {
	  ret=-1;
	} else {
	  printf("Wrong command %d\n",com);
	  ret=1;
	}
      }
    } else {
      printf("Error while receiving command!\n");
      ret=1;
    }
  } else {
    ret = -1;
  }
  close_outgoing_file();
  free(outfile.name);
  return ret;
}

int sendcommand(apc_command_t com,const char *arg, int mode)
{
  if(com==APC_COMMAND_FILETRANSFER) {
    return sendfile(arg, mode);
  } else if(com==APC_COMMAND_ERROR) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_ENTERDIR) {
    if( (strlen(arg)+1+4) <= (TRANSFERBLOCKSIZE-4) ) {
      putint(outblock,com);
      strncpy(outblock+4,arg,TRANSFERBLOCKSIZE-4-4);
      putint(outblock+TRANSFERBLOCKSIZE-4,permAmiga2PC(mode));
      transferblock();
    } else {
      printf("Too long dirname!\n");
      return 1;
    }
  } else if(com==APC_COMMAND_LEAVEDIR) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_FINISHED) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_NOP) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_ABORT) {
    putint(outblock,com);
    transferblock();
  } else if(com==APC_COMMAND_SKIPFILE) {
    putint(outblock,com);
    transferblock();
  } else {
    printf("Unknown command %d to send!\n",com);
    return 1;
  }
  return 0;
}

int recvcommand(apc_command_t *com,char **arg, int *mode)
{
  apc_command_t tc;
  char *narg;

  transferblock();
  tc=(apc_command_t)getint(inblock);
  switch(tc) {
    case APC_COMMAND_ERROR:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_ENTERDIR:
      if(arg!=NULL) {
	narg=(char*)malloc(TRANSFERBLOCKSIZE/2-4+1);
	if(narg!=NULL) {
	  *com=tc;
	  strncpy(narg,inblock+4,TRANSFERBLOCKSIZE/2-4-4);
	  narg[TRANSFERBLOCKSIZE/2-4-4]='\0';
	  *arg=narg;
	  *mode=permPC2Amiga(getint(inblock+TRANSFERBLOCKSIZE/2-4));
	} else return 1;
      } else return 1;
      break;
    case APC_COMMAND_LEAVEDIR:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_FILETRANSFER:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      return recvfile();
      break;
    case APC_COMMAND_FINISHED:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_NOP:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_ABORT:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    case APC_COMMAND_SKIPFILE:
      *com=tc;
      if(arg!=NULL) *arg=NULL;
      if(mode!=NULL) *mode=0;
      break;
    default:
      printf("Unknown command %d received!\n",tc);
      return 1;
      break;
  }
  return 0;
}

int recvfile()
{
  char *inarg=NULL;
  int s1;
  int getfile;
  int bytecyc;
  int outchecksum,inchecksum,checksum,wrong,tc;
  int size,recbytes;
  double d1;
  ULONG secss,secsi,secs;
  ULONG usecss,usecsi,usecs;
  int count;
  char outbuf[128],recvbuf[128],*cwd,*tstr=NULL;
  int pos=0,cwdsize;
  int l;
  int ret;
  int mode;

  size=getint(inblock+4);
  l=getint(inblock+8);

  inarg=(char*)malloc(l+1);
  if(inarg==NULL) return 1;
  strncpy(inarg,inblock+12,l);
  inarg[l]='\0';

  mode=permPC2Amiga(getint(inblock+TRANSFERBLOCKSIZE/2-4));
  
  if ( ! BE_QUIET ) printf("receiving file \"%s\", size %d\n",inarg,size);
  ret=init_incoming_file(inarg);
  getfile=1;

  checksum=0;
  bytecyc=0;

  /* preparing progress display */
  sprintf(outbuf,"%d",size);
  sprintf(recvbuf,"RECV: %%%dd/%%%dd (%%3d%%%%)",strlen(outbuf),strlen(outbuf));

  if ( BE_VERBOSE ) printf("Starting transfer\n");
  inchecksum=outchecksum=0;
  recbytes=0;
  wrong=0;
  tc = 0;

  if((ret>0)||(ret==-2)) {
    printf( "Can't create %s in %s, aborting\n", inarg, basedir );
    sendcommand(APC_COMMAND_ABORT,NULL,0);
  } else if(ret==-1) {
    sendcommand(APC_COMMAND_SKIPFILE,NULL,0);
  } else if(ret==0) {
    sendcommand(APC_COMMAND_NOP,NULL,0);

    CurrentTime(&secss,&usecss);
    for(count=0;getfile==1;count++) {
      if(recbytes<size) {
	if(getfile==1) {
	  transferblock();
	  tc=getint(inblock+TRANSFERBLOCKSIZE/2-4);
	  if(tc==0xffffffff) wrong=2;
	}
	if(wrong!=0) break;
	
	s1=size-recbytes;
	if(s1>TRANSFERBLOCKSIZE/2-4) s1=TRANSFERBLOCKSIZE/2-4;
	else getfile=2;
	
	writefrombuffer(inblock,s1,&inchecksum);
	recbytes+=s1;
	checksum=inchecksum-tc;
	if(checksum!=0) wrong=1;
      } else getfile = 2;
      
      if ( ( count == 19 ) || ( getfile != 1 ) ) {
	count=0;
	if ( size )
	  sprintf(outbuf,recvbuf,recbytes,size,(recbytes*100)/size);
	else
	  sprintf(outbuf,recvbuf,0,0,100);	  
	if ( ! BE_QUIET ) printf("\r%s",outbuf);
	fflush(stdout);
      }
    }
    CurrentTime(&secsi,&usecsi);
    if ( BE_VERBOSE ) printf("\nTransfer finished");
    else if ( ! BE_QUIET ) printf("\n");
    if(wrong==1) {
      if ( BE_VERBOSE ) printf("\n");
      printf("Error while transfer\nI have received wrong datas!\n");
      printf("Please repeat transmission!\n");
    } else if(wrong==2) {
      if ( BE_VERBOSE ) printf("\n");
      printf("Error while transfer\nThe PC reports data loss!\n");
      printf("Please repeat transmission!\n");
    } else {
      secs=secsi-secss;
      usecs=usecsi-usecss;
      if(usecs<0) {
	secs--;
	usecs+=1000000;
      }
      
      if ( ( secs == 0 ) && ( usecs == 0 ) ) d1 = 0.0;
      else {
	d1=size;
	d1*=1000000;
	d1/=(secs*1000000.0+usecs);
	d1/=1024;
      }
      if ( BE_VERBOSE ) printf(", file received with %d KBytes/s\n",(int)d1);

      tfl.files++;
      tfl.bytes += size;
      tfl.secs += secs;
      tfl.usecs += usecs;
      if ( tfl.usecs >= 1000000 ) {
	tfl.secs++;
	tfl.usecs -= 1000000;
      }
    }
    close_incoming_file();
    
    /* File is stored in the current dir when basedir is NULL
     * File is stored relative to current dir in basedir when basedir contains NOT a ":"
     * File is stored in basedir when basedir contains a ":"
     */

    /* First get the cwd */
    cwdsize = 1024;
    cwd = (char*) malloc( cwdsize );
    if(cwd!=NULL) {
      while(getcwd(cwd,cwdsize)==NULL) {
	if(errno==ERANGE) {
	  free(cwd);
	  cwdsize*=2;
	  cwd=(char*)malloc(cwdsize);
	  if(cwd==NULL) break;
	} else {
	  free(cwd);
	  cwd=NULL;
	}
      }
    }

    /* now check all 3 cases */

    if ( basedir == NULL ) {

      /* just in the cwd */

      if(cwd!=NULL) {
	if(cwd[strlen(cwd)-1]==':') {
	  tstr=(char*)malloc(strlen(cwd)+strlen(infile.name)+1);
	  if(tstr!=NULL) sprintf(tstr,"%s%s",cwd,infile.name);
	} else {
	  tstr=(char*)malloc(strlen(cwd)+1+strlen(infile.name)+1);
	  if(tstr!=NULL) sprintf(tstr,"%s/%s",cwd,infile.name);
	}
      }
    } else {

      /* at least relative to basedir */

      if ( strrchr( basedir, ':' ) == NULL ) {

	/* basedir is relative */

	if ( cwd[strlen( cwd ) - 1] == ':' ) {
	  tstr = (char*) malloc( strlen( cwd ) + strlen( basedir ) + strlen( infile.name ) + 1 );
	  if ( tstr != NULL ) sprintf( tstr, "%s%s%s", cwd, basedir, infile.name );
	} else {
	  tstr = (char*) malloc( strlen( cwd ) + 1 + strlen( basedir ) + strlen( infile.name ) + 1 );
	  if(tstr!=NULL) sprintf( tstr, "%s/%s%s", cwd, basedir, infile.name );
	}
      } else {
	tstr = (char*) malloc( strlen( basedir ) + strlen( infile.name ) + 1 );
	if ( tstr != NULL ) sprintf( tstr, "%s%s", basedir, infile.name );
      }
    }

    if ( tstr != NULL ) {
      if ( ( BE_VERBOSE ) || ( wrong ) ) printf( "Incoming file is stored at: %s\n", tstr );

      if ( ! ignore_prot )
	if ( SetProtection( tstr, mode ) == 0 )
	  printf( "Can't restore file protection to %s\n", tstr );

      free( tstr );
    }
    if ( cwd != NULL) free(cwd);
    free( infile.name );
  }
  free(inarg);
  return ret;
}
